import os
import imp
import sys
import re
import inspect 

pluginSubDir = "pythonplugins"
gimmickSubDir = "gimmicks"

baseDir = os.path.dirname(__file__)
pluginBaseDir = os.path.join( baseDir ,pluginSubDir)
import __builtin__
import site
import nuke
import nukescripts


class KellerNukePluginException(Exception):
    def __init__(self, *args, **kwargs):
        Exception.__init__(self, *args, **kwargs)

class KellerNukePlugin():
    
    STATE_UNLOADED = 00
    STATE_ERROR = 05
    STATE_LOADED = 10
    STATE_INACTIVE = 20
    STATE_ACTIVE = 30
    
    _STATE_STR_ = { 00: "UNLOADED",
                    05: "ERROR",
                    10: "LOADED",
                    20: "INACTIVE",
                    30: "ACTIVE"
                   }
    
    
    def __init__(self, module):
        pass
        self.module = module
        self.pluginName = self.__class__.__name__
        self.state = KellerNukePlugin.STATE_LOADED
        self.valid = True
        
    def setInvalid(self):
        self.valid = False
        self.state = KellerNukePlugin.STATE_UNLOADED
         
    def checkValid(self):
        if not self.valid:
            raise KellerNukePluginException("plugin unloaded. instance is no longer valid")
        
    def getName(self):
        return self.pluginName
    
    def getState(self):
        return self.state
    
    def getStateAsString(self):
        return KellerNukePlugin._STATE_STR_[self.getState()]
    
    def getModule(self):
        return self.module
    
    
    def activate(self):
        self.checkValid()
        
        if (self.state == KellerNukePlugin.STATE_INACTIVE) or (self.state == KellerNukePlugin.STATE_LOADED):
            self.configurePluginInternal()
            self.state = KellerNukePlugin.STATE_ACTIVE
            
            
        #else:
        #    raise KellerNukePluginException("Plugin has wrong state for this operation")
    
    def deactivate(self):
        self.checkValid()
        if self.state == KellerNukePlugin.STATE_ACTIVE:
            try:
                self.unconfigurePluginInternal()
            except:
                # TODO, handle this
                logDebug("Error while unconfiguring plugin %s" % self.getName())
            self.state = KellerNukePlugin.STATE_INACTIVE
        #else:
        #    raise KellerNukePluginException("Plugin has wrong state for this operation")
    
    
  
        
    
    def configurePluginInternal(self):
        pass
        
        logDebug("configure plugin %s" % self.pluginName)
        
        # configure folders
        baseDir = os.path.dirname(__file__)
        
                
        self.configurePlugin()
    
    def unconfigurePluginInternal(self):
        self.unconfigurePlugin()
        logDebug("unconfigure plugin %s" % self.getName())
    
    
    def unconfigurePlugin(self):
        pass
    
    def configurePlugin(self):
        pass
    
    def __str2__(self):
        return "%s > %s[%s]" % (super(KellerNukePlugin, self), self.getName(), self.getStateAsString())
    
    
def logDebug(msg):
    import inspect, sys, os
    
    frm = inspect.stack()[1]
    mod = inspect.getmodule(frm[0])
    caller = mod.__name__
    #caller = os.path.basename(sys._getframe(0).f_back.f_code.co_filename)
    
    nuke.debug("[%s] %s" % (caller,msg))


