# -*- coding: utf-8 -*-
# Copyright 2014 MMD Tools authors
# This file was originally part of the MMD Tools add-on for Blender
# You can find MMD Tools here: https://github.com/MMD-Blender/blender_mmd_tools
# Neoneko has modified this file to work with Avatar Toolkit and may of made changes or improvements.
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.

from typing import TYPE_CHECKING, cast

import bpy

from ..core.model import FnModel, Model
from ..core.translations import MMD_DATA_TYPE_TO_HANDLERS, FnTranslations
from ..translations import DictionaryEnum

if TYPE_CHECKING:
    from ..properties.translations import MMDTranslation, MMDTranslationElement, MMDTranslationElementIndex


class TranslateMMDModel(bpy.types.Operator):
    bl_idname = "mmd_tools.translate_mmd_model"
    bl_label = "Translate a MMD Model"
    bl_description = "Translate Japanese names of a MMD model"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}

    dictionary: bpy.props.EnumProperty(
        name="Dictionary",
        items=DictionaryEnum.get_dictionary_items,
        description="Translate names from Japanese to English using selected dictionary",
    )
    types: bpy.props.EnumProperty(
        name="Types",
        description="Select which parts will be translated",
        options={"ENUM_FLAG"},
        items=[
            ("BONE", "Bones", "Bones", 1),
            ("MORPH", "Morphs", "Morphs", 2),
            ("MATERIAL", "Materials", "Materials", 4),
            ("DISPLAY", "Display", "Display frames", 8),
            ("PHYSICS", "Physics", "Rigidbodies and joints", 16),
            ("INFO", "Information", "Model name and comments", 32),
        ],
        default={
            "BONE",
            "MORPH",
            "MATERIAL",
            "DISPLAY",
            "PHYSICS",
        },
    )
    modes: bpy.props.EnumProperty(
        name="Modes",
        description="Select translation mode",
        options={"ENUM_FLAG"},
        items=[
            ("MMD", "MMD Names", "Fill MMD English names", 1),
            ("BLENDER", "Blender Names", "Translate blender names (experimental)", 2),
        ],
        default={"MMD"},
    )
    use_morph_prefix: bpy.props.BoolProperty(
        name="Use Morph Prefix",
        description="Add/remove prefix to English name of morph",
        default=False,
    )
    overwrite: bpy.props.BoolProperty(
        name="Overwrite",
        description="Overwrite a translated English name",
        default=False,
    )
    allow_fails: bpy.props.BoolProperty(
        name="Allow Fails",
        description="Allow incompletely translated names",
        default=False,
    )

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return obj in context.selected_objects and FnModel.find_root_object(obj)

    def invoke(self, context, event):
        vm = context.window_manager
        return vm.invoke_props_dialog(self)

    def execute(self, context):
        try:
            self.__translator = DictionaryEnum.get_translator(self.dictionary)
        except Exception as e:
            self.report({"ERROR"}, "Failed to load dictionary: %s" % e)
            return {"CANCELLED"}

        obj = context.active_object
        root = FnModel.find_root_object(obj)
        rig = Model(root)

        if "MMD" in self.modes:
            for i in self.types:
                getattr(self, "translate_%s" % i.lower())(rig)

        if "BLENDER" in self.modes:
            self.translate_blender_names(rig)

        translator = self.__translator
        txt = translator.save_fails()
        if translator.fails:
            self.report({"WARNING"}, "Failed to translate %d names, see '%s' in text editor" % (len(translator.fails), txt.name))
        return {"FINISHED"}

    def translate(self, name_j, name_e):
        if not self.overwrite and name_e and self.__translator.is_translated(name_e):
            return name_e
        if self.allow_fails:
            name_e = None
        return self.__translator.translate(name_j, name_e)

    def translate_blender_names(self, rig: Model):
        if "BONE" in self.types:
            for b in rig.armature().pose.bones:
                rig.renameBone(b.name, self.translate(b.name, b.name))

        if "MORPH" in self.types:
            for i in (x for x in rig.meshes() if x.data.shape_keys):
                for kb in i.data.shape_keys.key_blocks:
                    kb.name = self.translate(kb.name, kb.name)

        if "MATERIAL" in self.types:
            for m in (x for x in rig.materials() if x):
                m.name = self.translate(m.name, m.name)

        if "DISPLAY" in self.types:
            g: bpy.types.BoneCollection
            for g in cast(bpy.types.Armature, rig.armature().data).collections:
                g.name = self.translate(g.name, g.name)

        if "PHYSICS" in self.types:
            for i in rig.rigidBodies():
                i.name = self.translate(i.name, i.name)

            for i in rig.joints():
                i.name = self.translate(i.name, i.name)

        if "INFO" in self.types:
            objects = [rig.rootObject(), rig.armature()]
            objects.extend(rig.meshes())
            for i in objects:
                i.name = self.translate(i.name, i.name)

    def translate_info(self, rig):
        mmd_root = rig.rootObject().mmd_root
        mmd_root.name_e = self.translate(mmd_root.name, mmd_root.name_e)

        comment_text = bpy.data.texts.get(mmd_root.comment_text, None)
        comment_e_text = bpy.data.texts.get(mmd_root.comment_e_text, None)
        if comment_text and comment_e_text:
            comment_e = self.translate(comment_text.as_string(), comment_e_text.as_string())
            comment_e_text.from_string(comment_e)

    def translate_bone(self, rig):
        bones = rig.armature().pose.bones
        for b in bones:
            if b.is_mmd_shadow_bone:
                continue
            b.mmd_bone.name_e = self.translate(b.mmd_bone.name_j, b.mmd_bone.name_e)

    def translate_morph(self, rig):
        mmd_root = rig.rootObject().mmd_root
        attr_list = ("group", "vertex", "bone", "uv", "material")
        prefix_list = ("G_", "", "B_", "UV_", "M_")
        for attr, prefix in zip(attr_list, prefix_list):
            for m in getattr(mmd_root, attr + "_morphs", []):
                m.name_e = self.translate(m.name, m.name_e)
                if not prefix:
                    continue
                if self.use_morph_prefix:
                    if not m.name_e.startswith(prefix):
                        m.name_e = prefix + m.name_e
                elif m.name_e.startswith(prefix):
                    m.name_e = m.name_e[len(prefix) :]

    def translate_material(self, rig):
        for m in rig.materials():
            if m is None:
                continue
            m.mmd_material.name_e = self.translate(m.mmd_material.name_j, m.mmd_material.name_e)

    def translate_display(self, rig):
        mmd_root = rig.rootObject().mmd_root
        for f in mmd_root.display_item_frames:
            f.name_e = self.translate(f.name, f.name_e)

    def translate_physics(self, rig):
        for i in rig.rigidBodies():
            i.mmd_rigid.name_e = self.translate(i.mmd_rigid.name_j, i.mmd_rigid.name_e)

        for i in rig.joints():
            i.mmd_joint.name_e = self.translate(i.mmd_joint.name_j, i.mmd_joint.name_e)


