# SPDX-FileCopyrightText: 2020-2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later

from abc import ABC, abstractmethod
import bpy
import os
import sys

sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from modules.mesh_test import RunTest


class CurvesTest(ABC):

    def __init__(self, test_object_name, exp_object_name, test_name=None):
        self.test_object_name = test_object_name
        self.exp_object_name = exp_object_name
        self.test_object = bpy.data.objects[self.test_object_name]
        self.expected_object = bpy.data.objects[self.exp_object_name]
        self.verbose = os.getenv("BLENDER_VERBOSE") is not None

        if test_name:
            self.test_name = test_name
        else:
            filepath = bpy.data.filepath
            self.test_name = bpy.path.display_name_from_filepath(filepath)
        self._failed_tests_list = []

    def create_evaluated_object(self):
        """
        Creates an evaluated object.
        """
        bpy.context.view_layer.objects.active = self.test_object

        # Duplicate test object.
        bpy.ops.object.mode_set(mode="OBJECT")
        bpy.ops.object.select_all(action="DESELECT")
        bpy.context.view_layer.objects.active = self.test_object

        self.test_object.select_set(True)
        bpy.ops.object.duplicate()
        self.evaluated_object = bpy.context.active_object
        self.evaluated_object.name = "evaluated_object"

    @staticmethod
    def _print_result(result):
        """
        Prints the comparison, selection and validation result.
        """
        print("Results:")
        for key in result:
            print("{} : {}".format(key, result[key][1]))
        print()

    def run_test(self):
        """
        Runs a single test, runs it again if test file is updated.
        """
        print("\nSTART {} test.".format(self.test_name))

        self.create_evaluated_object()
        self.apply_operations()

        result = self.compare_objects(self.evaluated_object, self.expected_object)

        # Initializing with True to get correct resultant of result_code booleans.
        success = True
        inside_loop_flag = False
        for key in result:
            inside_loop_flag = True
            success = success and result[key][0]

        # Check "success" is actually evaluated and is not the default True value.
        if not inside_loop_flag:
            success = False

        if success:
            self.print_passed_test_result(result)
            # Clean up.
            if self.verbose:
                print("Cleaning up...")
            # Delete evaluated_test_object.
            bpy.ops.object.delete()
            return True

        else:
            self.print_failed_test_result(result)
            return False

    @abstractmethod
    def apply_operations(self, evaluated_test_object_name):
        pass

    @staticmethod
    def compare_curves(evaluated_curves, expected_curves):
        if len(evaluated_curves.attributes.items()) != len(expected_curves.attributes.items()):
            print("Attribute count doesn't match")

        for a_idx, attribute in evaluated_curves.attributes.items():
            expected_attribute = expected_curves.attributes[a_idx]

            if len(attribute.data.items()) != len(expected_attribute.data.items()):
                print("Attribute data length doesn't match")

            value_attr_name = (
                'vector' if attribute.data_type == 'FLOAT_VECTOR' or
                attribute.data_type == 'FLOAT2' else
                'color' if attribute.data_type == 'FLOAT_COLOR' else 'value'
            )

            for v_idx, attribute_value in attribute.data.items():
                if getattr(
                        attribute_value,
                        value_attr_name) != getattr(
                        expected_attribute.data[v_idx],
                        value_attr_name):
                    print("Attribute '{}' values do not match".format(attribute.name))
                    return False

        return True

    def compare_objects(self, evaluated_object, expected_object):
        result_codes = {}

        equal = self.compare_curves(evaluated_object.data, expected_object.data)

        result_codes['Curves Comparison'] = (equal, evaluated_object.data)
        return result_codes

    def print_failed_test_result(self, result):
        """
        Print results for failed test.
        """
        print("FAILED {} test with the following: ".format(self.test_name))

    def print_passed_test_result(self, result):
        """
        Print results for passing test.
        """
        print("PASSED {} test successfully.".format(self.test_name))


class CurvesOpTest(CurvesTest):

    def __init__(self, test_name, test_object_name, exp_object_name, operators_stack):
        super().__init__(test_object_name, exp_object_name, test_name)
        self.operators_stack = operators_stack

    def apply_operations(self):
        for operator_name in self.operators_stack:
            bpy.ops.object.mode_set(mode='EDIT')
            curves_operator = getattr(bpy.ops.curves, operator_name)

            try:
                retval = curves_operator()
            except AttributeError:
                raise AttributeError("bpy.ops.curves has no attribute {}".format(operator_name))
            except TypeError as ex:
                raise TypeError("Incorrect operator parameters {!r} raised {!r}".format([], ex))

            if retval != {'FINISHED'}:
                raise RuntimeError("Unexpected operator return value: {}".format(operator_name))
            bpy.ops.object.mode_set(mode='OBJECT')


def main():
    tests = [
        CurvesOpTest("Extrude 1 Point Curve", "a_test1PointCurve", "a_test1PointCurveExpected", ['extrude']),
        CurvesOpTest("Extrude Middle Points", "b_testMiddlePoints", "b_testMiddlePointsExpected", ['extrude']),
        CurvesOpTest("Extrude End Points", "c_testEndPoints", "c_testEndPointsExpected", ['extrude']),
        CurvesOpTest("Extrude Neighbors In Separate Curves", "d_testNeighborsInCurves", "d_testNeighborsInCurvesExpected", ['extrude']),
        CurvesOpTest("Extrude Edge Curves", "e_testEdgeCurves", "e_testEdgeCurvesExpected", ['extrude']),
        CurvesOpTest("Extrude Middle Curve", "f_testMiddleCurve", "f_testMiddleCurveExpected", ['extrude']),
        CurvesOpTest("Extrude All Points", "g_testAllPoints", "g_testAllPointsExpected", ['extrude'])
    ]

    curves_extrude_test = RunTest(tests)

    command = list(sys.argv)
    for i, cmd in enumerate(command):
        if cmd == "--run-all-tests":
            curves_extrude_test.do_compare = True
            curves_extrude_test.run_all_tests()
            break
        elif cmd == "--run-test":
            curves_extrude_test.do_compare = False
            name = command[i + 1]
            curves_extrude_test.run_test(name)
            break


if __name__ == "__main__":
    main()
