#vngame;all;Utilities/Status Copy v1.0
# this util help you copy status from one to another
# by Countd360

from UnityEngine import Quaternion, Vector3, Vector2, Color, Rect, GameObject, Transform
from UnityEngine import GUI, GUILayout, GUIStyle, GUIContent, GUIUtility, TextAnchor
from UnityEngine import Time, Screen, Input, KeyCode, Event, EventType
from System import String, Array, Action, GC
from vngameengine import HSNeoOCI, HSNeoOCIItem, HSNeoOCIChar, HSNeoOCIFolder, GData
from vngameengine import importOrReload, parseKeyCode
import copy
import traceback
import unity_util

def start(game):

    if not hasattr(game.gdata, "StatusCopyData"):
        print("\n\nStart a new status copy application...")
        initialize(game)

    wnd = game.get_extra_window(game.gdata.StatusCopyData.wndId)
    if wnd != None:
        # already existed
        return

    def skin_setup(g):
        wwidth = 180
        wheight = 172
        g.windowName = "Status Copy v1.0"
        g.windowRect = Rect(Screen.width  - wwidth - 20, Screen.height - wheight - 20, wwidth, wheight)
        g.windowStyle = g.windowStyleDefault
    
    # setup skin
    from skin_customwindow import SkinCustomWindow
    skin = SkinCustomWindow()
    skin.funcSetup = skin_setup
    skin.funcWindowGUI = statuscopy_GUI_subwindow
    skin.metadata = game.gdata.StatusCopyData
    skin.guidata = GData()
    skin.guidata.guiMode = "main"

    # open sub-window
    game.gdata.StatusCopyData.wndId = game.new_extra_window_skin(skin)


def close(game):
    if not hasattr(game.gdata, "StatusCopyData"):
        return

    wnd = game.get_extra_window(game.gdata.StatusCopyData.wndId)
    if wnd != None:
        # already existed
        game.close_extra_window(game.gdata.StatusCopyData.wndId)
        game.gdata.StatusCopyData.wndId = None



def initialize(game):
    game.gdata.StatusCopyData = StatusCopy(game)


def statuscopy_GUI_subwindow(baseController, windowid):
    try:
        guidata = baseController.skin.guidata
        metadata = baseController.skin.metadata
        game = metadata.game
        if guidata.guiMode == "main":
            statuscopy_main_GUI(guidata, metadata)
        elif guidata.guiMode == "more":
            statuscopy_more_GUI(guidata, metadata)
        else:
            raise Exception("Unknow GUI mode: " + guidata.guiMode)
    except:
        traceback.print_exc()
        close(game)
    
    # close button
    wndRect = baseController.windowRect
    cbRect = Rect(wndRect.width - 16, 3, 13, 13)
    oldColor = GUI.color
    GUI.color = Color.red
    if GUI.Button(cbRect, ""):
        close(game)
    GUI.color = oldColor
    