class KellerPluginManagerImpl():
    def __init__(self):
        pass
        self.kellerPluginPool = dict()
        self.serviceData = dict()
 
     
    def getPlugins(self):
        return self.kellerPluginPool.values()
        
    def getPluginByName(self, pluginName):
        
        if pluginName in self.kellerPluginPool:
            return self.kellerPluginPool[pluginName]
        return None
         
    def deactivatePlugins(self):
        
        # iterate over list COPY
        for plugin in self.kellerPlugins.values[:]:
            if plugin.getState() == KellerNukePlugin.STATE_ACTIVE:
                plugin.deactivate()
            
    def dumpPlugins(self):
        
        
        pattern = "{0:<25} {1:<10}\n"
        
        s = ""
        s  = s + pattern.format("Name", "State")
        s  = s + "---------------------------------------------------------------------------------\n"
        
        
        for plugin in self.kellerPluginPool.values():
            s = s + pattern.format(plugin.getName(), plugin.getStateAsString())

        
    def reloadPluginByName(self, pluginName):
        
        plugin = self.getPluginByName(pluginName)
        
        if plugin is None:
            logDebug("plugin %s not found " % pluginName)
            return
        
        
        return self.reloadPlugin(plugin)
        
    def reloadPlugin(self, plugin):
        
        pluginName = plugin.getName()
        
        
        if (plugin.getState() == KellerNukePlugin.STATE_ACTIVE):
            plugin.deactivate()
        
         
            
        if (plugin.getState() == KellerNukePlugin.STATE_INACTIVE):
            
            plugin.setInvalid()
                        
            # remove from pool
            del self.kellerPluginPool[pluginName]
            
            reload(plugin.module)
            
            plugin = self.loadPluginModuleContents(plugin.module)
            
            return plugin
            # add to pool
            
            # add to plugin pool
            
      
    
    def loadPluginModuleContents(self, module):
        
            
        for (mname, mobj) in inspect.getmembers(module):
            #print mname
            #print mobj
            #print AbstractNukePlugin
            if inspect.ismodule(mobj):
                #nuke.debug("importing %s" % mname)
                #__import__(mname)
                __builtin__.__dict__[mname] = mobj
                
            if (inspect.isclass(mobj)) and (issubclass(mobj, KellerNukePlugin)):
                #nuke.debug(">>>> Found plugin definition for %s" % mname)
                minstance = mobj(module)
                
                # add to pool
                self.kellerPluginPool[minstance.getName()] = minstance
                
                
                minstance.activate()
                return minstance
    
    
    def setServiceData(self, name, service):
        self.serviceData[name] = service
        
    def getServiceData(self, name):
        if name in self.serviceData.keys():
            return self.serviceData[name]
        else:
            return None
    
    def removeServiceData(self, name):
        if name in self.serviceData.keys():
            del self.serviceData[name]
        
    def loadPluginDir(self, pluginDir, loadPluginInstance=False):
        if not os.path.isdir(pluginDir):
            return
        #logDebug("Adding plugin dir %s" % pluginDir)
        #site.addsitedir(pluginDir)
        
        # get python type descriptions
        type_descriptions = dict()
        for description in imp.get_suffixes():
            type_descriptions[description[0]] = description
        
        
        #print "basedir is %s " %pluginBaseDir
        #nuke.debug("basedir is %s" % pluginBaseDir)
        pythonFiles = os.listdir(pluginDir)
        
        for pyFile in pythonFiles:
            filename = os.path.basename(pyFile)
            #print "found " + filename
            
            match = re.match("(?P<prefix>.*)(?P<postfix>\..*)", filename)
            
            if match is not None:
                prefix = match.group("prefix")
                ext = match.group("postfix")
        
                            
                if ext in [".py"]:
              
                    fileObj = open(os.path.join(pluginDir,filename))
                    
                    module = imp.load_module(prefix, fileObj, os.path.join(pluginDir,filename), type_descriptions[ext])
                    __builtin__.__dict__[module.__name__] = module
                        
                    
                    
                    if loadPluginInstance:
                        self.loadPluginModuleContents(module)
                    
                    fileObj.close()
    

        
        
        
# create PluginManager instance
__builtin__.KellerPluginManager = KellerPluginManagerImpl()
   
__builtin__.KellerNukePlugin = KellerNukePlugin
__builtin__.KellerNukePluginException = KellerNukePluginException

__builtin__.nuke = nuke
__builtin__.nukescripts = nukescripts
__builtin__.WORKGROUP_BASEDIR = baseDir
__builtin__.logDebug = logDebug

sys.dont_write_bytecode = True
# allowed python suffixes for modules
#suffixes = [str(suff[0]) for suff in imp.get_suffixes()]



def addSiteDir(pluginDir):
    if not os.path.isdir(pluginDir):
        return
    #logDebug("Adding to site: %s" % pluginDir)
    if pluginDir not in sys.path:
        site.addsitedir(pluginDir)



        
def loadPlugins(pluginBaseDir, loadPluginInstance = False):
    
    pluginDirList = os.listdir(pluginBaseDir)
    
    for pluginDir in pluginDirList:
        addSiteDir(os.path.join(pluginBaseDir,pluginDir))
    # process plugins
    for pluginDir in pluginDirList:
        KellerPluginManager.loadPluginDir(os.path.join(pluginBaseDir,pluginDir), loadPluginInstance)
        
        
        
#def reloadPlugins():
#    unloadPlugins()
#    loadPlugins()
def init(pluginBaseDir):
    # add all dirs to site and make them visible
    
    logDebug("Initializing Keller PluginCore...")
    
    # process and register all plugins, but to not activate
    loadPlugins(pluginBaseDir, loadPluginInstance=False)

def start(pluginBaseDir):
    # process all plugins and activate instances
    loadPlugins(pluginBaseDir, loadPluginInstance=True)
