# External Plugin Functions 
# Module for calling functions from native plugins in "/Plugins" folder
# v4.0
#
# Changelog:
# 1.1
# - static method "exists" in ExtPlugin - crossplatform
# 2.0 (VNGE 11.0)
# - NEO: HSStudioNEOAddon, HSStudioNEOExtSave
# - PH: PHSAddon
# - KK: KKPE (beta support
# 2.1
# - NEO: HSStudioNEOExtSave - functions ExtDataGet, ExtDataSet (get/set full plugin data in string)
# - All: GetPrivate function - grant access to private fields
# 2.2
# - NEO: HSStudioNEOAddon - Add some function to support play MMD
# - KK: KKVMDPlayPlugin - a plugin to support play MMD
# 2.3 (VNGE 11.7)
# - NEO: HSPENeo plugin (works with HSPE 2.8.0+)
# 2.4 (VNGE 11.8)
# - CharaStudio: KKPE plugin support
# Warning: this work only with internal beta KKPE version! Don't use it for now!
# 2.5 (VNGE 12.0)
# - CharaStudio: Screencap plugin support
# 2.6 (VNGE 12.5)
# - All: has_plugin support function
# 2.7 (VNGE 13.9)
# - NeoV2 (AI): AIPE plugin support
# 2.8 (VNGE 14.1)
# - All: calculating plugin folder (easy switch between Bep4 and Bep5)
# 2.9 (VNGE 15.0)
# - Support for NodeConstraints plugin
# 3.0 (VNGE 16.0)
# - Support AIVMDPlugin
# - Support DHH_AI
# 3.1
# - changed logic for Exists check. Allow load plugins from subfolders in Bep5.
# 3.2 (VNGE 16.8)
# - Update AIVMDPlugin to support AIVMDPlayer too
# - Support AI_FKIK
# - Add: CallPrivateStatic function
# 3.3 (VNGE 16.9)
# - ExtensibleSaveFormat and AI_ExtensibleSaveFormat mod (for AI and KK). Support for OnSceneLoad, Import, Save events
# - HSExtSave mod (for HS). Support for OnSceneLoad, Import, Save events
# 3.4 (VNGE 17.5)
# - AI_ExtensibleSaveFormat also load HS2_ExtensibleSaveFormat
# - AI_ExtensibleSaveFormat's Helper function for save/load extend data 
# 3.5 (VNGE 17.7)
# - support HS2PE
# 3.6
# - support HS2VmdPlayPlugin
# - some new function for KKVmdPlayPlugin
# - ExtensibleSaveFormat's Helper function for save/load extend data
# 3.7 (VNGE 18.0)
# - support ILLAPI (KKAPI, HS2API etc.)
# 3.8 (VNGE 18.2) (countd360)
# - support NodesConstraints 1.2.0 again
# - support HS2VmdPlayPlugin 0.3.x
# 3.9
# - refactored ExtensibleSaveFormat to one class
# - support HS2_FKIK
# 3.10
# - fix NodesConstraints 1.2.0 support bug
# 3.11 (Keitaro)
# - Screencap adapted to HS2/AI
# - more functions in Screencap class (used in autocharamoments)
# 4.0 (Keitaro)
# - HSPE, KKPE moved to AIPE code
# - Screencap moved back to KK Screencap only. HS2/AI uses another structure, so it can't be applied
# - AIPE support Get/Set only BlendShapes XML / converted Objects
# 4.1 (countd360)
# - support ABMX for PH, KK, AI and HS2 (need dll)
# 4.2 (Keitaro)
# - support PH_ExtensibleSaveFormat (for BepisPlugins 16.2.0.2 and above)
# - support PHAPI (at least to make buttons in Studio)
# 4.3 (Keitaro)
# - support Timeline plugin
# 4.4 (hentai-san)
# - Support DHH (1.15.10)
# - Support Graphics (0.3.3.36)
# 4.7 (Keitaro)
# - Support latest Graphics (0.4.0) with new Preset load/save logic
# 4.8 (Countd360)
# - Support XXPE data for Prop
# - HS2: support for StudioCharaEditor/BoobSetting
# 5.0 (Keitaro)
# - support fot Sideloader ID resolving
# 5.1 (Countd360)
# - KK/AI/HS2: Support MoreAccessories
# - HS2: Update StudioCharaEditor to use Sideloader ID resolving
# 5.2 (Countd360)
# - HS2: Update StudioCharaEditor support
# 5.3 (Keitaro)
# - BepInEx support
# 5.4 (Countd360)
# - KKS: ABMX/ILLAPI/Sideloader/ExtensibleSaveFormat support KKS
# - ALL: add a open file dialog function to ILLAPI
# 5.4.1 (Keitaro)
# - small upd text in open file dialog
# 5.4.2 (Keitaro, Zanith)
# - HS1: support for ExtraItemAnimations
# 5.5
# - KKS: KKSPE and KKS_VmdPlayPlugin support
# 5.7
# - KK: bug fix for ILLAPI not correctly detected in KK
# 5.7.1
# - KK: bug fix for ABMX/Sideload not correctly detected in KK
# 5.8
# - KK: update sideloader plugin to support zipmod motion better
# 5.9
# - ALL: add BepInEx function to list all plugins and check for plugin
# 6.0
# - AI/HS2: update more accessory support class, not support KK/KKS!?
# - HS2: add chara editor support class
# - KK/KKS: add skin effect support class
# - HS2: add overlay support class (in process)
# 6.1
# - KK: Fix bug for videoplayer support
# 6.2
# - KK/KKS/AI/HS2: get dic key update infomation of imported scene from NodesConstraints 
# 6.3
# - Timeline: support timeline data
# 6.4
# - KK/KKS: updated AdditionalSkinEffects to support more effect

# Use "dotPeek" to decompile native plugin dlls to understand how they work.
# Download link: https://www.jetbrains.com/decompiler/download/

# Adding your own class/function:
#   - Create a class extended from ExtPlugin 
#   - Use plugin name as class name for minimal confusion
#   - Create functions to call native plugin functions
#   - Do not create code that affects other functions if possible


import traceback
import clr
import os
from vngameengine import get_engine_id, HSNeoOCIChar

pluginsDetection = {}

class ExtPlugin(): # v1.0
    def __init__(self, name = None):
        self.isDetected = False
        self.name = name
        #print "ExtPlugin:", name
        self.initFlag()

    @staticmethod
    def calc_plugin_dir():
        from UnityEngine import Application
        from os import path
        rootfolder = path.realpath(path.join(Application.dataPath, '..'))

        # os.path.splitext(__file__)[0] + '.ini'
        pydirname = path.dirname(__file__)
        pluginpath = path.realpath(path.join(pydirname, '..', '..'))
        return path.relpath(pluginpath, rootfolder)

    @staticmethod
    def exists(name):
        # usually check IPA folder
        # plgdir = os.path.join(os.getcwd(), "Plugins/")
        #
        # if get_engine_id() == "charastudio":
        #     # in CharaStudio check BepInEx folder
        #     plgdir = os.path.join(os.getcwd(), "BepInEx/")
        #
        # if get_engine_id() == "neov2":
        #     # in StudioNeoV2 check BepInEx/plugins folder
        #     plgdir = os.path.join(os.getcwd(), "BepInEx/Plugins/")

        # ------------ old check - using DLL file path ---------------------
        # plgdir = ExtPlugin.calc_plugin_dir()
        # return os.path.isfile(plgdir + "/" + name + ".dll")

        # new check
        global pluginsDetection
        if name in pluginsDetection:
            #print "Cached explugin check for ", name
            pass
        else:
            try:
                clr.AddReference(name)
                pluginsDetection[name] = True
            except Exception as e:
                pluginsDetection[name] = False

        return pluginsDetection[name]
    # init flags
    def initFlag(self):
        #plgdir = os.path.join(os.getcwd(), "Plugins/")
        if ExtPlugin.exists(self.name):
            self.isDetected = True
            clr.AddReference(self.name)
            #print(self.name + ".dll detected.")
        else:
            self.isDetected = False
            print(self.name + ".dll not found.")

# ::::::::::: StudioNeo ::::::::::: 
class ExtraItemAnimations(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "ExtraItemAnimations")

    def GetCurrentAnimation(self, item):
        if self.isDetected == True:
            try:
                from ExtraItemAnimations import AnimationTools
                try:
                    return AnimationTools.GetCurrentAnimationName(item)
                except SystemError as e:
                    print("Failed to get anim from AnimationTools")
                    print(str(e))
                    pass
            except SystemError as e:
                print("Failed to import ExtraItemAnimationsPlugin")
                print(str(e))
                pass
        else:
            print("Failed to init ExtraItemAnimations")
            pass


    def PlayAnimation(self, item, animation_name):
        if self.isDetected == True:
            try:
                from ExtraItemAnimations import AnimationTools
                try:
                    AnimationTools.PlayAnimation(item, animation_name)
                except SystemError as e:
                    print("Failed to play anim on AnimationTools")
                    print(str(e))
                    pass
            except SystemError as e:
                print("Failed to import ExtraItemAnimationsPlugin")
                print(str(e))
                pass

# --- HSStudioNeoAddon --- 
class HSStudioNEOAddon(ExtPlugin):
    def __init__(self):
        #super(HSStudioNEOAddon, self).__init__("HSStudioNEOAddon") #can't get it to work
        ExtPlugin.__init__(self, "HSStudioNEOAddon")
        if self.isDetected:
            from HSStudioNEOAddon import BaseMgr, HSVMDAnimationMgr
            self.vmdMgr = BaseMgr[HSVMDAnimationMgr].Instance

    # Activate simulataneous FK/IK (char: HSNeoOCIChar, OCIChar)
    def activateFKIK(self, char):
        from Studio import OCIChar
        if self.isDetected == True and not char == None:
            from HSStudioNEOAddon import FKIKAssist
            if isinstance(char, HSNeoOCIChar):
                FKIKAssist.FKIKAssistMgr.ActivateFKIK(char.objctrl)
            elif isinstance(char, OCIChar): #might be redundant, not sure
                FKIKAssist.FKIKAssistMgr.ActivateFKIK(char)

    # MMD play ALL
    def MMDPlayAll(self):
        if self.isDetected:
            self.vmdMgr.PlayAll()
            
    # MMD Pause ALL
    def MMDPauseAll(self):
        if self.isDetected:
            self.vmdMgr.PauseAll()

    # MMD play ALL
    def MMDStopAll(self):
        if self.isDetected:
            self.vmdMgr.StopAll()
    
    # MMD set anime position
    def MMDSetAnimPositionAll(self, time):
        if self.isDetected:
            self.vmdMgr.PlayAll()
            self.vmdMgr.PauseAll()
            for vc in self.vmdMgr.controllers:
                vc.SetAnimPosition(time)
            self.vmdMgr.SoundMgr.AnimePosition = time
                
    # MMD get controller for char
    def MMDGetCharVmdController(self, char):
        from Studio import OCIChar
        if self.isDetected and char:
            if isinstance(char, HSNeoOCIChar):
                tgtOCIChar = char.objctrl
            elif isinstance(char, OCIChar): #might be redundant, not sure
                tgtOCIChar = char
            else:
                return None
            for vc in self.vmdMgr.controllers:
                if vc.studioChara == tgtOCIChar:
                    return vc
        return None
        
    # MMD export setting for char
    def MMDExportCharVmdSetting(self, char):
        vc = self.MMDGetCharVmdController(char)
        if vc and vc.VMDAnimEnabled:
            exset = {}
            exset["VMDAnimEnabled"] = "1"
            exset["lastLoadedVMD"] = vc.lastLoadedVMD if vc.lastLoadedVMD else ""
            exset["speed"] = str(vc.speed)
            exset["Loop"] = "1" if vc.Loop else "0"
            exset["centerBasePos"] = "(%.3f, %.3f, %.3f)"%(vc.centerBasePos.x, vc.centerBasePos.y, vc.centerBasePos.z)
            return exset
        else:
            return None
            
    # MMD import setting for char
    def MMDImportCharVmdSetting(self, char, imSet):
        vc = self.MMDGetCharVmdController(char)
        if vc == None:
            return
        # TODO: load settings
        
# --- HSStudioNEOExtSave ---
class HSStudioNEOExtSave(ExtPlugin):
    def __init__(self):
        #super(HSStudioNEOExtSave, self).__init__("HSStudioNEOExtSave") #can't get it to work
        ExtPlugin.__init__(self, "HSStudioNEOExtSave")
        
    # Save ext data
    def SaveExtData(self, filePath):
        from HSStudioNEOExtSave import StudioNEOExtendSaveMgr
        StudioNEOExtendSaveMgr.Instance.SaveExtData(filePath)
        
    # Load ext data
    def LoadExtData(self, filePath):
        from HSStudioNEOExtSave import StudioNEOExtendSaveMgr
        StudioNEOExtendSaveMgr.Instance.LoadExtData(filePath)
        StudioNEOExtendSaveMgr.Instance.LoadExtDataRaw(filePath)

    def ExtDataGet(self):
        import clr
        clr.AddReference("System.Xml")

        from HSStudioNEOExtSave import StudioNEOExtendSaveMgr
        #handlers = StudioNEOExtendSaveMgr.Instance.GetField("handlers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
        handlers = GetPrivate(StudioNEOExtendSaveMgr.Instance, "handlers")

        from System.Xml import XmlDocument

        if len(handlers) > 0:
            xmlDocument = XmlDocument()
            newChild = xmlDocument.CreateXmlDeclaration("1.0", "utf-8", None)
            xmlDocument.AppendChild(newChild)
            xmlElement = xmlDocument.CreateElement("ExtSave")
            xmlDocument.AppendChild(xmlElement)
            for handler in handlers:
                try:
                    handler.OnSave(xmlElement)
                except Exception as e:
                    print("Error in ExtSave handler save: " + str(e))

            #return xmlDocument.ToString()
            from System.IO import StringWriter
            #from System.Xml import XmlTextWriter

            stringWriter = StringWriter()
            xmlDocument.Save(stringWriter)
            return stringWriter.ToString()

        return ""

    def ExtDataSet(self, datastring):
        import clr
        clr.AddReference("System.Xml")

        from HSStudioNEOExtSave import StudioNEOExtendSaveMgr
        # handlers = StudioNEOExtendSaveMgr.Instance.GetField("handlers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
        handlers = GetPrivate(StudioNEOExtendSaveMgr.Instance, "handlers")

        from System.Xml import XmlDocument

        if len(handlers) > 0:
            xmlDocument = XmlDocument()
            xmlDocument.LoadXml(datastring)
            documentElement = xmlDocument.DocumentElement
            for handler in handlers:
                try:
                    handler.OnLoad(documentElement)
                except Exception as e:
                    print("Error in ExtSave handler load: " + str(e))