def statuscopy_main_GUI(guiData, statusCopy):
    oldColor = GUI.color

    # source
    GUILayout.BeginHorizontal()
    GUILayout.Label("Src", GUILayout.Width(20))
    if GUILayout.Button(statusCopy.srcName):
        statusCopy.selectCopySource()
    if not statusCopy.srcObj:
        GUI.color = Color.grey
    if GUILayout.Button(">", GUILayout.Width(20)) and statusCopy.srcObj:
        statusCopy.setWorkspaceSelection(statusCopy.srcObj)
    GUI.color = oldColor
    GUILayout.EndHorizontal()

    # target
    GUILayout.BeginHorizontal()
    GUILayout.Label("Tgt", GUILayout.Width(20))
    if GUILayout.Button(statusCopy.tgtName):
        statusCopy.selectCopyTarget()
    if len(statusCopy.tgtObj) == 0:
        GUI.color = Color.grey
    if GUILayout.Button(">", GUILayout.Width(20)) and len(statusCopy.tgtObj):
        statusCopy.setWorkspaceSelection(statusCopy.tgtObj)
    GUI.color = oldColor
    GUILayout.EndHorizontal()

    # position
    GUILayout.BeginHorizontal()
    GUILayout.Label("Position", GUILayout.Width(50))
    if statusCopy.copyLocalPos:
        GUI.color = Color.green
    clp = GUILayout.Toggle(statusCopy.copyLocalPos, "Local")
    GUI.color = oldColor
    if statusCopy.copyLocalPos != clp:
        statusCopy.copyLocalPos = clp
        if clp:
            statusCopy.copyGlobalPos = False
    if statusCopy.copyGlobalPos:
        GUI.color = Color.green
    cgp = GUILayout.Toggle(statusCopy.copyGlobalPos, "Global")
    GUI.color = oldColor
    if statusCopy.copyGlobalPos != cgp:
        statusCopy.copyGlobalPos = cgp
        if cgp:
            statusCopy.copyLocalPos = False
    GUILayout.EndHorizontal()

    # rotation
    GUILayout.BeginHorizontal()
    GUILayout.Label("Rotation", GUILayout.Width(50))
    if statusCopy.copyLocalRot:
        GUI.color = Color.green
    clr = GUILayout.Toggle(statusCopy.copyLocalRot, "Local")
    GUI.color = oldColor
    if statusCopy.copyLocalRot != clr:
        statusCopy.copyLocalRot = clr
        if clr:
            statusCopy.copyGlobalRot = False
    if statusCopy.copyGlobalRot:
        GUI.color = Color.green
    cgr = GUILayout.Toggle(statusCopy.copyGlobalRot, "Global")
    GUI.color = oldColor
    if statusCopy.copyGlobalRot != cgr:
        statusCopy.copyGlobalRot = cgr
        if cgr:
            statusCopy.copyLocalRot = False
    GUILayout.EndHorizontal()

    # addition
    GUILayout.BeginHorizontal()
    if statusCopy.hasScale:
        if statusCopy.copyScale:
            GUI.color = Color.green
        statusCopy.copyScale = GUILayout.Toggle(statusCopy.copyScale, "Scale", GUILayout.Width(50))
        GUI.color = oldColor
    if statusCopy.hasColor:
        if statusCopy.copyColor:
            GUI.color = Color.green
        statusCopy.copyColor = GUILayout.Toggle(statusCopy.copyColor, "Color", GUILayout.Width(50))
        GUI.color = oldColor
    if statusCopy.hasPose:
        if statusCopy.copyPose:
            GUI.color = Color.green
        statusCopy.copyPose = GUILayout.Toggle(statusCopy.copyPose, "Pose", GUILayout.Width(50))
        GUI.color = oldColor
    if len(statusCopy.targetProperty):
        GUI.color = Color.yellow
    elif len(statusCopy.validProperty) == 0:
        GUI.color = Color.grey
    if GUILayout.Button("More") and len(statusCopy.validProperty):
        guiData.stList, guiData.stSelect, guiData.stDisplay = statusCopy.prepareMoreProperties()
        guiData.moreScrollPos = Vector2.zero
        guiData.guiMode = "more"
    GUI.color = oldColor
    GUILayout.EndHorizontal()

    # controls
    GUILayout.BeginHorizontal()
    if not statusCopy.autoCopy:
        GUI.color = Color.green
    if GUILayout.Button("Copy"):
        statusCopy.startCopyJob()
    GUI.color = oldColor
    if statusCopy.autoCopy:
        GUI.color = Color.green
    if GUILayout.Button("Auto"):
        statusCopy.autoCopy = not statusCopy.autoCopy
        if statusCopy.autoCopy:
            statusCopy.startCopyJob()
    GUI.color = oldColor
    if not statusCopy.canSwitch:
        GUI.color = Color.grey
    if GUILayout.Button("SW", GUILayout.Width(40)) and statusCopy.canSwitch:
        statusCopy.switchCopyObject()
    GUI.color = oldColor
    GUILayout.EndHorizontal()

