import mathutils, bpy, os
from pathlib import Path
from bpy.types import Scene, Operator, Armature
from bpy.props import StringProperty, BoolProperty
from bpy.utils import register_class, unregister_class
from bpy.ops import object
from ...utility import PluginError, ExportUtils, toAlnum, writeCData, raisePluginError
from .properties import OOTAnimExportSettingsProperty, OOTAnimImportSettingsProperty
from ..exporter.animation import ootExportLinkAnimation, ootExportNonLinkAnimation
from .importer import ootImportLinkAnimationC, ootImportNonLinkAnimationC

from ..utility import (
    ootGetPath,
    addIncludeFiles,
    addIncludeFilesExtension,
    checkEmptyName,
    ootGetObjectPath,
    getOOTScale,
    add_include_to_spec_segment,
)


def exportAnimationC(armatureObj: bpy.types.Object, settings: OOTAnimExportSettingsProperty):
    if settings.isCustom:
        checkEmptyName(settings.customPath)
    elif not settings.isLink:
        checkEmptyName(settings.folderName)

    if settings.isCustomFilename:
        checkEmptyName(settings.filename)

    path = bpy.path.abspath(settings.customPath)
    if settings.isCustom:
        exportPath = path
    elif not settings.isLink:
        exportPath = ootGetObjectPath(settings.isCustom, path, settings.folderName, False)
    else:
        exportPath = None  # Won't be used as the export is not custom

    checkEmptyName(armatureObj.name)
    name = toAlnum(armatureObj.name)
    filename = settings.filename if settings.isCustomFilename else name
    convertTransformMatrix = (
        mathutils.Matrix.Scale(getOOTScale(armatureObj.ootActorScale), 4)
        @ mathutils.Matrix.Diagonal(armatureObj.scale).to_4x4()
    )

    if settings.isLink:
        ootAnim = ootExportLinkAnimation(armatureObj, convertTransformMatrix, name)
        ootAnimC, ootAnimHeaderC = ootAnim.toC(settings.isCustom)

        path = ootGetPath(
            exportPath,
            settings.isCustom,
            "assets/misc/link_animetion",
            "",
            True,
            False,
        )
        headerPath = ootGetPath(
            exportPath,
            settings.isCustom,
            "assets/objects/gameplay_keep",
            "",
            True,
            False,
        )
        writeCData(
            ootAnimC, os.path.join(path, ootAnim.dataName() + ".h"), os.path.join(path, ootAnim.dataName() + ".c")
        )
        writeCData(
            ootAnimHeaderC,
            os.path.join(headerPath, ootAnim.headerName + ".h"),
            os.path.join(headerPath, ootAnim.headerName + ".c"),
        )

        if not settings.isCustom:
            if (Path(bpy.context.scene.ootDecompPath) / "assets/objects/gameplay_keep/gameplay_keep.c").exists():
                # Pre "new" assets system
                addIncludeFiles("link_animetion", path, ootAnim.dataName())
                addIncludeFiles("gameplay_keep", headerPath, ootAnim.headerName)
            else:
                add_include_to_spec_segment(
                    Path(bpy.context.scene.ootDecompPath) / "spec/spec",
                    "link_animetion",
                    f"$(BUILD_DIR)/assets/misc/link_animetion/{ootAnim.dataName()}.o",
                )
                add_include_to_spec_segment(
                    Path(bpy.context.scene.ootDecompPath) / "spec/spec",
                    "gameplay_keep",
                    f"$(BUILD_DIR)/assets/objects/gameplay_keep/{ootAnim.headerName}.o",
                )
                addIncludeFilesExtension("gameplay_keep", headerPath, ootAnim.headerName, "h")

    else:
        ootAnim = ootExportNonLinkAnimation(armatureObj, convertTransformMatrix, name, filename)

        ootAnimC = ootAnim.toC()
        path = ootGetPath(exportPath, settings.isCustom, "assets/objects/", settings.folderName, True, False)
        writeCData(ootAnimC, os.path.join(path, filename + ".h"), os.path.join(path, filename + ".c"))

        if not settings.isCustom:
            addIncludeFiles(settings.folderName, path, filename)


