#
# -*- coding: utf-8 -*-
'''
$RCSfile: configuration.py,v $
$Revision: 1157 $
$Author: markus $
$Date: 2012-04-16 18:06:55 +0200 (Mo, 16. Apr 2012) $
'''


############################################################################################################
#
#   IMPORTS
#
#===========================================================================================================
import inspect as           _i
import os.path as           _p
import sys, os
from types import DictType, ListType
import ConfigParser

INT_PROPS =['recLimit','expire','error_maxbytes','error_backupcount','debug_maxbytes','debug_backupcount','request_maxbytes','request_backupcount']
BOOL_PROPS=['debug','pickle','prettyprint']

def isTrue(arg):
    '''Boolean expression evaluation extended by '0' and 'false' (case insensitive) evaluating to False 
    instead of True done by bool function which is used for all other evaluations.'''
    try:
        arg =str(arg)
        arg = arg.strip()
        arg = arg.lower()
        if arg in ('0', 'false'):
            return False
        else:
            return bool(arg)
    except:
        return bool(arg)


############################################################################################################
#
#   Singleton metaclass. Exists in biocase.tools also but avoided here so no biocase namespace is required
#
#===========================================================================================================
class Singleton(type):
    def __init__(cls,name,bases,dic):
        super(Singleton,cls).__init__(name,bases,dic)
        cls.__instance=None
    def __call__(cls,*args,**kw):
        if cls.__instance is None:
            cls.__instance=super(Singleton,cls).__call__(*args,**kw)
        return cls.__instance
        
        
############################################################################################################
#
#   CONFIGURATION SECTION CLASS
#
#===========================================================================================================
class ConfigurationSection( object ):
    #-------------------------------------------------------------------------------------------------------
    def __init__(self):
        self.__name__     = None
        self.__filename__ = None

    #-------------------------------------------------------------------------------------------------------ä
    def __setOption__(self, optionName, value):
        if optionName in INT_PROPS:
            try:
                value=int(value)
            except:
                # Configuration option is no integer! Default to 0
                value=0
        elif optionName in BOOL_PROPS:
            value=isTrue(value)
        setattr(self, optionName, value )

    #-------------------------------------------------------------------------------------------------------
    def __save__( self ):
        if self.__filename__ is None:
            return False
        options = {}
        # get entries (flatten lists)
        for name in dir( self ):
            if name.startswith( '__' ): continue
            attr = getattr( self, name )
            if type(attr) == ListType:
                for i,e in enumerate(attr):
                    options["%s_%i"%(name,i+1)] = e
            elif name in INT_PROPS:
                options[name]=unicode(attr)
            elif name in BOOL_PROPS:
                options[name]=unicode(attr)
            else:
                options[name]=unicode(attr)
        # write options
        fp = file(self.__filename__)
        pre  = ""
        post = ""
        tmp = ""
        inSect = False
        # read existing ini and ignore this sections
        for line in fp.readlines():
            if not inSect:
                tmp = tmp + line
                if line.strip() == "[%s]"%self.__name__:
                    # found this objects section.
                    inSect = True
                    pre = tmp
                    tmp = ""                    
            else:
                if line.strip().startswith("["):
                    # other section starting
                    inSect = False
                    tmp = "\n" + line
                else:
                    # options. ignore them
                    pass
        post = tmp
        fp.close()            
        
        fpw = file(self.__filename__, 'w')
        fpw.write(pre)
        # create new string for options
        names = sorted(options.keys())
        for name in names:
            fpw.write("%s  =  %s\n" % (name.encode('utf-8'), options[name].encode('utf-8')))
        fpw.write(post)
        fpw.close()

        