DEFAULT_SHOW_ROW_COUNT = 20


class MMD_TOOLS_UL_MMDTranslationElementIndex(bpy.types.UIList):
    def draw_item(self, context, layout: bpy.types.UILayout, data, mmd_translation_element_index: "MMDTranslationElementIndex", icon, active_data, active_propname, index: int):
        mmd_translation_element: "MMDTranslationElement" = data.translation_elements[mmd_translation_element_index.value]
        MMD_DATA_TYPE_TO_HANDLERS[mmd_translation_element.type].draw_item(layout, mmd_translation_element, index)


class RestoreMMDDataReferenceOperator(bpy.types.Operator):
    bl_idname = "mmd_tools.restore_mmd_translation_element_name"
    bl_label = "Restore this Name"
    bl_options = {"INTERNAL"}

    index: bpy.props.IntProperty()
    prop_name: bpy.props.StringProperty()
    restore_value: bpy.props.StringProperty()

    def execute(self, context: bpy.types.Context):
        root_object = FnModel.find_root_object(context.object)
        mmd_translation_element_index = root_object.mmd_root.translation.filtered_translation_element_indices[self.index].value
        mmd_translation_element = root_object.mmd_root.translation.translation_elements[mmd_translation_element_index]
        setattr(mmd_translation_element, self.prop_name, self.restore_value)

        return {"FINISHED"}