# ::::::::::: PlayHome Studio :::::::::::
class PHSAddon(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "PHSAddon")
        # support class instance from plugin
        if self.isDetected:
            from PHSAddon import BaseMgr, HSVMDAnimationMgr, ExtMP3
            self.vmdMgr = BaseMgr[HSVMDAnimationMgr].Instance
            self.mp3player = ExtMP3.MP3Player

    # Activate simulataneous FK/IK (char: HSNeoOCIChar, OCIChar)
    def activateFKIK(self, char):
        from Studio import OCIChar
        if self.isDetected == True and not char == None:
            from PHSAddon import FKIKAssist
            if isinstance(char, HSNeoOCIChar):
                try:
                    FKIKAssist.FKIKAssistMgr.ActivateFKIK(char.objctrl)
                except SystemError as e:
                    #print(str(e))
                    pass
            elif isinstance(char, OCIChar): #might be redundant, not sure
                try:
                    FKIKAssist.FKIKAssistMgr.ActivateFKIK(char)
                except SystemError as e:
                    #print(str(e))
                    pass
    
    # MMD play ALL
    def MMDPlayAll(self):
        if self.isDetected:
            self.vmdMgr.PlayAll()
            self.mp3player.Play()
            
    # MMD Pause ALL
    def MMDPauseAll(self):
        if self.isDetected:
            self.vmdMgr.PauseAll()
            self.mp3player.Pause()

    # MMD play ALL
    def MMDStopAll(self):
        if self.isDetected:
            self.vmdMgr.StopAll()
            self.mp3player.Stop()
    
    # MMD set anime position
    def MMDSetAnimPositionAll(self, time):
        if self.isDetected:
            self.vmdMgr.PlayAll()
            self.vmdMgr.PauseAll()
            for vc in self.vmdMgr.controllers:
                vc.SetAnimPosition(time)
            #self.mp3player.Seek(time)
            from System import Single
            #p = Array[Single]([time])
            CallPrivateStatic(self.mp3player, "Seek", [Single(time)])
                
    # MMD get controller for char
    def MMDGetCharVmdController(self, char):
        from Studio import OCIChar
        if self.isDetected and char:
            if isinstance(char, HSNeoOCIChar):
                tgtOCIChar = char.objctrl
            elif isinstance(char, OCIChar): #might be redundant, not sure
                tgtOCIChar = char
            else:
                return None
            for vc in self.vmdMgr.controllers:
                if vc.studioChara == tgtOCIChar:
                    return vc
        return None
        
    # MMD export setting for char
    def MMDExportCharVmdSetting(self, char):
        vc = self.MMDGetCharVmdController(char)
        if vc and vc.VMDAnimEnabled:
            exset = {}
            exset["VMDAnimEnabled"] = "1"
            exset["lastLoadedVMD"] = vc.lastLoadedVMD if vc.lastLoadedVMD else ""
            exset["lastLipPath"] = vc.lastLipPath if vc.lastLipPath else ""
            exset["isLipSync"] = "1" if vc.isLipSync else "0"
            exset["speed"] = str(vc.speed)
            exset["Loop"] = "1" if vc.Loop else "0"
            exset["centerBasePos"] = "(%.3f, %.3f, %.3f)"%(vc.centerBasePos.x, vc.centerBasePos.y, vc.centerBasePos.z)
            exset["hipPositionAdjust"] = "(%.3f, %.3f, %.3f)"%(vc.hipPositionAdjust.x, vc.hipPositionAdjust.y, vc.hipPositionAdjust.z)
            return exset
        else:
            return None
            
    # MMD import setting for char
    def MMDImportCharVmdSetting(self, char, imSet):
        vc = self.MMDGetCharVmdController(char)
        if vc == None:
            return
        if imSet.has_key("isLipSync"):
            vc.SetLipSyn(imSet["isLipSync"] == "1")
        # is the other settings necessary?        

# ::::::::::: StudioNeoV2 (AI/HS2) :::::::::::
class AIPE(ExtPlugin):
    def __init__(self):
        #Here we initialize (load code of plugin) depends on it's filename.
        #This allow you use same code for multiple Studios
        #If we not found - try next
        if ExtPlugin.exists("AIPE"):
            ExtPlugin.__init__(self, "AIPE")
        elif ExtPlugin.exists("HS2PE"):
            ExtPlugin.__init__(self, "HS2PE")
        elif ExtPlugin.exists("KKPE"):
            ExtPlugin.__init__(self, "KKPE")
        elif ExtPlugin.exists("KKSPE"):
            ExtPlugin.__init__(self, "KKSPE")
        elif ExtPlugin.exists("HSPENeo"):
            ExtPlugin.__init__(self, "HSPENeo")
        else:
            self.isDetected = False

    def GetCharaSettingsText(self, ocichar):
        import clr
        clr.AddReference("System.Xml")

        from System.IO import StringWriter
        from System.Xml import XmlTextWriter

        stringWriter = StringWriter()
        xmlTextWriter = XmlTextWriter(stringWriter)

        # here we import necessary class from plugin - almost same as in C#
        from HSPE import PoseController

        xmlTextWriter.WriteStartElement("characterInfo")
        # here we work with OCIChar. OCIChar passed to function - this is chara control object
        # we SaveXML - it data in XML in xmlTextWriter
        self.GetPoseController(ocichar).SaveXml(xmlTextWriter)
        xmlTextWriter.WriteEndElement()

        # here we return result as string
        # vnactor expect you will provide result in some of standart datatypes:
        # lists, arrays, int, string, float, Vector3D etc.
        # NOT in XML structures!
        # so good way is to convert it
        return stringWriter.ToString()

    # setting chara data
    def SetCharaSettingsText(self,ocichar, text):
        import clr
        clr.AddReference("System.Xml")

        from System.Xml import XmlDocument

        xmlDocument = XmlDocument()
        xmlDocument.LoadXml(text)
        node = xmlDocument.FirstChild

        from HSPE import PoseController

        self.GetPoseController(ocichar).ScheduleLoad(node, None)

    def GetPoseController(self, ociobj):
        from HSPE import PoseController
        from Studio import OCIItem, OCIChar
        if isinstance(ociobj, OCIChar):
            return ociobj.charInfo.gameObject.GetComponent(PoseController)
        elif isinstance(ociobj, OCIItem):
            return ociobj.objectItem.GetComponent(PoseController)
        else:
            return None

    def GetModules(self,ocichar):
        poseController = self.GetPoseController(ocichar)
        if poseController:
            return GetPrivate(poseController, "_modules")
        else:
            return []

    def GetBlendShapesEditor(self,ocichar):
        from HSPE.AMModules import BlendShapesEditor
        modules = self.GetModules(ocichar)
        for mod in modules:
            if type(mod) == BlendShapesEditor:
                return mod
        return None

    def GetBlendShapesXML(self,ocichar):
        import clr
        clr.AddReference("System.Xml")

        from System.IO import StringWriter
        from System.Xml import XmlTextWriter

        stringWriter = StringWriter()
        xmlTextWriter = XmlTextWriter(stringWriter)

        xmlTextWriter.WriteStartElement("blendShapes")
        self.GetBlendShapesEditor(ocichar).SaveXml(xmlTextWriter)
        xmlTextWriter.WriteEndElement()

        return stringWriter.ToString()

    def GetBlendShapesObj(self,ocichar):
        import clr
        clr.AddReference("System.Xml")

        from System.IO import StringWriter
        from System.Xml import XmlTextWriter, XmlDocument

        settingData = self.GetBlendShapesXML(ocichar)

        fullObj = {}
        try:

            # load contents of setting data
            xDoc = XmlDocument()
            xDoc.LoadXml(settingData)


            # find each setting and adjust
            xRoot = xDoc.FirstChild
            for nc in xRoot.FirstChild.ChildNodes:
                if nc.Name == "skinnedMesh":
                    obj = {}
                    for blsh in nc.ChildNodes:
                        ind = str(blsh.GetAttribute("index"))
                        w = float(blsh.GetAttribute("weight"))
                        obj[ind] = w
                    fullObj[str(nc.GetAttribute("name"))] = obj


                else:
                    print("XXPEBlendShapes Warning: unknown setting node '%s'"%nc.Name)
        except:
            pass
        return fullObj


    def SetBlendShapesXML(self,ocichar,text):
        import clr
        clr.AddReference("System.Xml")

        #from System.IO import StringWriter
        from System.Xml import XmlDocument

        xmlDocument = XmlDocument()
        xmlDocument.LoadXml(text)
        node = xmlDocument.FirstChild

        self.GetBlendShapesEditor(ocichar).LoadXml(node)

    def SetBlendShapesObj(self,ocichar,fullObj):
        import clr
        clr.AddReference("System.Xml")

        from System.IO import StringWriter
        from System.Xml import XmlTextWriter

        stringWriter = StringWriter()
        xmlTextWriter = XmlTextWriter(stringWriter)

        xmlTextWriter.WriteStartElement("blendShapes")
        #self.GetBlendShapesEditor(ocichar).SaveXml(xmlTextWriter)
        xmlTextWriter.WriteStartElement("skinnedMeshes")
        for smeshes in fullObj:
            xmlTextWriter.WriteStartElement("skinnedMesh")
            xmlTextWriter.WriteAttributeString("name", str(smeshes))
            for smesh in fullObj[smeshes]:
                xmlTextWriter.WriteStartElement("blendShape")
                xmlTextWriter.WriteAttributeString("index", str(smesh))
                xmlTextWriter.WriteAttributeString("weight", str(fullObj[smeshes][smesh]))
                xmlTextWriter.WriteEndElement()
            xmlTextWriter.WriteEndElement()

        xmlTextWriter.WriteEndElement()
        xmlTextWriter.WriteEndElement()

        #print stringWriter.ToString()
        text = stringWriter.ToString()
        #pass
        self.SetBlendShapesXML(ocichar,text)

# ::::::::::: CharaStudio ::::::::::: 
class KKPE(AIPE): # the same as AIPE class
    pass
    # def __init__(self):
    #     #super(HSStudioNEOAddon, self).__init__("HSStudioNEOAddon") #can't get it to work
    #     #pass
    #     #ExtPlugin.__init__(self, "KKPE")
    #     super(AIPE, self).__init__()

class HSPENeo(AIPE): # the same as AIPE class
    #pass
    def __init__(self):
        ExtPlugin.__init__(self, "HSPENeo")

class ScreencapPlugin(ExtPlugin):
    def __init__(self):
        if ExtPlugin.exists("Screencap"):
            ExtPlugin.__init__(self, "Screencap")
        # elif ExtPlugin.exists("HS2_Screencap"):
        #     ExtPlugin.__init__(self, "HS2_Screencap")
        # elif ExtPlugin.exists("AI_Screencap"):
        #     ExtPlugin.__init__(self, "AI_Screencap")
        else:
            self.isDetected = False
        #ExtPlugin.__init__(self, "Screencap")

    def TakeCharScreenshot(self):
        from Screencap import ScreenshotManager
        from Studio import Studio
        studio = Studio.Instance

        studio.StartCoroutine(
            CallPrivate(ScreenshotManager.Instance, "TakeCharScreenshot", []))  # this take screenshot without UI

    def TakeScreenshot(self):
        from Screencap import ScreenshotManager
        from Studio import Studio
        studio = Studio.Instance

        CallPrivate(ScreenshotManager.Instance, "TakeScreenshot", []) # this take screenshot with UI

    def GetUniqueFilename(self):
        from Screencap import ScreenshotManager

        uniqueFilename = CallPrivate(ScreenshotManager.Instance, "GetUniqueFilename", [""])
        return uniqueFilename

    def GetScrenshotTexture(self, transparency): # higly beta and non-stable
        from Screencap import ScreenshotManager

        currentAlphaShot = GetPrivate(ScreenshotManager.Instance, "currentAlphaShot")
        #print currentAlphaShot
        #print ScreenshotManager.Instance.ResolutionX.Value
        #bytes = currentAlphaShot.Capture(ScreenshotManager.Instance.ResolutionX.Value, ScreenshotManager.Instance.ResolutionY.Value, ScreenshotManager.Instance.DownscalingRate.Value, False)
        bytes = currentAlphaShot.Capture(ScreenshotManager.Instance.ResolutionX.Value,
                                         ScreenshotManager.Instance.ResolutionY.Value,
                                         ScreenshotManager.Instance.DownscalingRate.Value, transparency)
        #File.WriteAllBytes(uniqueFilename, bytes) # this save in PNG

        # converting to JPG
        from UnityEngine import GUI, Texture2D
        tex = Texture2D(ScreenshotManager.Instance.ResolutionX.Value, ScreenshotManager.Instance.ResolutionY.Value);
        tex.LoadImage(bytes)

        return tex

