# External Plugin Functions 
# Module for calling functions from native plugins in "/Plugins" folder
# v2.8
#
# 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



# 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 clr
import os
from vngameengine import get_engine_id, HSNeoOCIChar

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/")
        plgdir = ExtPlugin.calc_plugin_dir()

        return os.path.isfile(plgdir + "/" + name + ".dll")


    # 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 ::::::::::: 

# --- 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, e:
                    print "Error in ExtSave handler save:", 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, e:
                    print "Error in ExtSave handler load:", e

class HSPENeo(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "HSPENeo")

    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)

        from HSPE import MainWindow

        from UnityEngine import GameObject
        from HSPE import PoseController

        xmlTextWriter.WriteStartElement("characterInfo")
        ocichar.charInfo.gameObject.GetComponent(PoseController).SaveXml(xmlTextWriter)
        xmlTextWriter.WriteEndElement()

        return stringWriter.ToString()

    def SetCharaSettingsText(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;

        from HSPE import MainWindow

        from UnityEngine import GameObject
        from HSPE import PoseController

        ocichar.charInfo.gameObject.GetComponent(PoseController).ScheduleLoad(node, None)


# ::::::::::: 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 e
                    pass
            elif isinstance(char, OCIChar): #might be redundant, not sure
                try:
                    FKIKAssist.FKIKAssistMgr.ActivateFKIK(char)
                except SystemError as e:
                    #print 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)
                
    # 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?        
        
# ::::::::::: CharaStudio ::::::::::: 
class KKPE(ExtPlugin):
    def __init__(self):
        #super(HSStudioNEOAddon, self).__init__("HSStudioNEOAddon") #can't get it to work
        ExtPlugin.__init__(self, "KKPE")

    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)

        from HSPE import MainWindow

        from UnityEngine import GameObject
        from HSPE import PoseController
        # print self.studio.m_BackgroundCtrl.Load(ffile)
        # for obj in GameObject.FindObjectOfType(BackgroundCtrl):

        #obj = GameObject.FindObjectOfType(MainWindow)

        #obj.SaveChara(ocichar,xmlTextWriter)
        xmlTextWriter.WriteStartElement("characterInfo")
        ocichar.charInfo.gameObject.GetComponent(PoseController).SaveXml(xmlTextWriter)
        xmlTextWriter.WriteEndElement()

        return stringWriter.ToString()

    def SetCharaSettingsText(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;

        from HSPE import MainWindow

        from UnityEngine import GameObject
        from HSPE import PoseController

        ocichar.charInfo.gameObject.GetComponent(PoseController).ScheduleLoad(node, None)
        # print self.studio.m_BackgroundCtrl.Load(ffile)
        # for obj in GameObject.FindObjectOfType(BackgroundCtrl):

        # obj = GameObject.FindObjectOfType(MainWindow)

        # obj.SaveChara(ocichar,xmlTextWriter)
        # xmlTextWriter.WriteStartElement("characterInfo")
        # ocichar.charInfo.gameObject.GetComponent(PoseController).SaveXml(xmlTextWriter)
        # xmlTextWriter.WriteEndElement()
        #
        # return stringWriter.ToString()


class ScreencapPlugin(ExtPlugin):
    def __init__(self):
        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


class KKVMDPlayPlugin(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "KKVMDPlayPlugin")
        # support class instance from plugin
        if self.isDetected:
            from KKVMDPlayPlugin import VMDAnimationMgr
            self.vmdMgr = VMDAnimationMgr.Instance

    # 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.Stop()

    # 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.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):
                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?     

# ::::::::::: StudioNeoV2 (AI) ::::::::::: 
class AIPE(ExtPlugin):
    def __init__(self):
        ExtPlugin.__init__(self, "AIPE")

    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)

        from UnityEngine import GameObject
        from HSPE import PoseController

        xmlTextWriter.WriteStartElement("characterInfo")
        ocichar.charInfo.gameObject.GetComponent(PoseController).SaveXml(xmlTextWriter)
        xmlTextWriter.WriteEndElement()

        return stringWriter.ToString()

    def SetCharaSettingsText(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;

        from UnityEngine import GameObject
        from HSPE import PoseController

        ocichar.charInfo.gameObject.GetComponent(PoseController).ScheduleLoad(node, None)

# ::::::::::: 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

        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")
        CallPrivate(nodescon,"SaveSceneGeneric",[xmlTextWriter])

        xmlTextWriter.WriteEndElement()

        return stringWriter.ToString()

    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;

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

        CallPrivate(nodescon,"LoadSceneGeneric",[node.FirstChild,-1])

    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)


# 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
    method = self.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)

    from System import Array
    ar = tuple(p)

    return method.Invoke(self, 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)