def ootImportAnimationC(
    armatureObj: bpy.types.Object,
    settings: OOTAnimImportSettingsProperty,
    actorScale: float,
):
    importPath = bpy.path.abspath(settings.customPath)
    filepath = ootGetObjectPath(settings.isCustom, importPath, settings.folderName, True)
    if settings.isLink:
        numLimbs = 21
        if not settings.isCustom:
            basePath = bpy.path.abspath(bpy.context.scene.ootDecompPath)
            animFilepath = os.path.join(
                basePath,
                f"{bpy.context.scene.fast64.oot.get_extracted_path()}/assets/misc/link_animetion/link_animetion.c",
            )

            # starting with zeldaret/oot dbe1a80541173652c344f20226310a8bf90f3086 gameplay_keep is split and committed
            animHeaderFilepath = f"{basePath}/assets/objects/gameplay_keep/player_anim_headers.c"
            if not os.path.exists(animHeaderFilepath):
                animHeaderFilepath = os.path.join(
                    basePath,
                    f"{bpy.context.scene.fast64.oot.get_extracted_path()}/assets/objects/gameplay_keep/gameplay_keep.c",
                )
        else:
            animFilepath = filepath
            animHeaderFilepath = filepath
        ootImportLinkAnimationC(
            armatureObj,
            animHeaderFilepath,
            animFilepath,
            settings.animName,
            actorScale,
            numLimbs,
            settings.isCustom,
        )
    else:
        ootImportNonLinkAnimationC(armatureObj, filepath, settings.animName, actorScale, settings.isCustom)


class OOT_ExportAnim(Operator):
    bl_idname = "object.oot_export_anim"
    bl_label = "Export Animation"
    bl_options = {"REGISTER", "UNDO", "PRESET"}

    # Called on demand (i.e. button press, menu item)
    # Can also be called from operator search menu (Spacebar)
    def execute(self, context):
        with ExportUtils() as export_utils:
            try:
                if len(context.selected_objects) == 0 or not isinstance(context.selected_objects[0].data, Armature):
                    raise PluginError("Armature not selected.")
                if len(context.selected_objects) > 1:
                    raise PluginError("Multiple objects selected, make sure to select only one.")
                armatureObj = context.selected_objects[0]
                if context.mode != "OBJECT":
                    object.mode_set(mode="OBJECT")
            except Exception as e:
                raisePluginError(self, e)
                return {"CANCELLED"}

            try:
                settings = context.scene.fast64.oot.animExportSettings
                exportAnimationC(armatureObj, settings)
                self.report({"INFO"}, "Success!")

            except Exception as e:
                raisePluginError(self, e)
                return {"CANCELLED"}  # must return a set

        return {"FINISHED"}  # must return a set


class OOT_ImportAnim(Operator):
    bl_idname = "object.oot_import_anim"
    bl_label = "Import Animation"
    bl_options = {"REGISTER", "UNDO", "PRESET"}

    # Called on demand (i.e. button press, menu item)
    # Can also be called from operator search menu (Spacebar)
    def execute(self, context):
        try:
            if len(context.selected_objects) == 0 or not isinstance(context.selected_objects[0].data, Armature):
                raise PluginError("Armature not selected.")
            if len(context.selected_objects) > 1:
                raise PluginError("Multiple objects selected, make sure to select only one.")
            armatureObj = context.selected_objects[0]
            if context.mode != "OBJECT":
                object.mode_set(mode="OBJECT")

            # We need to apply scale otherwise translation imports won't be correct.
            object.select_all(action="DESELECT")
            armatureObj.select_set(True)
            context.view_layer.objects.active = armatureObj
            object.transform_apply(location=False, rotation=False, scale=True, properties=False)

        except Exception as e:
            raisePluginError(self, e)
            return {"CANCELLED"}

        try:
            actorScale = getOOTScale(armatureObj.ootActorScale)
            settings = context.scene.fast64.oot.animImportSettings
            ootImportAnimationC(armatureObj, settings, actorScale)
            self.report({"INFO"}, "Success!")

        except Exception as e:
            raisePluginError(self, e)
            return {"CANCELLED"}  # must return a set

        return {"FINISHED"}  # must return a set


oot_anim_classes = (
    OOT_ExportAnim,
    OOT_ImportAnim,
)


def anim_ops_register():
    Scene.ootAnimIsCustomExport = BoolProperty(name="Use Custom Path")
    Scene.ootAnimExportCustomPath = StringProperty(name="Folder", subtype="FILE_PATH")
    Scene.ootAnimExportFolderName = StringProperty(name="Animation Folder", default="object_geldb")

    Scene.ootAnimIsCustomImport = BoolProperty(name="Use Custom Path")
    Scene.ootAnimImportCustomPath = StringProperty(name="Folder", subtype="FILE_PATH")
    Scene.ootAnimImportFolderName = StringProperty(name="Animation Folder", default="object_geldb")

    Scene.ootAnimSkeletonName = StringProperty(name="Skeleton Name", default="gGerudoRedSkel")
    Scene.ootAnimName = StringProperty(name="Anim Name", default="gGerudoRedSpinAttackAnim")
    for cls in oot_anim_classes:
        register_class(cls)


def anim_ops_unregister():
    del Scene.ootAnimIsCustomExport
    del Scene.ootAnimExportCustomPath
    del Scene.ootAnimExportFolderName

    del Scene.ootAnimIsCustomImport
    del Scene.ootAnimImportCustomPath
    del Scene.ootAnimImportFolderName

    del Scene.ootAnimSkeletonName
    del Scene.ootAnimName
    for cls in reversed(oot_anim_classes):
        unregister_class(cls)