class KKVMDPlayPlugin(ExtPlugin):
    def __init__(self):
        if ExtPlugin.exists("KKVMDPlayPlugin"):
            ExtPlugin.__init__(self, "KKVMDPlayPlugin")
        elif ExtPlugin.exists("KKS_VMDPlayPlugin"):
            ExtPlugin.__init__(self, "KKS_VMDPlayPlugin")
        else:
            self.isDetected = False
        
        # support class instance from plugin
        if self.isDetected:
            from KKVMDPlayPlugin import VMDAnimationMgr
            self.vmdMgr = VMDAnimationMgr.Instance
            self.cameraMgr = self.vmdMgr.CameraMgr
            self.audioMgr = self.vmdMgr.SoundMgr

    # MMD play ALL
    def MMDPlayAll(self):
        if self.isDetected:
            self.vmdMgr.PlayAll()
            
    # MMD Pause ALL
    def MMDPauseAll(self):
        if self.isDetected:
            self.vmdMgr.PauseAll()

    # MMD play ALL
    def MMDStopAll(self):
        if self.isDetected:
            self.vmdMgr.StopAll()
    
    # MMD set anime position
    def MMDSetAnimPositionAll(self, time):
        if self.isDetected:
            self.vmdMgr.PlayAll()
            self.vmdMgr.PauseAll()
            for vc in self.vmdMgr.controllers:
                vc.SetAnimPosition(time)
            self.cameraMgr.AnimePosition = time
            self.audioMgr.AnimePosition = time
                
    # MMD get controller for char
    def MMDGetCharVmdController(self, char):
        from Studio import OCIChar
        if self.isDetected and char:
            if isinstance(char, HSNeoOCIChar):
                tgtChara = char.objctrl.charInfo
            elif isinstance(char, OCIChar): #might be redundant, not sure
                tgtChara = char.charInfo
            else:
                return None
            for vc in self.vmdMgr.controllers:
                if vc.chara == tgtChara:
                    return vc
        return None
        
    # MMD export setting for char
    def MMDExportCharVmdSetting(self, char):
        vc = self.MMDGetCharVmdController(char)
        if vc and vc.VMDAnimEnabled:
            exset = {}
            exset["VMDAnimEnabled"] = "1"
            exset["lastLoadedVMD"] = vc.lastLoadedVMD if vc.lastLoadedVMD else ""
            #exset["isLipSync"] = "1" if vc.isLipSync else "0"
            #exset["speed"] = str(vc.speed)
            #exset["Loop"] = "1" if vc.Loop else "0"
            return exset
        else:
            return None
            
    # MMD import setting for char
    def MMDImportCharVmdSetting(self, char, imSet):
        vc = self.MMDGetCharVmdController(char)
        if vc == None:
            return
        if imSet.has_key("isLipSync"):
            vc.SetLipSyn(imSet["isLipSync"] == "1")
        # is the other settings necessary?     

    # MMD get controller infomation
    def MMDGetAllVmdController(self):
        if not self.isDetected:
            return None
        vcs = []
        for vc in self.vmdMgr.controllers:
            vcs.append(vc)
        return tuple(vcs)

    def MMDGetVmdInfo(self, ctrl):
        info = {}
        try:
            # version 0.1.x
            clip = ctrl.animationForVMD.clip
            info["Loaded"] = clip != None
            info["ChaControl"] = ctrl.chara
            info["FilePath"] = ctrl.lastLoadedVMD
            info["Length"] = 0 if clip == None else ctrl.animationForVMD.clip.length
            info["Frames"] = 0 if clip == None else int(ctrl.animationForVMD.clip.length * 30.0)
            #info["BoneNameMap"] = ctrl.boneNameMap
            #info["BoneAdjustMap"] = ctrl.boneAdjust
        except:
            # version 0.3.x
            state = ctrl.GetModelAnimationState()
            info["Loaded"] = state != None
            info["ChaControl"] = ctrl.chara
            info["FilePath"] = ctrl.lastLoadedVMD
            info["Length"] = 0 if state == None else state.length
            info["Frames"] = 0 if state == None else int(state.length * 30.0)
            #info["BoneNameMap"] = ctrl.boneNameMap
            #info["BoneAdjustMap"] = ctrl.boneAdjust
        return info

    def MMDGetCameraInfo(self):
        if not self.isDetected:
            return None
        info = {}
        info["Loaded"] = False
        if self.cameraMgr.AnimeLength > 0 and self.cameraMgr.cameraVMDFilePath != None:
            info["Loaded"] = True
            info["Enabled"] = self.cameraMgr.CameraEnabled
            info["FilePath"] = self.cameraMgr.cameraVMDFilePath
            info["Length"] = self.cameraMgr.AnimeLength
        return info

    def MMDGetSoundInfo(self):
        if not self.isDetected:
            return None
        info = {}
        info["Loaded"] = False
        if self.audioMgr.currentAudioClip != None:
            info["Loaded"] = True
            info["FilePath"] = self.audioMgr.audioFilePath
            info["Length"] = self.audioMgr.SoundLength
        return info

    # control and sync
    def MMDPlayVmd(self, ctrl):
        ctrl.Play()

    def MMDPauseVmd(self, ctrl):
        ctrl.Pause()

    def MMDStopVmd(self, ctrl):
        ctrl.Stop()

    def MMDSyncVmd(self, ctrl, time):
        ctrl.SetAnimPosition(time)

    def MMDPlayCamera(self):
        if self.isDetected:
            self.cameraMgr.Play()

    def MMDPauseCamera(self):
        if self.isDetected:
            self.cameraMgr.Pause()

    def MMDStopCamera(self):
        if self.isDetected:
            self.cameraMgr.Stop()

    def MMDSyncCamera(self, time):
        if self.isDetected:
            self.cameraMgr.AnimePosition = time

    def MMDPlaySound(self):
        if self.isDetected:
            self.audioMgr.PlaySound()

    def MMDPauseSound(self):
        if self.isDetected:
            self.audioMgr.PauseSound()

    def MMDStopSound(self):
        if self.isDetected:
            self.audioMgr.StopSound()

    def MMDSyncSound(self, time):
        if self.isDetected:
            self.audioMgr.AnimePosition = time

class AIVMDPlugin(ExtPlugin):
    def __init__(self):
        self.isDetected = False
        self.name = ""
        # try load AIVMDPlugin from maki3d, as manager1
        if ExtPlugin.exists("AIVMDPlugin"):
            self.isDetected1 = True
            self.name = "AIVMDPlugin"
            clr.AddReference("AIVMDPlugin")
            from AIVMDPlugin import VMDMotionManager
            self.vmdMgr1 = VMDMotionManager.Instance
            self.audioMgr1 = self.vmdMgr1.AudioManager
            self.cameraMgr1 = self.vmdMgr1.CameraManager
        else:
            self.isDetected1 = False
        # try load AIVMDPlayer from craft6, as manager2
        if ExtPlugin.exists("AIVMDPlayer"):
            self.isDetected2 = True
            self.name = "AIVMDPlugin"
            clr.AddReference("AIVMDPlayer")
            from AIVMDPlayer import VMDAnimationMgr
            self.vmdMgr2 = VMDAnimationMgr.Instance
            self.audioMgr2 = self.vmdMgr2.SoundMgr
            self.cameraMgr2 = self.vmdMgr2.CameraMgr
        else:
            self.isDetected2 = False
        # if found any one
        self.isDetected = self.isDetected1 or self.isDetected2

    # MMD play ALL
    def MMDPlayAll(self):
        if self.isDetected1:
            self.vmdMgr1.playAll()
        if self.isDetected2:
            self.vmdMgr2.PlayAll()
            
    # MMD Pause ALL
    def MMDPauseAll(self):
        if self.isDetected1:
            self.vmdMgr1.pauseAll()
        if self.isDetected2:
            self.vmdMgr2.PauseAll()

    # MMD play ALL
    def MMDStopAll(self):
        if self.isDetected1:
            self.vmdMgr1.stopAll()
        if self.isDetected2:
            self.vmdMgr2.StopAll()
    
    # MMD set anime position
    def MMDSetAnimPositionAll(self, time):
        if self.isDetected1:
            self.vmdMgr1.playAll()
            self.vmdMgr1.pauseAll()
            for vc in self.vmdMgr1.controllers:
                vc.SetAnimPosition(time)
            self.audioMgr1.AnimePosition = time
            self.cameraMgr1.AnimePosition = time
        if self.isDetected2:
            self.vmdMgr2.PlayAll()
            self.vmdMgr2.PauseAll()
            for vc in self.vmdMgr2.controllers:
                vc.SetAnimPosition(time)
            self.audioMgr2.AnimePosition = time
            self.cameraMgr2.AnimePosition = time
                
    # MMD get controller for char
    def MMDGetCharVmdController(self, char):
        from Studio import OCIChar
        if isinstance(char, HSNeoOCIChar):
            tgtChara = char.objctrl.charInfo
        elif isinstance(char, OCIChar): #might be redundant, not sure
            tgtChara = char.charInfo
        else:
            return None
        if self.isDetected1:
            for vc in self.vmdMgr1.controllers:
                if vc.chara == tgtChara:
                    return vc
        if self.isDetected2:
            for vc in self.vmdMgr2.controllers:
                if vc.chara == tgtChara:
                    return vc
        return None

    # MMD export setting for char
    def MMDExportCharVmdSetting(self, char):
        vc = self.MMDGetCharVmdController(char)
        if vc and vc.VMDAnimEnabled:
            exset = {}
            exset["VMDAnimEnabled"] = "1"
            exset["lastLoadedVMD"] = vc.lastLoadedVMD if vc.lastLoadedVMD else ""
            #exset["isLipSync"] = "1" if vc.isLipSync else "0"
            #exset["speed"] = str(vc.speed)
            #exset["Loop"] = "1" if vc.Loop else "0"
            return exset
        else:
            return None

    # MMD import setting for char
    def MMDImportCharVmdSetting(self, char, imSet):
        vc = self.MMDGetCharVmdController(char)
        if vc == None:
            return
        #if imSet.has_key("isLipSync"):
        #    vc.SetLipSyn(imSet["isLipSync"] == "1")
        # is the other settings necessary?     

    # MMD get all controller
    def MMDGetAllVmdController(self):
        if not self.isDetected:
            return None
        vcs = []
        if self.isDetected1:
            for vc in self.vmdMgr1.controllers:
                vcs.append(vc)
        if self.isDetected2:
            for vc in self.vmdMgr2.controllers:
                vcs.append(vc)
        return tuple(vcs)
    
    def MMDPlayVmd(self, ctrl):
        ctrl.Play()

    def MMDPauseVmd(self, ctrl):
        ctrl.Pause()

    def MMDStopVmd(self, ctrl):
        ctrl.Stop()

    def MMDSyncVmd(self, ctrl, time):
        ctrl.SetAnimPosition(time)

    def MMDGetVmdInfo(self, ctrl):
        info = {}
        clip = ctrl.animationForVMD.clip
        info["Loaded"] = clip != None
        info["ChaControl"] = ctrl.chara
        info["FilePath"] = ctrl.lastLoadedVMD
        info["Length"] = 0 if clip == None else ctrl.animationForVMD.clip.length
        info["Frames"] = 0 if clip == None else int(ctrl.animationForVMD.clip.length * 30.0)
        #info["BoneNameMap"] = ctrl.boneNameMap
        #info["BoneAdjustMap"] = ctrl.boneAdjust
        return info

    def MMDPlaySound(self):
        if self.isDetected1:
            self.audioMgr1.playSound()
        if self.isDetected2:
            self.audioMgr2.PlaySound()

    def MMDPauseSound(self):
        if self.isDetected1:
            self.audioMgr1.pauseSound()
        if self.isDetected2:
            self.audioMgr2.PauseSound()

    def MMDStopSound(self):
        if self.isDetected1:
            self.audioMgr1.stopSound()
        if self.isDetected2:
            self.audioMgr2.StopSound()

    def MMDSyncSound(self, time):
        if self.isDetected1:
            self.audioMgr1.AnimePosition = time
        if self.isDetected2:
            self.audioMgr2.AnimePosition = time

    def MMDGetSoundInfo(self):
        if not self.isDetected:
            return None
        info = {}
        info["Loaded"] = False
        if self.isDetected1 and self.audioMgr1.currentAudioClip != None:
            info["Loaded"] = True
            info["FilePath"] = self.audioMgr1.AudioFilePath
            info["Length"] = self.audioMgr1.SoundLength
        if self.isDetected2 and self.audioMgr2.currentAudioClip != None:
            info["Loaded"] = True
            info["FilePath"] = self.audioMgr2.audioFilePath
            info["Length"] = self.audioMgr2.SoundLength
        return info

    def MMDSetSound(self, pathname):
        if self.isDetected2:
            return self.audioMgr2.SetSoundClip(pathname)
        return False

    def MMDPlayCamera(self):
        if self.isDetected1:
            self.cameraMgr1.play()
        if self.isDetected2:
            self.cameraMgr2.Play()

    def MMDPauseCamera(self):
        if self.isDetected1:
            self.cameraMgr1.pause()
        if self.isDetected2:
            self.cameraMgr2.Pause()

    def MMDStopCamera(self):
        if self.isDetected1:
            self.cameraMgr1.stop()
        if self.isDetected2:
            self.cameraMgr2.Stop()

    def MMDSyncCamera(self, time):
        if self.isDetected1:
            self.cameraMgr1.AnimePosition = time
        if self.isDetected2:
            self.cameraMgr2.AnimePosition = time

    def MMDGetCameraInfo(self):
        if not self.isDetected:
            return None
        info = {}
        info["Loaded"] = False
        if self.isDetected1 and self.cameraMgr1.AnimeLength > 0 and self.cameraMgr1.cameraVMDFilePath != None:
            info["Loaded"] = True
            info["Enabled"] = self.cameraMgr1.CameraEnabled
            info["FilePath"] = self.cameraMgr1.cameraVMDFilePath
            info["Length"] = self.cameraMgr1.AnimeLength
        if self.isDetected2 and self.cameraMgr2.AnimeLength > 0 and self.cameraMgr2.cameraVMDFilePath != None:
            info["Loaded"] = True
            info["Enabled"] = self.cameraMgr2.CameraEnabled
            info["FilePath"] = self.cameraMgr2.cameraVMDFilePath
            info["Length"] = self.cameraMgr2.AnimeLength
        return info