class GlobalTranslationPopup(bpy.types.Operator):
    bl_idname = "mmd_tools.global_translation_popup"
    bl_label = "Global Translation Popup"
    bl_options = {"INTERNAL", "UNDO"}

    @classmethod
    def poll(cls, context):
        return FnModel.find_root_object(context.object) is not None

    def draw(self, _context):
        layout = self.layout
        mmd_translation = self._mmd_translation

        col = layout.column(align=True)
        col.label(text="Filter", icon="FILTER")
        row = col.row()
        row.prop(mmd_translation, "filter_types")

        group = row.row(align=True, heading="is Blank:")
        group.alignment = "RIGHT"
        group.prop(mmd_translation, "filter_japanese_blank", toggle=True, text="Japanese")
        group.prop(mmd_translation, "filter_english_blank", toggle=True, text="English")

        group = row.row(align=True)
        group.prop(mmd_translation, "filter_restorable", toggle=True, icon="FILE_REFRESH", icon_only=True)
        group.prop(mmd_translation, "filter_selected", toggle=True, icon="RESTRICT_SELECT_OFF", icon_only=True)
        group.prop(mmd_translation, "filter_visible", toggle=True, icon="HIDE_OFF", icon_only=True)

        col = layout.column(align=True)
        box = col.box().column(align=True)
        row = box.row(align=True)
        row.label(text="Select the target column for Batch Operations:", icon="TRACKER")
        row = box.row(align=True)
        row.label(text="", icon="BLANK1")
        row.prop(mmd_translation, "batch_operation_target", expand=True)
        row.label(text="", icon="RESTRICT_SELECT_OFF")
        row.label(text="", icon="HIDE_OFF")

        if len(mmd_translation.filtered_translation_element_indices) > DEFAULT_SHOW_ROW_COUNT:
            row.label(text="", icon="BLANK1")

        col.template_list(
            "MMD_TOOLS_UL_MMDTranslationElementIndex",
            "",
            mmd_translation,
            "filtered_translation_element_indices",
            mmd_translation,
            "filtered_translation_element_indices_active_index",
            rows=DEFAULT_SHOW_ROW_COUNT,
        )

        box = layout.box().column(align=True)
        box.label(text="Batch Operation:", icon="MODIFIER")
        box.prop(mmd_translation, "batch_operation_script", text="", icon="SCRIPT")

        box.separator()
        row = box.row()
        row.prop(mmd_translation, "batch_operation_script_preset", text="Preset", icon="CON_TRANSFORM_CACHE")
        row.operator(ExecuteTranslationBatchOperator.bl_idname, text="Execute")

        box.separator()
        translation_box = box.box().column(align=True)
        translation_box.label(text="Dictionaries:", icon="HELP")
        row = translation_box.row()
        row.prop(mmd_translation, "dictionary", text="to_english")
        # row.operator(ExecuteTranslationScriptOperator.bl_idname, text='Write to .csv')

        translation_box.separator()
        row = translation_box.row()
        row.prop(mmd_translation, "dictionary", text="replace")

    def invoke(self, context: bpy.types.Context, _event):
        root_object = FnModel.find_root_object(context.object)
        if root_object is None:
            return {"CANCELLED"}

        mmd_translation: "MMDTranslation" = root_object.mmd_root.translation
        self._mmd_translation = mmd_translation
        FnTranslations.clear_data(mmd_translation)
        FnTranslations.collect_data(mmd_translation)
        FnTranslations.update_query(mmd_translation)

        return context.window_manager.invoke_props_dialog(self, width=800)

    def execute(self, context):
        root_object = FnModel.find_root_object(context.object)
        if root_object is None:
            return {"CANCELLED"}

        FnTranslations.apply_translations(root_object)
        FnTranslations.clear_data(root_object.mmd_root.translation)

        return {"FINISHED"}


class ExecuteTranslationBatchOperator(bpy.types.Operator):
    bl_idname = "mmd_tools.execute_translation_batch"
    bl_label = "Execute Translation Batch"
    bl_options = {"INTERNAL"}

    def execute(self, context: bpy.types.Context):
        root = FnModel.find_root_object(context.object)
        if root is None:
            return {"CANCELLED"}

        fails, text = FnTranslations.execute_translation_batch(root)
        if fails:
            self.report({"WARNING"}, "Failed to translate %d names, see '%s' in text editor" % (len(fails), text.name))

        return {"FINISHED"}