def statuscopy_more_GUI(guiData, statusCopy):
    # status list
    guiData.moreScrollPos = GUILayout.BeginScrollView(guiData.moreScrollPos)
    for st in guiData.stList:
        guiData.stSelect[st] = GUILayout.Toggle(guiData.stSelect[st], guiData.stDisplay[st])
    GUILayout.EndScrollView()

    # controls
    GUILayout.BeginHorizontal()
    if GUILayout.Button("OK"):
        statusCopy.targetProperty = set([st for st in guiData.stList if guiData.stSelect[st]])
        guiData.guiMode = "main"
    if GUILayout.Button("All"):
        for st in guiData.stList:
            guiData.stSelect[st] = True
    if GUILayout.Button("None"):
        for st in guiData.stList:
            guiData.stSelect[st] = False
    if GUILayout.Button("Cancel"):
        guiData.guiMode = "main"
    GUILayout.EndHorizontal()



class StatusCopy():
    BASIC_PROPERTIES_SET = {"move_to", "rotate_to", "scale_to"}
    COLOR_PROPERTIES_SET = {"color", "ptn_set", "ptn_dtl", "metallic", "emission", "alpha", "line", "shadow_c", "light_cancel"}
    POSE_PROPERTIES_SET = {"kinematic", "fk_active", "fk_set", "fk_set2", "ik_active", "ik_set", "ik_set2", "anim", "anim_spd", "anim_ptn", "anim_lp", "anim_optprm", "anim_ntime", "look_at", "look_at_ptn", "look_at_pos", "face_to", "face_to_full", "face_to_full2", "eyebrow", "eyes", "eyes_open", "eyes_blink", "mouth", "mouth_open", "lip_sync", "hands"}

    def __init__(self, game):
        self.game = game
        self.wndId = None
        self.srcObj = None
        self.srcName = "-- select --"
        self.tgtObj = []
        self.tgtName = "-- select --"
        self.copyLocalPos = True
        self.copyGlobalPos = False
        self.copyLocalRot = True
        self.copyGlobalRot = False
        self.hasScale = False
        self.hasColor = False
        self.hasPose = False
        self.copyScale = False
        self.copyColor = False
        self.copyPose = False
        self.canSwitch = False
        self.validProperty = set()
        self.targetProperty = set()
        self.autoCopy = False
    
    def selectCopySource(self):
        self.srcObj = self.getCurrentSelectObj()
        self.verifySrcObject()

    def selectCopyTarget(self):
        self.tgtObj = self.getCurrentSelectObjs()
        self.verifyTgtObjects()

    def switchCopyObject(self):
        if self.canSwitch:
            srcback = self.srcObj
            self.srcObj = None if len(self.tgtObj) == 0 else self.tgtObj[0]
            self.tgtObj = [srcback]
            self.verifySrcObject()

    def verifySrcObject(self):
        if self.srcObj:
            self.srcName = "<color=#00ff00>" + self.srcObj.text_name[:6] + "</color>"
            # refresh properties
            self.refreshTargetProperties()
        else:
            self.srcName = "-- select --"
        self.verifyTgtObjects()

    def verifyTgtObjects(self):
        # delete None, srcObj or repeated obj from tgtObj
        verifiedTgtObj = []
        verifiedTgtObjCtrl = set()
        for obj in self.tgtObj:
            if obj == None or obj.objctrl == None:
                continue
            if self.srcObj and self.srcObj.objctrl == obj.objctrl:
                continue
            if obj.objctrl in verifiedTgtObjCtrl:
                continue
            verifiedTgtObj.append(obj)
            verifiedTgtObjCtrl.add(obj.objctrl)
        self.tgtObj = verifiedTgtObj
        # build name
        if len(self.tgtObj) == 1:
            self.tgtName = "<color=#ffff00>" + self.tgtObj[0].text_name[:6] + "</color>"
        elif len(self.tgtObj) > 1:
            self.tgtName = "<color=#ffa500>" + self.tgtObj[0].text_name[:5] + " +" + str(len(self.tgtObj)-1) + "</color>"
        else:
            self.tgtName = "-- select --"
        # switch check
        self.canSwitch = len(self.tgtObj) == 1 or (len(self.tgtObj) == 0 and self.srcObj != None)

    def refreshTargetProperties(self):
        from vnactor import get_ini_options, is_ini_value_true, set_ini_value
        # scale
        self.hasScale = StatusCopy.objHasScale(self.srcObj)
        # color
        self.hasColor = StatusCopy.objHasColor(self.srcObj)
        # pose
        self.hasPose = StatusCopy.objHasPose(self.srcObj)

        # valid properties
        backupOpts = {}
        for opt in get_ini_options():
            backupOpts[opt] = is_ini_value_true(opt)
            set_ini_value(opt, True)
        full_status = self.srcObj.export_full_status()
        for opt in get_ini_options():
            set_ini_value(opt, backupOpts[opt])
        self.validProperty = set(full_status.keys())
        self.validProperty.difference_update(StatusCopy.BASIC_PROPERTIES_SET)

        # update target properties
        self.targetProperty.intersection_update(self.validProperty)

    def prepareMoreProperties(self):
        from vnanime import translateAndSortStatusKeys

        # exclude
        validProperties = copy.deepcopy(self.validProperty)
        targetProperties = copy.deepcopy(self.targetProperty)
        if self.hasColor and self.copyColor:
            validProperties.difference_update(StatusCopy.COLOR_PROPERTIES_SET)
            targetProperties.difference_update(StatusCopy.COLOR_PROPERTIES_SET)
        if self.hasPose and self.copyPose:
            validProperties.difference_update(StatusCopy.POSE_PROPERTIES_SET)
            targetProperties.difference_update(StatusCopy.POSE_PROPERTIES_SET)

        statusKeyList, statusKeyTranslateDict = translateAndSortStatusKeys(validProperties)
        statusSelectDict = {}
        for st in statusKeyList:
            statusSelectDict[st] = st in targetProperties
        return statusKeyList, statusSelectDict, statusKeyTranslateDict

    def getCurrentSelectObj(self):
        if len(self.game.studio.treeNodeCtrl.selectNodes) == 0:
            self.showMessage("Please select object from WORKSPACE")
            return None
        if len(self.game.studio.treeNodeCtrl.selectNodes) > 1:
            self.showMessage("Please select only ONE object in workspace")
            return None
        tgtObj = HSNeoOCI.create_from_treenode(self.game.studio.treeNodeCtrl.selectNodes[0])
        if isinstance(tgtObj, HSNeoOCIChar):
            return tgtObj.as_actor
        elif hasattr(tgtObj, "as_prop"):
            return tgtObj.as_prop
        else:
            self.showMessage("Unsupported object type: " + str(type(tgtObj.objctrl)))
            return None

    def getCurrentSelectObjs(self):
        if len(self.game.studio.treeNodeCtrl.selectNodes) == 0:
            self.showMessage("Please select object from WORKSPACE")
            return []
        objs = []
        for sNode in self.game.studio.treeNodeCtrl.selectNodes:
            tgtObj = HSNeoOCI.create_from_treenode(sNode)
            if isinstance(tgtObj, HSNeoOCIChar):
                objs.append(tgtObj.as_actor)
            elif hasattr(tgtObj, "as_prop"):
                objs.append(tgtObj.as_prop)
            else:
                self.showMessage("Unsupported object type: " + str(type(tgtObj.objctrl)))
        return objs

    def setWorkspaceSelection(self, objs):
        from extplugins import CallPrivate
        if not isinstance(objs, list):
            objs = [objs]
        self.game.studio.treeNodeCtrl.SelectSingle(None, True)
        for i in range(len(objs)):
            tnd = objs[i].treeNodeObject
            if i == 0:
                self.game.studio.treeNodeCtrl.SelectSingle(tnd, False)
            else:
                CallPrivate(self.game.studio.treeNodeCtrl, "AddSelectNode", (tnd, False))

    def showMessage(self, message):
        self.game.logger_log_message(message, "StatusCopy: ")

    def doCopy(self):
        from vnactor import Actor, Prop
        if not self.srcObj:
            return "Source object not selected"
        if not len(self.tgtObj):
            return "Target object not selected"

        def getObjTransform(obj):
            if isinstance(obj, Prop):
                return obj.objctrl.objectItem.transform
            elif isinstance(obj, Actor):
                return obj.objctrl.charInfo.transform
            else:
                raise Exception("Unknown type of object: " + str(type(obj)))

        def getObjChangeAmout(obj):
            if isinstance(obj, Prop):
                return obj.objctrl.objectInfo.changeAmount
            elif isinstance(obj, Actor):
                return obj.objctrl.oiCharInfo.changeAmount
            else:
                raise Exception("Unknown type of object: " + str(type(obj)))

        try:
            # copy position
            if self.copyLocalPos:
                srcPos = self.srcObj.pos
                for to in self.tgtObj:
                    getObjChangeAmout(to).pos = srcPos
            if self.copyGlobalPos:
                srcPos = getObjTransform(self.srcObj).position
                for to in self.tgtObj:
                    ttp = getObjTransform(to).parent
                    getObjChangeAmout(to).pos = Quaternion.Inverse(ttp.rotation) * (srcPos - ttp.position)

            # copy rotation
            if self.copyLocalRot:
                srcRot = self.srcObj.rot
                for to in self.tgtObj:
                    getObjChangeAmout(to).rot = srcRot
            if self.copyGlobalRot:
                srcRot = getObjTransform(self.srcObj).rotation
                for to in self.tgtObj:
                    ttp = getObjTransform(to).parent
                    getObjChangeAmout(to).rot = (Quaternion.Inverse(ttp.rotation) * srcRot).eulerAngles

            # copy scale
            if self.hasScale and self.copyScale:
                srcScl = self.srcObj.scale
                for to in self.tgtObj:
                    if StatusCopy.objHasScale(to):
                        getObjChangeAmout(to).scale = srcScl

            # check if need advanced properties copy
            if (not self.hasColor or not self.copyColor) and (not self.hasPose or not self.copyPose) and len(self.targetProperty) == 0:
                # no need, copy done
                return None

            # source data
            from vnactor import get_ini_options, is_ini_value_true, set_ini_value
            backupOpts = {}
            for opt in get_ini_options():
                backupOpts[opt] = is_ini_value_true(opt)
                set_ini_value(opt, True)
            srcFullStatus = self.srcObj.export_full_status()
            for opt in get_ini_options():
                set_ini_value(opt, backupOpts[opt])

            # loop all target
            for to in self.tgtObj:
                # check type ?

                tgtStatus = {}
                # copy color
                if self.hasColor and self.copyColor:
                    for st in StatusCopy.COLOR_PROPERTIES_SET:
                        if st in srcFullStatus:
                            tgtStatus[st] = srcFullStatus[st]
                
                # copy pose
                if self.hasPose and self.copyPose:
                    for st in StatusCopy.POSE_PROPERTIES_SET:
                        if st in srcFullStatus:
                            tgtStatus[st] = srcFullStatus[st]

                # copy more properties
                for st in self.targetProperty:
                    if st in srcFullStatus:
                        tgtStatus[st] = srcFullStatus[st]

                # try import
                try:
                    to.import_status_diff_optimized(tgtStatus)
                except Exception as e:
                    self.showMessage("Fail to copy status to %s: %s"%(to.text_name, str(e)))

            # properties copy done
            return None

        except Exception as e:
            traceback.print_exc()
            return "Copy status error: " + str(e)
        
    def startCopyJob(self):
        self.game.append_update_job(StatusCopy.doCopyJob, self)

    @staticmethod
    def doCopyJob(game, statusCopy):
        # instance check
        if statusCopy == None or not hasattr(game.gdata, "StatusCopyData") or game.gdata.StatusCopyData != statusCopy:
            return True
        # do copy
        err = statusCopy.doCopy()
        if err:
            if not statusCopy.autoCopy:
                statusCopy.showMessage(err)
            statusCopy.autoCopy = False
        return not statusCopy.autoCopy

    @staticmethod
    def objHasScale(obj):
        from vnactor import Actor, Prop, PropCharaStudio
        if isinstance(obj, Actor):
            return True
        if isinstance(obj, Prop) and obj.isItem:
            return True
        if isinstance(obj, PropCharaStudio) and obj.isText:
            return True
        return False

    @staticmethod
    def objHasColor(obj):
        from vnactor import Prop, PropCharaStudio
        if isinstance(obj, Prop) and (obj.isColorable):
            return True
        if isinstance(obj, PropCharaStudio) and obj.isText:
            return True
        return False

    @staticmethod
    def objHasPose(obj):
        from vnactor import Actor
        if isinstance(obj, Actor):
            return True
        return False