class HS2VMDPlayPlugin(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "HS2_VMDPlayPlugin")
        # support class instance from plugin
        if self.isDetected:
            from HS2VMDPlayPlugin import VMDAnimationMgr
            self.vmdMgr = VMDAnimationMgr.Instance
            self.cameraMgr = self.vmdMgr.CameraMgr
            self.audioMgr = self.vmdMgr.SoundMgr

    # MMD play ALL
    def MMDPlayAll(self):
        if self.isDetected:
            self.vmdMgr.PlayAll()
            
    # MMD Pause ALL
    def MMDPauseAll(self):
        if self.isDetected:
            self.vmdMgr.PauseAll()

    # MMD play ALL
    def MMDStopAll(self):
        if self.isDetected:
            self.vmdMgr.StopAll()
    
    # MMD set anime position
    def MMDSetAnimPositionAll(self, time):
        if self.isDetected:
            self.vmdMgr.PlayAll()
            self.vmdMgr.PauseAll()
            for vc in self.vmdMgr.controllers:
                vc.SetAnimPosition(time)
            self.cameraMgr.AnimePosition = time
            self.audioMgr.AnimePosition = time
                
    # MMD get controller for char
    def MMDGetCharVmdController(self, char):
        from Studio import OCIChar
        if self.isDetected and char:
            if isinstance(char, HSNeoOCIChar):
                tgtChara = char.objctrl.charInfo
            elif isinstance(char, OCIChar): #might be redundant, not sure
                tgtChara = char.charInfo
            else:
                return None
            for vc in self.vmdMgr.controllers:
                if vc.chara == tgtChara:
                    return vc
        return None
        
    # MMD export setting for char
    def MMDExportCharVmdSetting(self, char):
        vc = self.MMDGetCharVmdController(char)
        if vc and vc.VMDAnimEnabled:
            exset = {}
            exset["VMDAnimEnabled"] = "1"
            exset["lastLoadedVMD"] = vc.lastLoadedVMD if vc.lastLoadedVMD else ""
            #exset["isLipSync"] = "1" if vc.isLipSync else "0"
            #exset["speed"] = str(vc.speed)
            #exset["Loop"] = "1" if vc.Loop else "0"
            return exset
        else:
            return None
            
    # MMD import setting for char
    def MMDImportCharVmdSetting(self, char, imSet):
        vc = self.MMDGetCharVmdController(char)
        if vc == None:
            return
        if imSet.has_key("isLipSync"):
            vc.SetLipSyn(imSet["isLipSync"] == "1")
        # is the other settings necessary?     

    # MMD get controller infomation
    def MMDGetAllVmdController(self):
        if not self.isDetected:
            return None
        vcs = []
        for vc in self.vmdMgr.controllers:
            vcs.append(vc)
        return tuple(vcs)

    def MMDGetVmdInfo(self, ctrl):
        info = {}
        state = ctrl.GetModelAnimationState()
        info["Loaded"] = state != None
        info["ChaControl"] = ctrl.chara
        info["FilePath"] = ctrl.lastLoadedVMD
        info["Length"] = 0 if state == None else state.length
        info["Frames"] = 0 if state == None else int(state.length * 30.0)
        #info["BoneNameMap"] = ctrl.boneNameMap
        #info["BoneAdjustMap"] = ctrl.boneAdjust
        return info

    def MMDGetCameraInfo(self):
        if not self.isDetected:
            return None
        info = {}
        info["Loaded"] = False
        if self.cameraMgr.AnimeLength > 0 and self.cameraMgr.cameraVMDFilePath != None:
            info["Loaded"] = True
            info["Enabled"] = self.cameraMgr.CameraEnabled
            info["FilePath"] = self.cameraMgr.cameraVMDFilePath
            info["Length"] = self.cameraMgr.AnimeLength
        return info

    def MMDGetSoundInfo(self):
        if not self.isDetected:
            return None
        info = {}
        info["Loaded"] = False
        if self.audioMgr.currentAudioClip != None:
            info["Loaded"] = True
            info["FilePath"] = self.audioMgr.audioFilePath
            info["Length"] = self.audioMgr.SoundLength
        return info

    # control and sync
    def MMDPlayVmd(self, ctrl):
        ctrl.Play()

    def MMDPauseVmd(self, ctrl):
        ctrl.Pause()

    def MMDStopVmd(self, ctrl):
        ctrl.Stop()

    def MMDSyncVmd(self, ctrl, time):
        ctrl.SetAnimPosition(time)

    def MMDPlayCamera(self):
        if self.isDetected:
            self.cameraMgr.Play()

    def MMDPauseCamera(self):
        if self.isDetected:
            self.cameraMgr.Pause()

    def MMDStopCamera(self):
        if self.isDetected:
            self.cameraMgr.Stop()

    def MMDSyncCamera(self, time):
        if self.isDetected:
            self.cameraMgr.AnimePosition = time

    def MMDPlaySound(self):
        if self.isDetected:
            self.audioMgr.PlaySound()

    def MMDPauseSound(self):
        if self.isDetected:
            self.audioMgr.PauseSound()

    def MMDStopSound(self):
        if self.isDetected:
            self.audioMgr.StopSound()

    def MMDSyncSound(self, time):
        if self.isDetected:
            self.audioMgr.AnimePosition = time

class DHH_AI(ExtPlugin):
    def __init__(self):
        self.is_new_dhh = ExtPlugin.exists("DHH_HS2") or ExtPlugin.exists("DHH_AI")
        if self.is_new_dhh:
            if ExtPlugin.exists("DHH_HS2"):
                ExtPlugin.__init__(self, "DHH_HS2")
            elif ExtPlugin.exists("DHH_AI"):
                ExtPlugin.__init__(self, "DHH_AI")
        else:
            ExtPlugin.__init__(self, "DHH_AI4")
            if self.isDetected:
                from DHH_AI4 import DHH_Main
                self.dhh_main = DHH_Main.instance
                self.dhh_path = os.path.realpath(GetPrivate(self.dhh_main, "path"))
                self.dhh_tempfile = os.path.join(self.dhh_path, "VNGETemp")
                self.delTempFiles()

    def getEnable(self):
        if self.isDetected:
            if self.is_new_dhh:
                from DHH import DHH_Main
                return DHH_Main.GraphicEnable
            else:
                return GetPrivate(self.dhh_main, "GraphicEnable")
        else:
            return False

    def setEnable(self, enable):
        if self.isDetected:
            if self.is_new_dhh:
                from DHH import DHH_Main
                DHH_Main.GraphicEnable = enable
            else:
                self.dhh_main.SetEnable(enable)

    def exportGraphSetting(self):
        if self.isDetected:
            if self.is_new_dhh:
                from DHH import DHH_Main
                return DHH_Main.Setting
            else:
                self.delTempFiles()
                idx = 0
                while (True):
                    indexedFile = self.dhh_tempfile + str(idx)
                    if not os.path.exists(indexedFile):
                        break
                    else:
                        idx += 1
                CallPrivate(self.dhh_main, "SaveGraphicSetting", [indexedFile])
                fp = open(indexedFile, "r")
                settxt = fp.read()
                fp.close()
                return settxt
        else:
            return None

    def importGraphSetting(self, setting):
        if self.isDetected:
            if self.is_new_dhh:
                from DHH import DHH_Main
                DHH_Main.Setting = setting
            else:
                self.delTempFiles()
                idx = 0
                while (True):
                    try:
                        indexedFile = self.dhh_tempfile + str(idx)
                        fp = open(indexedFile, "w")
                        fp.write(setting)
                        fp.flush()
                        fp.close()
                        break
                    except:
                        idx += 1
                # LoadGraphicSetting didn't close file stream...
                CallPrivate(self.dhh_main, "LoadGraphicSetting", [indexedFile])

    def delTempFiles(self):
        if self.isDetected:
            import os
            tempfiles = []
            for f in os.listdir(self.dhh_path):
                if f.startswith("VNGETemp"):
                    tempfiles.append(os.path.join(self.dhh_path, f))
            for f in tempfiles:
                try:
                    os.remove(f)
                except:
                    pass

class Graphics(ExtPlugin):
    def __init__(self):
        if ExtPlugin.exists("HS2Graphics"):
            ExtPlugin.__init__(self, "HS2Graphics")
        elif ExtPlugin.exists("AIGraphics"):
            ExtPlugin.__init__(self, "AIGraphics")

    def getEnable(self):
        if self.isDetected:
            try:
                from Graphics import Graphics
                return Graphics.PluginEnabled
            except Exception as e:
                print("cant get Graphics.PluginEnabled ")
                return True
        else:
            return False

    def setEnable(self, enable):
        if self.isDetected:
            try:
                from Graphics import Graphics
                Graphics.PluginEnabled = enable
            except Exception as e:
                print("cant set Graphics.PluginEnabled ")

    def getIgnoreDHHConflicts(self):
        if self.isDetected:
            try:
                from Graphics import Graphics
                return Graphics.IgnoreDHHConflicts
            except Exception as e:
                print("cant get Graphics.IgnoreDHHConflicts ")
                return True
        else:
            return True

    def setIgnoreDHHConflicts(self, ignore):
        if self.isDetected:
            try:
                from Graphics import Graphics
                Graphics.IgnoreDHHConflicts = ignore
            except Exception as e:
                print("cant set Graphics.IgnoreDHHConflicts ")

    def exportPreset(self):
        if self.isDetected:
            try:
                from Graphics import Graphics
                return Graphics.PresetString
            except Exception as e:
                # try new way Graphics
                prMan = self.getPresetsManager()
                return CallPrivate(prMan,"get_CurrentPreset",[])
        else:
            return None

    def importPreset(self, setting):
        if self.isDetected:
            try:
                from Graphics import Graphics
                Graphics.PresetString = setting
            except Exception as e:
                # try new way Graphics
                prMan = self.getPresetsManager()
                CallPrivate(prMan,"set_CurrentPreset",[setting])

    def getPresetsManager(self):
        from Graphics import Graphics
        # Graphics.PresetManager
        return GetPrivate(Graphics.Instance, "_presetManager")

    def getPresetData(self):
        if self.isDetected:
            try:
                from Graphics import Graphics
                #ba = Graphics.Instance.preset.Serialize()

                # workaround - get Bytes from ExtData
                paMan = self.getPresetsManager()
                extdata = CallPrivate(paMan, "GetExtendedData", [])
                ba = extdata.data["bytes"]
                from vnactor import bytearray_to_str64
                return bytearray_to_str64(ba)
            except Exception as e:
                print("cant get Graphics.Instance.preset.Serialize()")
                return ""
        else:
            return ""

    def setPresetData(self,str64):
        if self.isDetected:
            try:
                from Graphics import Graphics
                #ba = Graphics.Instance.preset.Serialize()
                from vnactor import str64_to_bytearray
                #ba = str64_to_bytearray(str64)
                #Graphics.Instance.preset.Load(ba)

                # workaround - save in presets folder and load tempvnge preset
                from System.IO import File
                File.WriteAllBytes("presets/tempvnge.preset", str64_to_bytearray(str64))
                CallPrivate(self.getPresetsManager(), "LoadPresets",[]) # reloading preset list
                self.importPreset("tempvnge")
            except Exception as e:
                print("cant Graphics.Instance.preset.Load()")
                return ""
        else:
            return ""

class AI_FKIK(ExtPlugin):
    def __init__(self):
        if ExtPlugin.exists("AI_FKIK"):
            ExtPlugin.__init__(self, "AI_FKIK")
        elif ExtPlugin.exists("HS2_FKIK"):
            ExtPlugin.__init__(self, "HS2_FKIK")
        else:
            self.isDetected = False
        #ExtPlugin.__init__(self, "AI_FKIK")

    # Activate simulataneous FK/IK (char: HSNeoOCIChar, OCIChar)
    def activateFKIK(self, char):
        from Studio import OCIChar
        if self.isDetected == True and not char == None:
            from KK_Plugins import FKIK
            if isinstance(char, HSNeoOCIChar):
                FKIK.EnableFKIK(char.objctrl)
            elif isinstance(char, OCIChar): #might be redundant, not sure
                FKIK.EnableFKIK(char)


class CharaAnime(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "CharaAnime")
        if self.isDetected:
            from CharaAnime import CharaAnimeMgr, GizmosManager
            self.Manager = CharaAnimeMgr.Instance
            self.GizmosMgr = GizmosManager.Instance