############################################################################################################
#
#   GLOBAL CONFIGURATION BASE CLASS. might be replaced by another class when using getCfg()
#
#===========================================================================================================
class Cfg( object ):
    __metaclass__ = Singleton

    def __init__(self):
        self.ns2prefix={}
        # set home locators. Needed to find default configuration files
        self._setHomeLocator()
        # always read the lib configs
        self._readConfigFiles()

    #-------------------------------------------------------------------------------------------------------
    def _setHomeLocator(self, bpsPath=None):
        #print "Set CFG bpsPath = %s"%bpsPath
        if bpsPath is None or not os.path.isdir(bpsPath):
            from adjustpath import bpsPath as homeLocator
            #print "adjustpath bpsPath = %s"%homeLocator
            if homeLocator is None or not os.path.isdir(homeLocator):
                homeLocator = _p.join( _p.dirname( _i.getfile( lambda:None )), os.pardir, os.pardir)
        else: 
            homeLocator = bpsPath
        Cfg.homeLocator             = _p.normpath(homeLocator)
        Cfg.libLocator              = _p.join( Cfg.homeLocator,             'lib'                  )
        Cfg.logLocator              = _p.join( Cfg.homeLocator,             'log'                  )
        Cfg.cacheLocator            = _p.join( Cfg.homeLocator,             'cache'                )

        Cfg.wwwLocator              = _p.join( Cfg.homeLocator,             'www'                  )
        Cfg.resourceLocator         = _p.join( Cfg.wwwLocator,              'resources'            )
        Cfg.schemasLocator          = _p.join( Cfg.resourceLocator,         'schemas'              )

        Cfg.configurationLocator    = _p.join( Cfg.homeLocator,             'config'               )
        Cfg.dsaLocator              = _p.join( Cfg.configurationLocator,    'datasources'          )
        Cfg.skinLocator             = _p.join( Cfg.configurationLocator,    'querytool','skins'    )

        Cfg.templateLocator         = _p.join( Cfg.homeLocator,             'templates'            )
        Cfg.rawCmfTemplateLocator   = _p.join( Cfg.templateLocator,         'cmf'                  )
        Cfg.dsaTemplateLocator      = _p.join( Cfg.templateLocator,         'datasources'          )

        Cfg.archiveWorkLocator      = _p.join( Cfg.homeLocator,             'archive'              )
        Cfg.archiveDownloadLocator  = _p.join( Cfg.wwwLocator,              'downloads'            )
        Cfg.archiveLibLocator       = _p.join( Cfg.libLocator,              'biocase', 'archive'   )
        Cfg.kettleLibLocator        = _p.join( Cfg.libLocator,              'kettle'               )        
    
        
    #-------------------------------------------------------------------------------------------------------ä
    def _setOption(self, sectionName, optionName, value):
        if sectionName in dir(self):
            sect = getattr(self, sectionName)
            if isinstance(sect, ConfigurationSection):
                sect.__setOption__(optionName, value)

    #-------------------------------------------------------------------------------------------------------
    def _listOptions(self, sectionName):
        if sectionName in dir( self ):
            sect = getattr( self, sectionName )
            if isinstance(sect, ConfigurationSection):
                return self._getRecursiveDict(sect).keys()
        return []

    #-------------------------------------------------------------------------------------------------------
    def _dictAsTable( self, d, indent=0 ):
        R = [ ]
        names = d.keys()
        names.sort()
        for name in names:
            value = d[ name ]
            if type(value) == DictType:
                value = '\n' + self._dictAsTable(d=value, indent=indent+1)
            indentString = '  '*indent
            R.append( '%s%s:\t%s' % ( indentString, name, value, ) )
        R = '\n'.join( R )
        return R

    #-------------------------------------------------------------------------------------------------------
    def _getSections(self):
        sections = []
        for name in dir( self ):
            attr = getattr( self, name )
            if isinstance(attr, ConfigurationSection):
                sections.append( attr )
        return sections
        
    #-------------------------------------------------------------------------------------------------------
    def _getDict( self, d=None, base='' ):
        '''Return a simple dictionary with keys using . for the sections'''
        R = {}
        if d is None:
            d = self._getRecursiveDict()
        names = d.keys()
        names.sort()
        for name in names:
            value = d[ name ]
            if type(value) == DictType:
                R2 = self._getDict(d=value, base="%s."%name)
                R.update(R2)
            else:
                R['%s%s'%(base,name)] = value
        return R
    
    def _getRecursiveDict( self, obj=None ):
        if obj == None:
            obj = self
        R = {}
        for name in dir( obj ):
            attr = getattr( obj, name )
            if name.startswith( '_' ) or _i.ismethod(attr): continue
            if isinstance(attr, ConfigurationSection):
                R[ name ] = self._getRecursiveDict( obj=attr )
            else:
                R[ name ] = attr
        return R
    
    #-------------------------------------------------------------------------------------------------------
    def _asTable( self, showEnvironment = False ):
        R = self._getRecursiveDict()
        return self._dictAsTable( R )

    #-------------------------------------------------------------------------------------------------------
    def __repr__( self ):
        return self._asTable()
    
    #-------------------------------------------------------------------------------------------------------
    def _readConfigFiles(self, filenames=[] ):
        # always read the lib, namespace & server configs
        self.__addCfgFile__(_p.join( self.libLocator, 'biocase','configuration.ini' ), readonly=True)
        self.__addCfgFile__(_p.join( self.configurationLocator,'config.ini' ), readonly=False)
        self.__addCfgFile__(_p.join( self.configurationLocator,'namespaces.ini' ), readonly=True)
        # postprocess namespaces. Create dictionary!
        if hasattr(self, "namespaces"):
            for name in dir(self.namespaces):
                if not name.startswith("_"):
                    self.ns2prefix[getattr(self.namespaces,name)]=name
        for fn in filenames:
            self.__addCfgFile__(configFilename, readonly=False)
            
    #-------------------------------------------------------------------------------------------------------
    def __addCfgFile__( self, configFilename, readonly=False ):
        '''Read a configParser file and add its entries to this object'''
        # read configparser file
        cfg = ConfigParser.ConfigParser()
        cfg.optionxform = str
        cfg.read(configFilename)
        for sectname in cfg.sections():
            # add section to cfg object
            section = ConfigurationSection()
            section.__name__ = sectname
            setattr(self, sectname, section)
            if readonly:
                section.__filename__ = None
            else:
                section.__filename__ = configFilename
            lists = {}
            for opt,val in cfg.items(sectname):
                if len(opt) > 1 and opt[-2] == '_':
                    # process as list
                    idx = int(opt[-1])
                    opt = opt[:-2]
                    if not lists.has_key(opt):
                        lists[opt]={}
                    lists[opt][idx] = val 
                else:
                    # set option in this section
                    section.__setOption__(opt, val)
            # add lists to section
            for opt, valList in lists.items():
                ids = valList.keys()
                ids.sort()
                l = []
                for i in ids:
                    l.append(valList[i])
                section.__setOption__(opt, l)
            
    #-------------------------------------------------------------------------------------------------------
    def __save__( self ):
        for sect in self._getSections():
            sect.__save__()
            
    #-------------------------------------------------------------------------------------------------------
    def getWebappURL(self):
        return "%s%s/" % (self.server.host, self.server.webroot)
    def getAccessPoint(self, dsa, protocol="biocase"):
        '''Return the full absolute URL to the pywrapper for this datasource'''
        if protocol == 'spice':
            return '%spyspice.cgi?dsa=%s'%(self.getWebappURL(), dsa)
        else:
            #import logging
            #log = logging.getLogger()
            #log.error('%spywrapper.cgi?dsa=%s'%(self.getWebappURL(), dsa))
            return '%spywrapper.cgi?dsa=%s'%(self.getWebappURL(), dsa)            
    def getIP(self):
        return self.server.host.replace(self.server.host[:self.server.host.find('://')+3], '' )
    #-------------------------------------------------------------------------------------------------------
    def reloadConfigFiles(self, additionalConfigFiles=[], bpsPath=None):
        '''reload config file(s)'''
        # remove existing sections
        for sec in self._getSections():
            delattr( self, sec.__name__ )
        # reset home locator
        self._setHomeLocator(bpsPath)
        # load sections again
        self._readConfigFiles(additionalConfigFiles)


############################################################################################################
#
#===========================================================================================================
if __name__ == '__main__':
    import sys
    sys.setrecursionlimit( 20 )
    cfg = Cfg()
    print cfg
    print cfg._listOptions("server")
    print type(cfg.server.debug)
    cfg.server.__setOption__("debug","False")
    print cfg.server.debug, type(cfg.server.debug)
    #cfg.__save__()
    cfg.reloadConfigFiles()
    print cfg.ns2prefix
    print "-"*80
    print cfg
    