# ::::::::::: NodesConstraints (HS,KK) :::::::::::
class NodesConstraints(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "NodesConstraints")

    def GetSysSettingsText(self):
        import clr
        clr.AddReference("System.Xml")

        from System.IO import StringWriter
        from System.Xml import XmlTextWriter, XmlDocument

        stringWriter = StringWriter()
        xmlTextWriter = XmlTextWriter(stringWriter)

        xmlTextWriter.WriteStartElement("nodescon")
        #ocichar.charInfo.gameObject.GetComponent(PoseController).SaveXml(xmlTextWriter)
        #from Studio import Studio
        from NodesConstraints import NodesConstraints
        #studio = Studio.Instance
        nodescon = GetPrivateStatic(NodesConstraints, "_self")
        try:
            CallPrivate(nodescon,"SaveSceneGeneric",[xmlTextWriter])
        except:
            CallPrivate(nodescon, "OnSceneSave", ["dummy path", xmlTextWriter])

        xmlTextWriter.WriteEndElement()

        #return stringWriter.ToString()
        settingData = stringWriter.ToString()
        try:
            # load contents of setting data
            xDoc = XmlDocument()
            xDoc.LoadXml(settingData)

            # get the sorted list of all objects in studio
            from Studio import Studio
            studio = Studio.Instance
            silist = sorted(list(studio.dicObjectCtrl.Keys))
            
            # find each setting and adjust
            xRoot = xDoc.FirstChild
            if xRoot.Name != "nodescon":
                print("NodesConstraints Error: unexpected root node, should be 'nodescon'.")
                raise Exception()
            if not xRoot.HasChildNodes or xRoot.FirstChild.Name != "constraints":
                print("NodesConstraints Error: miss container node 'constraints'")
                raise Exception()
            for nc in xRoot.FirstChild.ChildNodes:
                if nc.Name == "constraint":
                    pidx = int(nc.GetAttribute("parentObjectIndex"))
                    cidx = int(nc.GetAttribute("childObjectIndex"))
                    pid = silist[pidx]
                    cid = silist[cidx]
                    #print "constraint setting: %d(%d) -> %d(%d)"%(pidx, pid, cidx, cid)
                    nc.SetAttribute("parentObjectID", str(pid))
                    nc.SetAttribute("childObjectID", str(cid))
                else:
                    print("NodesConstraints Warning: unknown setting node '%s'"%nc.Name)
            # export again
            stringWriter2 = StringWriter()
            xmlTextWriter2 = XmlTextWriter(stringWriter2)
            xDoc.WriteContentTo(xmlTextWriter2)
            settingData = stringWriter2.ToString()
        except:
            pass
        return settingData

    def SetSysSettingsText(self, text):
        import clr
        clr.AddReference("System.Xml")

        from System.IO import StringWriter
        from System.Xml import XmlDocument

        xmlDocument = XmlDocument()
        xmlDocument.LoadXml(text)
        node = xmlDocument.FirstChild

        # re-calcuate object index base on the id
        try:
            # get the sorted list of all objects in studio
            from Studio import Studio
            studio = Studio.Instance
            silist = sorted(list(studio.dicObjectCtrl.Keys))

            if node.Name != "nodescon":
                print("NodesConstraints Error: unexpected root node, should be 'nodescon'.")
                raise Exception()
            if not node.HasChildNodes or node.FirstChild.Name != "constraints":
                print("NodesConstraints Error: miss container node 'constraints'")
                raise Exception()
            for nc in node.FirstChild.ChildNodes:
                if nc.Name == "constraint":
                    pid = int(nc.GetAttribute("parentObjectID"))
                    cid = int(nc.GetAttribute("childObjectID"))
                    pidx = silist.index(pid)
                    cidx = silist.index(cid)
                    #print "constraint setting: %d(%d) -> %d(%d)"%(pidx, pid, cidx, cid)
                    nc.SetAttribute("parentObjectIndex", str(pidx))
                    nc.SetAttribute("childObjectIndex", str(cidx))
                else:
                    print("NodesConstraints Warning: unknown setting node '%s'"%nc.Name)
        except:
            pass

        from NodesConstraints import NodesConstraints
        #studio = Studio.Instance
        nodescon = GetPrivateStatic(NodesConstraints, "_self")
        #print node
        self.ClearConstrains()

        try:
            CallPrivate(nodescon, "LoadSceneGeneric", [node.FirstChild,-1])
        except:
            CallPrivate(nodescon, "OnSceneLoad", ["dummy path", node])

    def ClearConstrains(self):
        from NodesConstraints import NodesConstraints
        #studio = Studio.Instance
        nodescon = GetPrivateStatic(NodesConstraints, "_self")

        constrains = GetPrivate(nodescon,"_constraints")
        for i in range(len(constrains)):
            CallPrivate(nodescon,"RemoveConstraintAt",[0])
        #ocichar.charInfo.gameObject.GetComponent(PoseController).ScheduleLoad(node, None)

    def GetConstrains(self):
        from NodesConstraints import NodesConstraints
        #studio = Studio.Instance
        nodescon = GetPrivateStatic(NodesConstraints, "_self")

        constrains = GetPrivate(nodescon,"_constraints")
        return list(constrains)

    def GetNewToOldKeys(self):
        from NodesConstraints import NodesConstraints
        try:
            nodescon = GetPrivateNestedType(NodesConstraints, "SceneInfo_Import_Patches")

            return GetPrivateStatic(nodescon, "_newToOldKeys")
        except:
            return None

# ::::::::::: ExtensibleSaveFormat (KK Bep5, AI) :::::::::::
class ExtensibleSaveFormat(ExtPlugin):
    def __init__(self):
        if ExtPlugin.exists("AI_ExtensibleSaveFormat"):
            ExtPlugin.__init__(self, "AI_ExtensibleSaveFormat")
        elif ExtPlugin.exists("HS2_ExtensibleSaveFormat"):
            ExtPlugin.__init__(self, "HS2_ExtensibleSaveFormat")
        elif ExtPlugin.exists("ExtensibleSaveFormat"):
            ExtPlugin.__init__(self, "ExtensibleSaveFormat")
        elif ExtPlugin.exists("KKS_ExtensibleSaveFormat"):
            ExtPlugin.__init__(self, "KKS_ExtensibleSaveFormat")
        elif ExtPlugin.exists("PH_ExtensibleSaveFormat"): # no, PH version doesn't suport studio save/load events
            ExtPlugin.__init__(self, "PH_ExtensibleSaveFormat")
        else:
            self.isDetected = False
        if self.isDetected:
            from ExtensibleSaveFormat import PluginData
            self.extSaveData = PluginData()
        else:
            self.extSaveData = None

    def reg_scene_being_loaded(self,callback):
        from ExtensibleSaveFormat import ExtendedSave
        ExtendedSave.SceneBeingLoaded += callback

    def reg_scene_being_saved(self,callback):
        from ExtensibleSaveFormat import ExtendedSave
        ExtendedSave.SceneBeingSaved += callback

    def reg_scene_being_imported(self,callback):
        from ExtensibleSaveFormat import ExtendedSave
        ExtendedSave.SceneBeingImported += callback

    def setExtendSaveData(self, key, data):
        if self.isDetected and self.extSaveData:
            self.extSaveData.data[key] = data

    def getExtendSaveData(self, key):
        if self.isDetected and self.extSaveData and self.extSaveData.data.ContainsKey(key):
            return self.extSaveData.data[key]
        else:
            return None

    def delExtendSaveData(self, key):
        if self.isDetected and self.extSaveData and self.extSaveData.data.ContainsKey(key):
            self.extSaveData.data.Remove(key)

    def newExtendSaveData(self, key):
        if self.isDetected:
            from ExtensibleSaveFormat import PluginData
            self.extSaveData = PluginData()

    def setExtendSaveVersion(self, ver):
        if self.isDetected and self.extSaveData:
            self.extSaveData.version = ver

    def getExtendSaveVersion(self):
        if self.isDetected and self.extSaveData:
            return self.extSaveData.version

    def saveExtendSaveData(self, id):
        from ExtensibleSaveFormat import ExtendedSave
        if self.isDetected:
            ExtendedSave.SetSceneExtendedDataById(id, self.extSaveData)

    def loadExtendSaveData(self, id):
        from ExtensibleSaveFormat import ExtendedSave, PluginData
        if self.isDetected:
            self.extSaveData = ExtendedSave.GetSceneExtendedDataById(id)
            if not self.extSaveData:
                self.extSaveData = PluginData()
            return self.extSaveData
        else:
            return None

class AI_ExtensibleSaveFormat(ExtensibleSaveFormat): # the same ExtensibleSaveFormat
    pass

# ::::::::::: Common class for KK_API, HS2_API etc, :::::::::::
class ILLAPI(ExtPlugin):
    def __init__(self):
        if ExtPlugin.exists("KKAPI"):
            ExtPlugin.__init__(self, "KKAPI")
        elif ExtPlugin.exists("KKSAPI"):
            ExtPlugin.__init__(self, "KKSAPI")
        elif ExtPlugin.exists("HS2API"):
            ExtPlugin.__init__(self, "HS2API")
        elif ExtPlugin.exists("AIAPI"):
            ExtPlugin.__init__(self, "AIAPI")
        elif ExtPlugin.exists("PHAPI"):
            ExtPlugin.__init__(self, "PHAPI")
        else:
            self.isDetected = False

    def Utilities_Extensions(self):
        from KKAPI.Utilities import Extensions
        return Extensions

    def Studio_StudioAPI(self):
        from KKAPI.Studio import StudioAPI
        return StudioAPI

    def Utilities_OpenFileDialog(self, callbackFunc, open = True, initialDir = None, title = None, extFilter = "All files|*.*", defExt = ""):
        from UnityEngine import Application
        from KKAPI.Utilities import OpenFileDialog
        from System import Action, Array, String
        from os import path

        if open:
            if title == None: title = "Open file"
            flags = OpenFileDialog.OpenSaveFileDialgueFlags.OFN_FILEMUSTEXIST | OpenFileDialog.OpenSaveFileDialgueFlags.OFN_EXPLORER | OpenFileDialog.OpenSaveFileDialgueFlags.OFN_LONGNAMES
        else:
            if title == None: title = "Save file"
            flags = OpenFileDialog.OpenSaveFileDialgueFlags.OFN_EXPLORER | OpenFileDialog.OpenSaveFileDialgueFlags.OFN_LONGNAMES
        if initialDir == None or not path.isdir(initialDir):
            initialDir = path.realpath(path.join(Application.dataPath, '..'))

        ca = Action[Array[String]](callbackFunc)
        OpenFileDialog.Show(ca, title, initialDir, extFilter, defExt, flags)

# ::::::::::: ExtensibleSaveFormat (KK Bep5, AI) :::::::::::
class HSExtSave(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "HSExtSave")

    def reg_all_scene(self, id, callback_load, callback_import, callback_save):
        from HSExtSave import HSExtSave
        HSExtSave.RegisterHandler(id, None, None, callback_load,callback_import,callback_save,None,None)

# ::::::::::: Timeline (KK Bep5) :::::::::::
class ExtTimeline(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "Timeline")
        self.timeline = None
        if self.isDetected:
            from Timeline import Timeline
            self.timeline = Timeline
            try:
                self.timeline_obj = GetPrivateStatic(self.timeline,"_self")
            except Exception as e:
                print("VNGE: Error getting Timeline internal obj")

    def Play(self):
        self.timeline.Play()

    def Pause(self):
        self.timeline.Pause()

    def Stop(self):
        self.timeline.Pause()

    def GetDuration(self):
        return self.timeline.duration

    def GetIsPlaying(self):
        return self.timeline.isPlaying

    def GetPlaybackTime(self):
        return self.timeline.playbackTime

    def SeekPlaybackTime(self,time):
        from System import Single
        CallPrivate(self.timeline_obj,"SeekPlaybackTime",[Single(time)])

    def SceneContainsInterpolables(self):
        tree = GetPrivate(self.timeline_obj,"_interpolables")
        return len(tree) > 0

    def GetSysSettingsText(self):
        import clr
        clr.AddReference("System.Xml")

        from System.IO import StringWriter
        from System.Xml import XmlTextWriter, XmlDocument

        stringWriter = StringWriter()
        xmlTextWriter = XmlTextWriter(stringWriter)

        xmlTextWriter.WriteStartElement("root")
        try:
            CallPrivate(self.timeline_obj, "SceneWrite", ["dummy path", xmlTextWriter])
        except:
            traceback.print_exc()
        xmlTextWriter.WriteEndElement()

        settingData = stringWriter.ToString()
        
        # convert objectIndex to objectID
        try:
            # load contents of setting data
            xDoc = XmlDocument()
            xDoc.LoadXml(settingData)

            # get the sorted list of all objects in studio
            from Studio import Studio
            studio = Studio.Instance
            silist = sorted(list(studio.dicObjectCtrl.Keys))
            
            # find each setting and adjust
            xRoot = xDoc.FirstChild
            if xRoot.Name != "root":
                print("Timeline Error: unexpected root node, should be 'root'.")
                raise Exception()
            for iobj in xRoot.ChildNodes:
                if iobj.Name == "interpolable":
                    oidx = int(iobj.GetAttribute("objectIndex"))
                    oid = silist[oidx]
                    iobj.SetAttribute("objectID", str(oid))
                else:
                    print("Timeline Warning: unknown setting node '%s'"%iobj.Name)
            # export again
            stringWriter2 = StringWriter()
            xmlTextWriter2 = XmlTextWriter(stringWriter2)
            xDoc.WriteContentTo(xmlTextWriter2)
            settingData = stringWriter2.ToString()
        except:
            pass
        
        return settingData

    def SetSysSettingsText(self, text):
        import clr
        clr.AddReference("System.Xml")

        from System.Xml import XmlDocument

        xmlDocument = XmlDocument()
        xmlDocument.LoadXml(text)
        node = xmlDocument.FirstChild

        try:
            CallPrivate(self.timeline_obj, "SceneLoad", ["dummy path", node])
        except:
            traceback.print_exc()

# ::::::::::: ABMX (AI,HS2) :::::::::::
class ABMX(ExtPlugin):
    def __init__(self):
        if ExtPlugin.exists("KKABMX"):
            ExtPlugin.__init__(self, "KKABMX")
            self.engine = "KK"
        elif ExtPlugin.exists("KKSABMX"):
            ExtPlugin.__init__(self, "KKSABMX")
            self.engine = "KKS"
        elif ExtPlugin.exists("AIABMX"):
            ExtPlugin.__init__(self, "AIABMX")
            self.engine = "AI"
        elif ExtPlugin.exists("HS2ABMX"):
            ExtPlugin.__init__(self, "HS2ABMX")
            self.engine = "HS2"
        elif ExtPlugin.exists("PHABMX"):
            ExtPlugin.__init__(self, "PHABMX")
            self.engine = "PH"
        else:
            self.isDetected = False
            self.engine = None

    def GetBoneController(self, char):
        if not self.isDetected:
            return None

        from Studio import OCIChar
        if isinstance(char, OCIChar):
            ociChar = char
        elif isinstance(char, HSNeoOCIChar):
            ociChar = char.objctrl
        else:
            return None
        
        from KKABMX.Core import BoneController
        bc = ociChar.charInfo.gameObject.GetComponent(BoneController)
        return bc

    def GetBoneData(self, char):
        bc = self.GetBoneController(char)
        if not bc:
            return {}
        coord = bc.CurrentCoordinate.Value
        
        bd = {}
        for m in bc.Modifiers:
            if m.IsEmpty():
                continue
            bm0 = m.GetModifier(coord)
            bd[m.BoneName] = (bm0.ScaleModifier, bm0.LengthModifier, bm0.PositionModifier, bm0.RotationModifier)
        return bd

    def SetBoneData(self, char, data):
        if not data or len(data) == 0:
            return 
        bc = self.GetBoneController(char)
        if not bc:
            return
        coord = bc.CurrentCoordinate.Value

        from vnactor import tuple3_2_vector3
        for boneName in data.keys():
            m = bc.GetModifier(boneName)
            if not m:
                try:
                    from KKABMX.Core import BoneModifier
                    m = BoneModifier(boneName)
                    bc.AddModifier(m)
                    if not m.BoneTransform:
                        raise Exception("Transform for the bone not found!")
                    print ("Bone modifier [%s] not existed, created a new one."%boneName)
                except Exception as e:
                    print ("Bone modifier [%s] not existed, and fail to create it: %s"%(boneName, str(e)))
                    continue
            bm0 = m.GetModifier(coord)
            bm0.ScaleModifier = tuple3_2_vector3(data[boneName][0])
            bm0.LengthModifier = data[boneName][1]
            bm0.PositionModifier = tuple3_2_vector3(data[boneName][2])
            bm0.RotationModifier = tuple3_2_vector3(data[boneName][3])

# ::::::::::: Unity VideoPlayer support for item :::::::::::
class VideoPlayerExt(ExtPlugin):
    def __init__(self, ociObj = None, linkedClip = None):
        ExtPlugin.__init__(self, "UnityEngine.VideoModule")
        self.BindObject(ociObj, linkedClip)

    def BindObject(self, ociObj, linkedClip):
        self.videoPlayer = self.GetVideoPlayerComponent(ociObj)
        self.linkedClip = linkedClip

    @property
    def hasVideoPlayer(self):
        return self.videoPlayer != None

    @property
    def videoPlayerPrepared(self):
        return self.hasVideoPlayer and self.videoPlayer.isPrepared

    @property
    def videoLength(self):
        if self.videoPlayerPrepared:
            if hasattr(self.videoPlayer, "length"):
                return self.videoPlayer.length
            else:
                return self.videoPlayer.frameCount / self.videoPlayer.frameRate
        else:
            return 0

    def GetVideoPlayerComponent(self, ociObj):
        if ociObj == None: return None
        try:
            from UnityEngine.Video import VideoPlayer
            if hasattr(ociObj, "objctrl"):
                ociObj = ociObj.objctrl
            vp = ociObj.objectItem.GetComponentInChildren[VideoPlayer]()
            return vp
        except:
            return None

    def GetVolume(self):
        if self.videoPlayer != None and self.videoPlayer.canSetDirectAudioVolume:
            return self.videoPlayer.GetDirectAudioVolume(0)
        else:
            return 0

    def SetVolume(self, volume):
        if self.videoPlayer != None and self.videoPlayer.canSetDirectAudioVolume:
            self.videoPlayer.SetDirectAudioVolume(0, volume)

    def PlayVideo(self):
        if self.videoPlayer != None and not self.videoPlayer.isPlaying:
            self.videoPlayer.Play()

    def PauseVideo(self):
        if self.videoPlayer != None and self.videoPlayer.isPlaying:
            self.videoPlayer.Pause()

    def SeekVideo(self, time):
        if self.videoPlayer != None and self.videoPlayer.canSetTime and 0 <= time < self.videoLength:
            self.videoPlayer.time = time

    def ExportVideoPlayerState(self):
        if self.hasVideoPlayer:
            state = {}
            state["playing"] = "1" if self.videoPlayer.isPlaying else "0"
            state["time"] = self.videoPlayer.time
            state["autosync"] = 1
            state["volume"] = self.GetVolume()
            return state
        else:
            return None

    def ImportVideoPlayerState(self, state):
        if self.videoPlayerPrepared and state != None:
            if self.linkedClip and "autosync" in state and state["autosync"]:
                # auto sync mode overwrite time and playing
                ctime = self.linkedClip.curTime
                if not self.videoPlayer.isPlaying and self.linkedClip.isPlaying:
                    # sync start
                    if 0 <= ctime < self.videoLength:
                        self.SeekVideo(ctime)
                        self.videoPlayer.Play()
                        print("sync play video")
                elif self.videoPlayer.isPlaying and not self.linkedClip.isPlaying:
                    # sync pause
                    self.videoPlayer.Pause()
                    self.SeekVideo(ctime)
                    print("sync pause video")
                elif not self.videoPlayer.isPlaying and not self.linkedClip.isPlaying:
                    # sync frame
                    self.SeekVideo(ctime)
                    #print("sync video time %s"%ctime)
                else:
                    # do nothing
                    pass
            else:
                # time (paused only)
                if "time" in state and self.videoPlayer.canSetTime and not self.videoPlayer.isPlaying and 0 <= state["time"] <= self.videoLength:
                    self.videoPlayer.time = state["time"]
                # playing
                if "playing" in state:
                    if state["playing"] == "1" and not self.videoPlayer.isPlaying:
                        self.videoPlayer.Play()
                    elif state["playing"] != "1" and self.videoPlayer.isPlaying:
                        self.videoPlayer.Pause()
            # volume
            if "volume" in state and 0 <= state["volume"] <= 1:
                self.SetVolume(state["volume"])

# ::::::::::: CharaEditor :::::::::::
class CharaEditor(ExtPlugin):
    APPEND_PLUGIN = {"ABMX", "Overlay"}

    def __init__(self):
        ExtPlugin.__init__(self, "HS2StudioCharaEditor")
        if self.isDetected:
            from StudioCharaEditor import CharaEditorMgr
            self.Manager = CharaEditorMgr.Instance

    def GetCharaEditorContorller(self, ociChar):
        if self.isDetected:
            ctrl = self.Manager.GetEditorController(ociChar)
            return ctrl
        else:
            return None

    def GetCharaEditorDataFull(self, ociChar):
        if self.isDetected:
            ctrl = self.Manager.GetEditorController(ociChar)
            fullData = ctrl.GetDataDictFull()
            return CharaEditor.toPythonData(fullData)
        else:
            return None

    def GetCharaEditorDataRawFiltered(self, ociChar, category, exclude):
        from System import Array, String
        if self.isDetected:
            ctrl = self.Manager.GetEditorController(ociChar)
            # filter
            tgtKeys = []
            for key in ctrl.myUpdateSequence:
                need = False
                # check category
                for cat in category:
                    if key.startswith(cat):
                        need = True
                        break
                if not need:
                    continue
                # check exclude
                for ext in exclude:
                    if ext in key:
                        need = False
                        break
                if not need:
                    continue
                # add to tgt
                tgtKeys.append(key)
            # filtered data
            if len(tgtKeys) == 0:
                return {}
            filtedData = ctrl.GetDataDictByKeys(Array[String](tgtKeys))
            return filtedData
        else:
            return None

    def GetCharaEditorDataConDisFiltered(self, ociChar, category, exclude):
        from System import Array, String
        if self.isDetected:
            ctrl = self.Manager.GetEditorController(ociChar)
            filtedData = self.GetCharaEditorDataRawFiltered(ociChar, category, exclude)
            continuousData = ctrl.GetContinuousDataSet(filtedData)
            continuousData = CharaEditor.toPythonData(continuousData)
            discreteData = ctrl.GetDiscreteDataSet(filtedData)
            discreteData = CharaEditor.toPythonData(discreteData)
            # convert id data in safe way
            sideloader = Sideloader()
            for key in discreteData.keys():
                discreteData[key] = sideloader.UniGetUARResolveData(discreteData[key])
            return continuousData, discreteData
        else:
            return None, None
        
    def GetCharaEditorDataConDis(self, ociChar):
        if self.isDetected:
            ctrl = self.Manager.GetEditorController(ociChar)
            fullData = ctrl.GetDataDictVanilla()
            continuousData = ctrl.GetContinuousDataSet(fullData)
            continuousData = CharaEditor.toPythonData(continuousData)
            discreteData = ctrl.GetDiscreteDataSet(fullData)
            discreteData = CharaEditor.toPythonData(discreteData)
            # convert id data in safe way
            sideloader = Sideloader()
            for key in discreteData.keys():
                discreteData[key] = sideloader.UniGetUARResolveData(discreteData[key])
            return continuousData, discreteData
        else:
            return None, None

    def GetCharaEditorDataABMX(self, ociChar):
        if self.isDetected:
            ctrl = self.Manager.GetEditorController(ociChar)
            abmxData = ctrl.GetDataDictABMX()
            return CharaEditor.toPythonData(abmxData)
        else:
            return None

    def SetCharaEditorDataRaw(self, ociChar, data, force=False):
        if self.isDetected:
            # set to CharaEditor
            ctrl = self.Manager.GetEditorController(ociChar)
            ctrl.SetDataDict(data, force)

    def SetCharaEditorData(self, ociChar, data, force=False):
        if self.isDetected:
            # copy and resolve UARResolveData data
            copyData = {}
            sideloader = Sideloader()
            for ckey in data.keys():
                if isinstance(data[ckey], list) and isinstance(data[ckey][0], str):
                    localId = sideloader.UniGetUARLocalSlotId(data[ckey])
                    #print "Found UARResolveData", data[ckey], "=>", localId
                    if localId != None:
                        copyData[ckey] = localId
                else:
                    copyData[ckey] = data[ckey]
            # set to CharaEditor
            ctrl = self.Manager.GetEditorController(ociChar)
            cData = CharaEditor.toCSharpData(copyData)
            ctrl.SetDataDict(cData, force)

    @staticmethod
    def toPythonData(orgData):
        from System.Collections.Generic import Dictionary
        from System import Single, Array, Convert
        from UnityEngine import Color, Texture2D
        if isinstance(orgData, Dictionary[str, object]):
            pData = dict(orgData)
            for key in pData.keys():
                pData[key] = CharaEditor.toPythonData(pData[key])
            return pData
        elif isinstance(orgData, Dictionary[int, Array[Single]]):
            pData = dict(orgData)
            for key in pData.keys():
                pData[key] = CharaEditor.toPythonData(pData[key])
            return pData
        elif isinstance(orgData, Single):
            return float(orgData)
        elif isinstance(orgData, Color):
            return (orgData.r, orgData.g, orgData.b, orgData.a)
        elif isinstance(orgData, Array):
            pData = list(orgData)
            for i in range(len(pData)):
                pData[i] = CharaEditor.toPythonData(pData[i])
            return pData
        elif isinstance(orgData, Texture2D):
            pData = ("Texture2D", orgData.width, orgData.height, int(orgData.format), 1 if orgData.mipmapCount > 1 else 0, Convert.ToBase64String(orgData.GetRawTextureData()))
            return pData
        else:
            return orgData

    @staticmethod
    def toCSharpData(orgData):
        from System.Collections.Generic import Dictionary
        from System import Single, Array, Convert
        from UnityEngine import Color, Texture2D, TextureFormat
        if isinstance(orgData, dict):
            key0 = None if len(orgData) == 0 else orgData.keys()[0]
            if key0 == None:
                cData = None
            elif isinstance(key0, str):
                cData = Dictionary[str, object]()
                for key in orgData.keys():
                    cData[key] = CharaEditor.toCSharpData(orgData[key])
            elif isinstance(key0, int):
                cData = Dictionary[int, Array[Single]]()
                for key in orgData.keys():
                    cData[key] = CharaEditor.toCSharpData(orgData[key])
            else:
                raise Exception("Unexpected CharaEditor Data")
            return cData
        elif isinstance(orgData, list):
            cData = []
            for subData in orgData:
                cData.append(CharaEditor.toCSharpData(subData))
            return Array[type(cData[0])](cData)
        elif isinstance(orgData, tuple) and len(orgData) == 4:
            return Color(orgData[0], orgData[1], orgData[2], orgData[3])
        elif isinstance(orgData, tuple) and len(orgData) == 6 and orgData[0] == "Texture2D":
            from vnactor import value_2_enum
            tex = Texture2D(orgData[1], orgData[2], value_2_enum(TextureFormat, orgData[3]), bool(orgData[4]))
            tex.LoadRawTextureData(Convert.FromBase64String(orgData[5]))
            tex.Apply()
            return tex
        elif isinstance(orgData, float):
            return Single(orgData)
        else:
            return orgData

# ::::::::::: BoobSetting :::::::::::
class BoobSettings(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "HS2_BoobSettings")

    def GetBoobContorller(self, ociChar):
        if self.isDetected:
            from HS2_BoobSettings import BoobController
            try :
                ctrl = ociChar.charInfo.GetComponent[BoobController]()
                return ctrl
            except:
                import traceback
                traceback.print_exc()
                return None
        else:
            return None

    def GetFloatData(self, ociChar):
        bc = self.GetBoobContorller(ociChar)
        if bc:
            pdata = {}
            for key in bc.floatData.Keys:
                pdata[key] = float(bc.floatData[key])
            return pdata
        else:
            return None

    def GetBoolData(self, ociChar):
        bc = self.GetBoobContorller(ociChar)
        if bc:
            pdata = {}
            for key in bc.boolData.Keys:
                pdata[key] = bc.boolData[key]
            return pdata
        else:
            return None

    def SetFloatData(self, ociChar, pData):
        from System import Single
        bc = self.GetBoobContorller(ociChar)
        if bc:
            for key in pData.keys():
                bc.floatData[key] = Single(pData[key])
            ociChar.charInfo.UpdateBustSoftness()
            ociChar.charInfo.ChangeBustInert(False)
            ociChar.charInfo.UpdateBustGravity()

    def SetBoolData(self, ociChar, pData):
        bc = self.GetBoobContorller(ociChar)
        if bc:
            for key in pData.keys():
                bc.boolData[key] = pData[key]
            ociChar.charInfo.UpdateBustSoftness()
            ociChar.charInfo.ChangeBustInert(False)
            ociChar.charInfo.UpdateBustGravity()

# ::::::::::: BepInEx :::::::::::
class PLBepInEx(ExtPlugin):
    def __init__(self):
        if ExtPlugin.exists("BepInEx"):
            ExtPlugin.__init__(self, "BepInEx")
        else:
            self.isDetected = False

    def create_log_source(self,name):
        from BepInEx.Logging import Logger, LogLevel
        return Logger.CreateLogSource(name)

    def list_loaded_plugins(self):
        from BepInEx import MetadataHelper, BaseUnityPlugin, BepInPlugin
        from BepInEx.Bootstrap import Chainloader
        output = []
        for plugin in Chainloader.Plugins:
            metadata = MetadataHelper.GetMetadata(plugin)
            output.append((metadata.Name, metadata.GUID, metadata.Version))
        return tuple(output)

    def search_plguin(self, tgtname):
        tgtname = tgtname.lower()
        found = []
        for name, guid, ver in self.list_loaded_plugins():
            if tgtname in name.lower() or tgtname in guid.lower():
                found.append((name, guid, ver))
                print("Plugin: %s, GUID: %s, Version: %s"%(name, guid, str(ver)))
        if len(found) == 0:
            print("No plugin found with keyword <%s>"%tgtname)
        return found

    def check_plugin(self, name=None, guid=None, version=None):
        from System import Version
        if name == None and guid == None:
            raise Exception("PLBepInEx.check_plugin need name or guid or both of them to check!")
        if version != None:
            ver = Version(version)
        else:
            ver = None
        pluginInfos = self.list_loaded_plugins()
        for (pname, pguid, pversion) in pluginInfos:
            if name != None and pname != name:
                continue
            if guid != None and pguid != guid:
                continue
            if ver != None and pversion < ver:
                continue
            return True
        return False

# ::::::::::: Sideloader :::::::::::
class Sideloader(ExtPlugin):
    UnknownLocalSlotId = set()

    def __init__(self):
        if ExtPlugin.exists("Sideloader"):
            ExtPlugin.__init__(self, "Sideloader")
            self.engine = "KK"
        elif ExtPlugin.exists("KKS_Sideloader"):
            ExtPlugin.__init__(self, "KKS_Sideloader")
            self.engine = "KKS"
        elif ExtPlugin.exists("AI_Sideloader"):
            ExtPlugin.__init__(self, "AI_Sideloader")
            self.engine = "AI"
        elif ExtPlugin.exists("HS2_Sideloader"):
            ExtPlugin.__init__(self, "HS2_Sideloader")
            self.engine = "HS2"
        else:
            self.isDetected = False
            self.engine = None

    def isSideloaderId(self, localSlotId):
        if isinstance(localSlotId, int) and self.isDetected and localSlotId not in Sideloader.UnknownLocalSlotId:
            from Sideloader.AutoResolver import UniversalAutoResolver
            return localSlotId >= UniversalAutoResolver.BaseSlotID
        else:
            return False

    def UniGetUARResolveData(self, localSlotId):
        if self.isSideloaderId(localSlotId):
            from Sideloader.AutoResolver import UniversalAutoResolver
            for ri in UniversalAutoResolver.LoadedStudioResolutionInfo:
                if ri.LocalSlot == localSlotId:
                    # version 1.0: version of resolve algorithm, GUID, Slot, Category
                    #return ["1.0", ri.GUID, ri.Slot, ri.Category]
                    # version 2.0: version of resolve algorithm, GUID, Group, Category, Slot
                    return ["2.0", ri.GUID, ri.Group, ri.Category, ri.Slot]
            for ri in UniversalAutoResolver.LoadedResolutionInfo:
                if ri.LocalSlot == localSlotId:
                    # version 1.0: version of resolve algorithm, GUID, Slot, Category
                    return ["1.0", ri.GUID, ri.Slot, int(ri.CategoryNo)]
            print("VNGE Sideloader, error find localSlotId=" + str(localSlotId))
            Sideloader.UnknownLocalSlotId.add(localSlotId)
            return localSlotId
        else:
            return localSlotId

    def UniGetUARLocalSlotId(self, resolveData):
        if self.isDetected:
            if isinstance(resolveData, (int, long)):
                # no data
                return resolveData

            # if resolveData[0] == "0.9": # resolve for 0.9 version of algo - if Slot and GUID enough
            #     from Sideloader.AutoResolver import UniversalAutoResolver
            #     for ri in UniversalAutoResolver.LoadedStudioResolutionInfo:
            #         if ri.GUID == resolveData[1] and ri.Slot == resolveData[2]:
            #             return ri.LocalSlot

            if resolveData[0] == "1.0": # resolve for 1.0 version of algo
                from Sideloader.AutoResolver import UniversalAutoResolver
                for ri in UniversalAutoResolver.LoadedStudioResolutionInfo:
                    if ri.GUID == resolveData[1] and ri.Slot == resolveData[2] and ri.Category == resolveData[3]:
                        return ri.LocalSlot
                for ri in UniversalAutoResolver.LoadedResolutionInfo:
                    if ri.GUID == resolveData[1] and ri.Slot == resolveData[2] and int(ri.CategoryNo) == resolveData[3]:
                        return ri.LocalSlot
            if resolveData[0] == "2.0":
                from Sideloader.AutoResolver import UniversalAutoResolver
                for ri in UniversalAutoResolver.LoadedStudioResolutionInfo:
                    if ri.GUID == resolveData[1] and ri.Group == resolveData[2] and ri.Category == resolveData[3] and ri.Slot == resolveData[4]:
                        return ri.LocalSlot
                for ri in UniversalAutoResolver.LoadedResolutionInfo:
                    if ri.GUID == resolveData[1] and ri.Group == resolveData[2] and int(ri.CategoryNo) == resolveData[3] and ri.Slot == resolveData[4]:
                        return ri.LocalSlot                
        else:
            return resolveData

# ::::::::::: Overlay :::::::::::
class Overlay(ExtPlugin):
    def __init__(self, actor = None):
        if ExtPlugin.exists("HS2_OverlayMods"):
            ExtPlugin.__init__(self, "HS2_OverlayMods")
            self.engine = "HS2"
        else:
            self.isDetected = False
            self.engine = None
        self.BindActor(actor)

    def BindActor(self, actor):
        # actor can be a Actor, OCIChar or ChaControl
        self.targetChaCtrl = None
        self.skinOverlayCtrl = None
        self.clothOverlayCtrl = None
        self.MainClothesNames = None
        if actor == None:
            return
        if self.engine in {"HS2", "AI"}:
            from AIChara import ChaControl
        else:
            return
        from vnactor import Actor
        from KoiSkinOverlayX import KoiSkinOverlayController
        from KoiClothesOverlayX import KoiClothesOverlayController, KoiClothesOverlayMgr
        if isinstance(actor, ChaControl):
            self.targetChaCtrl = actor
        elif isinstance(actor, Actor):
            self.targetChaCtrl = actor.objctrl.charInfo
        else:
            self.targetChaCtrl = actor.charInfo
        self.skinOverlayCtrl = self.targetChaCtrl.gameObject.GetComponentInChildren[KoiSkinOverlayController]()
        self.clothOverlayCtrl = self.targetChaCtrl.gameObject.GetComponentInChildren[KoiClothesOverlayController]()
        self.MainClothesNames = KoiClothesOverlayMgr.MainClothesNames

    def GetSkinTexType(self, name):
        from KoiSkinOverlayX import TexType
        if name == "BodyOver":
            return TexType.BodyOver
        elif name == "FaceOver":
            return TexType.FaceOver
        elif name == "BodyUnder":
            return TexType.BodyUnder
        elif name == "FaceUnder":
            return TexType.FaceUnder
        else:
            raise Exception("Unknown skin TexType name <%s>"%name)

    def GetSkinTexture(self, texName):
        if self.skinOverlayCtrl == None:
            return None
        texType = self.GetSkinTexType(texName)
        return self.skinOverlayCtrl.OverlayStorage.GetTexture(texType)

    def GetClothesTexData(self, clothPartsIndex):
        if self.clothOverlayCtrl == None:
            return None
        curTexDict = GetPrivateProperty(self.clothOverlayCtrl, "CurrentOverlayTextures")
        clothName = self.MainClothesNames[clothPartsIndex]
        if clothName in curTexDict:
            return curTexDict[clothName]
        else:
            return None

    def refreshClothesTexture(self, clothPartsIndex):
        clothName = self.MainClothesNames[clothPartsIndex]
        self.clothOverlayCtrl.RefreshTexture(clothName)

    def refreshClothesAllTextrue(self):
        self.clothOverlayCtrl.RefreshAllTextures()

    def DumpClothOrignalTexture(self, clothPartsIndex):
        from System import Action, Array, Byte
        from KoiSkinOverlayX import KoiSkinOverlayGui
        clothName = self.MainClothesNames[clothPartsIndex]
        def WriteAndOpenPng(pngData):
            KoiSkinOverlayGui.WriteAndOpenPng(pngData, clothName + "_Original")
        ca = Action[Array[Byte]](WriteAndOpenPng)
        self.clothOverlayCtrl.DumpBaseTexture(clothName, ca)

    def DumpClothOverlayTexture(self, clothPartsIndex):
        from KoiSkinOverlayX import KoiSkinOverlayGui
        from vngameengine import vnge_game
        clothName = self.MainClothesNames[clothPartsIndex]
        try:
            tex = self.clothOverlayCtrl.GetOverlayTex(clothName, False)
            if tex == None or tex.TextureBytes == None:
                vnge_game.logger_log_message("Nothing to export")
                return

            KoiSkinOverlayGui.WriteAndOpenPng(tex.TextureBytes, clothName)
        except Exception as e:
            vnge_game.logger_log_message("Failed to export texture - " + str(e))

    def DumpSkinOverlayTexture(self, texName):
        from KoiSkinOverlayX import KoiSkinOverlayGui
        from vngameengine import vnge_game
        try:
            tex = self.GetSkinTexture(texName)
            if tex == None:
                vnge_game.logger_log_message("Nothing to export")
                return
            #texCopy = tex.ToTexture2D()
            from UnityEngine import TextureFormat, ImageConversion, Texture2D
            pngBytes = ImageConversion.EncodeToPNG(tex)
            KoiSkinOverlayGui.WriteAndOpenPng(pngBytes, texName)
        except Exception as e:
            vnge_game.logger_log_message("Failed to export texture - " + str(e))

    def LoadNewOverlayTexture(self, partsIndex):
        # open file dialog to load
        from KoiSkinOverlayX import KoiSkinOverlayGui
        def callbackWrapper_api(files):
            if files and len(files):
                from vngameengine import vnge_game
                vnge_game.updFunc = Overlay._LoadNewOverlayTextureFileWrapper
                vnge_game.updFuncParam = (self, partsIndex, files[0])
        illApi = ILLAPI()
        illApi.Utilities_OpenFileDialog(
            callbackWrapper_api, 
            True, 
            KoiSkinOverlayGui.GetDefaultLoadDir(), 
            "Open overlay image", 
            KoiSkinOverlayGui.FileFilter, 
            KoiSkinOverlayGui.FileExt)

    @staticmethod
    def _LoadNewOverlayTextureFileWrapper(param):
        # wrapper
        param[0].LoadNewOverlayTextureFile(param[1], param[2])

    def LoadNewOverlayTextureFile(self, partsIndex, filename):
        # load from a file, must be called in update
        from vngameengine import vnge_game
        if not os.path.isfile(filename):
            vnge_game.logger_log_message("File <%s> not existed."%filename)
            return
        try:
            from System.IO import File
            from UnityEngine import TextureFormat, ImageConversion, Texture2D
            from KoiSkinOverlayX import Util
            pngBytes = File.ReadAllBytes(filename)
            if isinstance(partsIndex, str):
                # skin overlay
                self.SkinSetTexAndUpdate(partsIndex, pngBytes)
            elif isinstance(partsIndex, int):
                # cloth overlay
                textureFormat = TextureFormat.ARGB32
                tex = Util.TextureFromBytes(pngBytes, textureFormat)
                #tex = Texture2D(2, 2)
                #ImageConversion.LoadImage(tex, pngBytes)
                self.ClothSetTexAndUpdate(partsIndex, tex)
        except Exception as e:
            traceback.print_exc()
            vnge_game.logger_log_message("Load from file failed: %s."%str(e))

    def SkinSetTexAndUpdate(self, texName, texBytes):
        # note, texBytes is PNG bytes data, or None
        from vngameengine import vnge_game
        try:
            if self.skinOverlayCtrl:
                texType = self.GetSkinTexType(texName)
                self.skinOverlayCtrl.SetOverlayTex(texBytes, texType)
        except Exception as e:
            traceback.print_exc()
            vnge_game.logger_log_message("Import texture failed: " + str(e))

    def ClothSetTexAndUpdate(self, clothPartsIndex, tex):
        # note, tex is Texture2D object, or None
        from vngameengine import vnge_game
        try:
            # load a textrue2D data
            clothName = self.MainClothesNames[clothPartsIndex]
            if tex != None:
                origTex = tuple(self.clothOverlayCtrl.GetApplicableRenderers(clothName))[0].material.mainTexture
                isWrongRes = origTex != None and (tex.width != origTex.width or tex.height != origTex.height)
                if isWrongRes:
                    vnge_game.logger_log_message("WARNING - Wrong texture resolution! It's recommended to use %dx%d instead."%(origTex.width, origTex.height))
                else:
                    vnge_game.logger_log_message("Texture imported successfully")
            t = self.clothOverlayCtrl.GetOverlayTex(clothName, True)
            t.Texture = tex
            self.clothOverlayCtrl.RefreshTexture(clothName)
        except Exception as e:
            traceback.print_exc()
            vnge_game.logger_log_message("Import texture failed: " + str(e))

# ::::::::::: MoreAccessories :::::::::::
class MoreAccessories(ExtPlugin):
    def __init__(self):
        self.isDetected = False
        self.mgrInstance = None
        self.engine = None
        if ExtPlugin.exists("MoreAccessories"):
            ExtPlugin.__init__(self, "MoreAccessories")
            self.isDetected = True
            # get MoreAccessories instance
            try:
                engid = get_engine_id()
                if engid == "charastudio":
                    from MoreAccessoriesKOI import MoreAccessories
                    self.mgrInstance = GetPrivateStatic(MoreAccessories, "_self")
                    self.engine = "KK"
                elif engid == "neov2":
                    from MoreAccessoriesAI import MoreAccessories
                    self.mgrInstance = GetPrivateStatic(MoreAccessories, "_self")
                    self.engine = "AI/HS2"
            except:
                print ("Fail to initialize MoreAccessories")

    def _toChaCtrl(self, ociChar):
        try:
            from vnactor import Actor
            from Studio import OCIChar
            if isinstance(ociChar, Actor):
                chaCtrl = ociChar.objctrl.charInfo
            elif isinstance(ociChar, OCIChar):
                chaCtrl = ociChar.charInfo
            else:
                chaCtrl = ociChar
            return chaCtrl
        except:
            raise Exception("Input must be a Actor, OCIChar, or ChaCtrl object")

    def getAdditionalAccessoriesData(self, ociChar):
        if not self.isDetected:
            return None
        chaFile = self._toChaCtrl(ociChar).chaFile
        # TODO: check different data type in KK/KKS and AI/HS2
        if self.engine == "KK" and self.mgrInstance:
            accessoriesByChar = GetPrivate(self.mgrInstance, "_accessoriesByChar")
            if chaFile in accessoriesByChar:
                return accessoriesByChar[chaFile]
        elif self.engine == "AI/HS2" and self.mgrInstance:
            additionalData = CallPrivate(self.mgrInstance, "GetAdditionalDataByCharacter", [chaFile])
            return additionalData
        return None

    def getAccessoryCount(self, ociChar):
        if not self.isDetected:
            return 20
        additionData = self.getAdditionalAccessoriesData(ociChar)
        if not additionData:
            return 20
        if self.engine == "KK":
            return 20 + len(additionData.showAccessories)
        elif self.engine == "AI/HS2":
            return 20 + len(additionData.objects)

    def addAccessorySlot(self, ociChar, numToAdd = 1):
        raise Exception("I cannot create MoreAccessories.AdditionalData.AccessoryObject!! T.T")
        
        additionData = self.getAdditionalAccessoriesData(ociChar)
        if not additionData:
            return
        if self.engine == "AI/HS2":
            from AIChara import ChaFileAccessory
            pi = ChaFileAccessory.PartsInfo()
            pi.MemberInit()
            pi.type = 350
            additionData.parts.Add(pi)


    def getAccessoryCmp(self, ociChar, slotNo):
        if slotNo < 20:
            return self._toChaCtrl(ociChar).cmpAccessory[slotNo]
        if self.isDetected:
            additionData = self.getAdditionalAccessoriesData(ociChar)
            if additionData:
                if self.engine == "KK":
                    pass
                elif self.engine == "AI/HS2" and slotNo - 20 < len(additionData.objects):
                    return additionData.objects[slotNo - 20].cmp
        return None

    def getAccessoryInfo(self, ociChar, slotNo):
        if slotNo < 20:
            return self._toChaCtrl(ociChar).infoAccessory[slotNo]
        if self.isDetected:
            additionData = self.getAdditionalAccessoriesData(ociChar)
            if additionData:
                if self.engine == "KK":
                    pass
                elif self.engine == "AI/HS2" and slotNo - 20 < len(additionData.objects):
                    return additionData.objects[slotNo - 20].info
        return None

    def getAccessoryPartsInfo(self, ociChar, slotNo):
        if slotNo < 20:
            return self._toChaCtrl(ociChar).nowCoordinate.accessory.parts[slotNo]
        if self.isDetected:
            additionData = self.getAdditionalAccessoriesData(ociChar)
            if additionData:
                if self.engine == "KK":
                    pass
                elif self.engine == "AI/HS2" and slotNo - 20 < len(additionData.objects):
                    return additionData.parts[slotNo - 20]
        return None

    def getAccessoryVisiable(self, ociChar, slotNo):
        if slotNo < 20:
            return self._toChaCtrl(ociChar).fileStatus.showAccessory[slotNo]
        if self.isDetected:
            additionData = self.getAdditionalAccessoriesData(ociChar)
            if additionData:
                if self.engine == "KK":
                    pass
                elif self.engine == "AI/HS2" and slotNo - 20 < len(additionData.objects):
                    return additionData.objects[slotNo - 20].show
        return None

    def setAccessoryVisiable(self, ociChar, slotNo, visiable):
        if slotNo < 20:
            self._toChaCtrl(ociChar).fileStatus.showAccessory[slotNo] = visiable
        elif self.isDetected:
            additionData = self.getAdditionalAccessoriesData(ociChar)
            if additionData:
                if self.engine == "KK":
                    pass
                elif self.engine == "AI/HS2" and slotNo - 20 < len(additionData.objects):
                    additionData.objects[slotNo - 20].show = visiable

    def getAdditionalAccessoriesVisiable(self, ociChar):
        if not self.isDetected:
            return None
        from vnactor import Actor
        if isinstance(ociChar, Actor):
            ociChar = ociChar.objctrl
        if self.engine == "KK" and self.mgrInstance:
            chaFileControl = ociChar.charInfo.chaFile
            accessoriesByChar = GetPrivate(self.mgrInstance, "_accessoriesByChar")
            if chaFileControl in accessoriesByChar:
                charAdditionalData = accessoriesByChar[chaFileControl]
                return list(charAdditionalData.showAccessories)
        elif self.engine == "AI/HS2" and self.mgrInstance:
            chaFileControl = ociChar.charInfo.chaFile
            additionalData = CallPrivate(self.mgrInstance, "GetAdditionalDataByCharacter", [chaFileControl])
            aav = []
            for adObject in additionalData.objects:
                if adObject.obj:
                    aav.append(adObject.show)
                else:
                    aav.append(None)
            return aav
        return None

    def setAdditionalAccessoriesVisiable(self, ociChar, visibleSet):
        if not self.isDetected:
            return
        from vnactor import Actor
        if isinstance(ociChar, Actor):
            ociChar = ociChar.objctrl
        if self.engine == "KK" and self.mgrInstance:
            chaFileControl = ociChar.charInfo.chaFile
            accessoriesByChar = GetPrivate(self.mgrInstance, "_accessoriesByChar")
            if chaFileControl in accessoriesByChar:
                charAdditionalData = accessoriesByChar[chaFileControl]
                for i in range(min(len(charAdditionalData.showAccessories), len(visibleSet))):
                    charAdditionalData.showAccessories[i] = bool(visibleSet[i])
        elif self.engine == "AI/HS2" and self.mgrInstance:
            chaFileControl = ociChar.charInfo.chaFile
            additionalData = CallPrivate(self.mgrInstance, "GetAdditionalDataByCharacter", [chaFileControl])
            for i in range(len(additionalData.objects)):
                adObject = additionalData.objects[i]
                if adObject.obj and i < len(visibleSet):
                    adObject.show = bool(visibleSet[i])

# ::::::::::: AdditionalSkinEffects :::::::::::
class AdditionalSkinEffects(ExtPlugin):
    def __init__(self):
        self.isDetected = False
        self.engine = None
        if ExtPlugin.exists("KK_SkinEffects"):
            ExtPlugin.__init__(self, "KK_SkinEffects")
            self.isDetected = True
            self.engine = "KK"
        elif ExtPlugin.exists("KKS_SkinEffects"):
            ExtPlugin.__init__(self, "KKS_SkinEffects")
            self.isDetected = True
            self.engine = "KKS"

    def SkinEffectController(self, ociChar):
        if not self.isDetected:
            return None
        from KK_SkinEffects import SkinEffectsController
        try:
            return ociChar.charInfo.gameObject.GetComponentInChildren[SkinEffectsController]()
        except:
            traceback.print_exc()
            return None
    
    def GetAdditionalSkinEffects(self, ociChar):
        if not self.isDetected:
            return None
        ctrl = self.SkinEffectController(ociChar)
        if ctrl:
            #return (ctrl.ButtLevel, ctrl.SweatLevel, ctrl.TearLevel, ctrl.DroolLevel, ctrl.BukkakeLevel, ctrl.BloodLevel)
            # version 2, return a dict
            levels = [k for k in dir(ctrl) if k[0] != "_" and k.endswith("Level") and isinstance(getattr(ctrl, k), int)]
            aseData = {}
            for k in levels:
                aseData[k] = getattr(ctrl, k)
            return aseData
        else:
            return None

    def SetAdditionalSkinEffects(self, ociChar, value):
        if not self.isDetected:
            return
        # data check
        if (isinstance(value, tuple) or isinstance(value, list)) and len(value) == 6:
            # old data, transfer to dict
            aseData = {}
            aseData["ButtLevel"] = value[0]
            aseData["SweatLevel"] = value[1]
            aseData["TearLevel"] = value[2]
            aseData["DroolLevel"] = value[3]
            aseData["BukkakeLevel"] = value[4]
            aseData["BloodLevel"] = value[5]
        elif isinstance(value, dict):
            # new data
            aseData = value
        else:
            # unknown data
            return
        # load data
        ctrl = self.SkinEffectController(ociChar)
        if ctrl:
            for k in aseData.keys():
                try:
                    if hasattr(ctrl, k):
                        setattr(ctrl, k, aseData[k])
                except:
                    print("[Extplugins/AdditionalSkinEffects] Fail to restore skin effect: " + k)

# ::::::::::: AdvIKPlugin :::::::::::
class AdvIKPlugin(ExtPlugin):
    def __init__(self):
        if ExtPlugin.exists("KK_AdvIKPlugin"):
            ExtPlugin.__init__(self, "KK_AdvIKPlugin")
            self.engine = "KK"
        elif ExtPlugin.exists("KKS_AdvIKPlugin"):
            ExtPlugin.__init__(self, "KKS_AdvIKPlugin")
            self.engine = "KKS"
        elif ExtPlugin.exists("AI_AdvIKPlugin"):
            ExtPlugin.__init__(self, "AI_AdvIKPlugin")
            self.engine = "AI"
        elif ExtPlugin.exists("HS2_AdvIKPlugin"):
            ExtPlugin.__init__(self, "HS2_AdvIKPlugin")
            self.engine = "HS2"
        else:
            self.isDetected = False
            self.engine = None


# support functions
def has_plugin(pluginname):
    return ExtPlugin.exists(pluginname)

def GetPrivate(self, name):
    from System.Reflection import BindingFlags
    type = self.GetType()
    typefield = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
    return typefield.GetValue(self)

def CallPrivate(self, name, p):
    from System.Reflection import BindingFlags
    bindflgs = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy
    try:
        method = self.GetType().GetMethod(name, bindflgs)
    except:
        # try fix Ambiguous match
        types = tuple((pm.GetType() for pm in p))
        method = self.GetType().GetMethod(name, bindflgs, None, types, None)

    from System import Array
    ar = tuple(p)

    return method.Invoke(self, ar)

def GetPrivateProperty(self, name):
    return CallPrivate(self, "get_" + name, [])

def SetPrivateProperty(self, name, ar):
    CallPrivate(self, "set_" + name, ar)

def GetPrivateStatic(type, name):
    #return type.__dict__(name)
    from System.Reflection import BindingFlags
    #type = self.GetType()
    #typeof()
    import clr
    #clr.GetClrType(str)
    typefield = clr.GetClrType(type).GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy  | BindingFlags.Static)
    return typefield.GetValue(None)

def CallPrivateStatic(type, name, p):
    from System.Reflection import BindingFlags
    import clr
    method = clr.GetClrType(type).GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy  | BindingFlags.Static)
    
    #from System import Array
    ar = tuple(p)
    return method.Invoke(type, ar)

def GetPrivateNestedType(type, name):
    from System.Reflection import BindingFlags
    import clr

    nestedTypes = clr.GetClrType(type).GetNestedTypes(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy  | BindingFlags.Static)
    for nt in nestedTypes:
        if nt.Name == name:
            return nt
    return None
