# -*- coding: utf8 -*-
'''
SAX HANDLER for CMF configuration files
####################################################
#                     SAX HANDLER
#             for CMF configuration files
# http://www.biocase.org/schemas/conceptmapping/1.2
####################################################
$RCSfile: cmf_handler.py,v $
$Revision: 905 $
$Author: markus $
$Date: 2008-04-30 11:57:10 +0200 (Mi, 30. Apr 2008) $

'''


import string
from biocase.tools.saxbasehandler         import SaxBaseHandler
from biocase.tools.various_functions         import isTrue, unique
from biocase.wrapper.errorclasses     import *
from biocase.wrapper.cmf_element    import CMFMappingClass, TotalMatchedRecords
from biocase.wrapper.sql.operators    import comparisonOperator
from biocase.wrapper.typeconverter    import DBValueClass, DBAttributeClass

import biocase.configuration
cfg = biocase.configuration.Cfg()

import logging
log=logging.getLogger("pywrapper.cmf.handler")


def addMapping2ListOfAll(existingList, newMapping):
    '''Add a new mapping to the * list of all mappings.
    Check if the new mapping refers to an attribute already existing in the list.
    If this is the case, the old object is returned. 
    Otherwise the new object is assigned an rsindex,added to the list and returned as well.
    In some cases like the metaprofile this reduces a lot the complexity of the final SQL statement.'''
    found = 0
    for mapping in existingList:
        if mapping.attr == newMapping.attr and mapping.table == newMapping.table:
            # it is existing already in the list. 
            # return existing object
            return mapping
            break
    # the new mapping did not exist in the full list before. add it
    newMapping.rsindex = len(existingList) +1
    existingList.append(newMapping)
    return newMapping


# ----------------------------------------------------------------------------
class CMFhandler_2_1(SaxBaseHandler):
    '''Parses CMFs of version 2.1'''
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __init__(self, cmfObj):
        self.cmfObj = cmfObj
        self.cmfObj.recIdents    = []
        #
        # the final result data
        # an init-dictionary to be used to init the root cmf element object
        #
        self.initDict         = {}
        self.rootTable        = ''
        self.staticTables    = []
        # a list of all DBAttrObjects found in the CMF file.
        # Used instead of * for querying the DB.
        # The CMF element objects have a RSindex property, which tells them the index of the resultsetlist where 
        self.cmfObj.allDBAttr    = []
        #
        # temporary variables
        #
        # a list of open dictionaries holding init data for elementclass objects or attributes
        self._tmpElemList = []
        self._preferredCEID = None
        self._IsVocabulary = 0
        self._isAttr = 0
        self._isDaEl = 0
        self._filterObj = None
        self._isFilter = 0
        self._isMapFilter = 0
        self._SPICEReqType = None
        self._value = ''
        SaxBaseHandler.__init__(self)

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def startElement(self, name, attrs):
        name, attrs = SaxBaseHandler.startElement(self, name, attrs)
        if name == 'dataElement':
            self._isDaEl = 1
            self._tmpElemList.append({'xpath':attrs.get('path', None), 'attributes':[], 'subelements':[], 'name':attrs.get('name', None), 'ns':attrs.get('namespace', None), 'repmin':attrs.get('min', None), 'repmax':attrs.get('max', None), 'dontrepeat':attrs.get('dontRepeat', None), 'type':attrs.get('w3cType', None), 'cnid':attrs.get('choiceNodeId', None), 'ceid':attrs.get('choiceElemId', None), 'preferredChoice':isTrue(attrs.get('preferredChoiceFlag', None))})
        elif name == 'name':
            self.cmfObj.acronym = attrs.get('acronym', None)
            self.cmfObj.version = attrs.get('version', None)
        elif name == 'generated':
            self.cmfObj.generated_when = attrs.get('when', None)
            self.cmfObj.generated_by   = attrs.get('by', None)
        elif name == 'filterRecords' or name == 'filterRecordsWhere':
            self._isFilter = 1
        elif name == 'if':
            self._isMapFilter = 1
        elif name == 'attribute':
            self._isAttr = 1
            self._tmpElemList.append({'xpath':attrs.get('path', None), 'attributes':[], 'subelements':[], 'name':attrs.get('name', None), 'ns':attrs.get('namespace', None), 'repmin':attrs.get('min', None), 'repmax':attrs.get('max', None), 'dontrepeat':attrs.get('dontRepeat', None), 'type':attrs.get('w3cType', None)})
        elif name == 'mapping':
            # create new mapping object
            mapObj = CMFMappingClass()
            # add object to tmp-list:
            if self._tmpElemList[len(self._tmpElemList)-1].has_key('mappings'):
                self._tmpElemList[len(self._tmpElemList)-1]['mappings'].append(mapObj)
            else:
                self._tmpElemList[len(self._tmpElemList)-1]['mappings'] = [mapObj]
        elif name == 'literal':
            self._tmpElemList[len(self._tmpElemList)-1]['mappings'][-1].addItem(attrs.get('value', None))
        elif name == 'TotalMatchedRecords':
            self._tmpElemList[len(self._tmpElemList)-1]['mappings'][-1].addItem( TotalMatchedRecords() )
        elif name == 'dbAttribute':
            # create new DBAttrObj
            dbAttrObj = DBAttributeClass()
            dbAttrObj.table = attrs.get('tablealias', None)
            dbAttrObj.attr  = attrs.get('attribute', None)
            t = attrs.get('type', 'text')
            if t in ('unicode','string'):
                t = 'text'
            dbAttrObj.type  = t
            dbAttrObj.nosearch = isTrue(attrs.get('nosearch', 0))
            #
            # create RecordSet Index for mapped DBAttrObj
            #
            # add object to attrObjectList and set rsindex of object to its index of that list:
            dbAttrObj = addMapping2ListOfAll(self.cmfObj.allDBAttr, dbAttrObj)
            # add db attribute object to last mapping object
            self._tmpElemList[len(self._tmpElemList)-1]['mappings'][-1].addItem(dbAttrObj)
        elif name == 'vocabulary':
            self._IsVocabulary = 1
            self._tmpElemList[len(self._tmpElemList)-1]['voc'] = []
            self._tmpElemList[len(self._tmpElemList)-1]['vocdb'] = []
        elif name == 'term' and self._IsVocabulary == 1:
            self._tmpElemList[len(self._tmpElemList)-1]['voc'].append(attrs.get('schemaTerm', None))
            # add empty list
            self._tmpElemList[len(self._tmpElemList)-1]['vocdb'].append([])
        elif name in ['equals', 'notEquals', 'in']:
            dbAttrObj = DBAttributeClass()
            dbAttrObj.table = attrs.get('tablealias', None)
            dbAttrObj.attr  = attrs.get('attribute', None)
            dbAttrObj.type  = attrs.get('type', 'text')
            # add object to attrObjectList
            dbAttrObj = addMapping2ListOfAll(self.cmfObj.allDBAttr, dbAttrObj)
            # tmp store the filter object
            self._filterObj = comparisonOperator(name, dbAttrObj, None)
        elif name == 'altRootTableAlias':
            self._SPICEReqType = attrs.get('reqType', None)
            self.cmfObj.altRootTableAliasSPICE[self._SPICEReqType] = None
            
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def endElement(self, name):
        name=SaxBaseHandler.endElement(self, name)
        #self.watchVars(name, '', 'END')
        if name == 'name':
            self.cmfObj.name = self._value
        elif name == 'rootTableAlias':
            self.rootTable = self._value
        elif name == 'staticTableAlias':
            self.staticTables.append( self._value )
        elif name == 'recordIdentifier':
            self.cmfObj.recIdents.append(self._value)
        elif name == 'omitNamespaces':
            self.cmfObj.omitNS = isTrue(self._value)
        elif name == 'dataElement':
            # work with initDict for this element
            initDict = self._tmpElemList.pop()
            if len(self._tmpElemList) > 0:
                # save initDictionary as subelement of parent
                self._tmpElemList[len(self._tmpElemList)-1]['subelements'].append(initDict)
            else:
                # save last element as part of initDict as the RESULT !!!
                self.initDict = initDict
            self._isDaEl = 0
        elif name == 'attribute':
            initDict = self._tmpElemList.pop()
            self._tmpElemList[len(self._tmpElemList)-1]['attributes'].append(initDict)
            self._isAttr = 0
        elif name == 'vocabulary':
            self._IsVocabulary = 0
        elif name == 'dbTerm' and self._IsVocabulary:
            # a controlled vocabulary term as used in the database. map it to voc term if it didnt exist before:
            if self._value not in self._tmpElemList[len(self._tmpElemList)-1]['vocdb'][-1]:
                self._tmpElemList[len(self._tmpElemList)-1]['vocdb'][-1].append(self._value)
        elif name == 'term' and self._IsVocabulary:
            # automatically generate term mappings by replacing for underscores _ with spaces
            # if there is no dbterm-schema mapping existing
            if len(self._tmpElemList[len(self._tmpElemList)-1]['vocdb'][-1]) == 0:
                dbterm = self._tmpElemList[len(self._tmpElemList)-1]['voc'][-1]
                if dbterm is not None and dbterm.find('_'):
                    self._tmpElemList[len(self._tmpElemList)-1]['vocdb'][-1].append(dbterm.replace('_', ' '))
        elif name in ['equals', 'notEquals']:
            # set comparison value to filter object
            self._filterObj.arg2 = self._value
        elif name == 'value' and (self._isFilter or self._isMapFilter):
            # values of IN COP
            if self._filterObj.arg2 == None:
                self._filterObj.arg2 = []
            self._filterObj.arg2.append(self._value)
        elif name == 'filterRecords' or name == 'filterRecordsWhere':
            self._isFilter = 0
            # its a general filter object
            self._tmpElemList[len(self._tmpElemList)-1]['filter'] = self._filterObj
        elif name == 'if':
            self._isMapFilter = 0
            # its part of an mapping object
            self._tmpElemList[len(self._tmpElemList)-1]['mappings'][-1].filterObj = self._filterObj
        elif name == 'altRootTableAlias':
            self.cmfObj.altRootTableAliasSPICE[self._SPICEReqType] = self._value



        
# ----------------------------------------------------------------------------
class CMFNamespaceHandler(SaxBaseHandler):
    '''Parses any CMF to retrieve its "root" namespace.'''
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __init__(self):
        # the final result data
        #
        self.namespace        = None
        #
        # temporary variables
        self._value = ''
        SaxBaseHandler.__init__(self)

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def startElement(self, name, attrs):
        name, attrs = SaxBaseHandler.startElement(self, name, attrs)
        if name == 'dataElement' and self.namespace == None:
            self.namespace = attrs.get('namespace', None)
            
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def endElement(self, name):
        name=SaxBaseHandler.endElement(self, name)
        if name == 'schemaNamespace':
            self.namespace = self._value



# ----------------------------------------------------------------------------
class CMFVersionHandler(SaxBaseHandler):
    '''Parses any CMF to retrieve its namespace version.'''
    cmfV21  = ("2.1",'http://www.biocase.org/schemas/conceptmapping/2.1')
    cmfV20  = ("2.0",'http://www.biocase.org/schemas/conceptmapping/2.0')
    cmfV14  = ("1.4",'http://www.biocase.org/schemas/conceptmapping/1.4')
    cmfInfos = [cmfV14,cmfV20,cmfV21]
    cmfTags = {'metadata':cmfV21, 'settings':cmfV21, 'mapping':cmfV21, 'filterRecords':cmfV21
            , 'mapDB':cmfV20,'filterRecordsWhere':cmfV20
            , 'schemaNamespace':cmfV14, 'repMax':cmfV14, 'repMin':cmfV14}
        
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __init__(self):
        # the final result data
        #
        self.cmfInfoTuple    = None
        #
        SaxBaseHandler.__init__(self)

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def _version2InfoTuple(self, version):
        for info in self.__class__.cmfInfos:
            if version == info[0]:
                return info
        return None

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def _NS2InfoTuple(self, NS):
        for info in self.__class__.cmfInfos:
            if NS == info[1]:
                return info
        return None

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def startElement(self, name, attrs):
        name, attrs = SaxBaseHandler.startElement(self, name, attrs)
        if self.__class__.cmfTags.has_key(name):
            self.cmfInfoTuple  = self.__class__.cmfTags[name]
            raise StopSAX
        elif attrs.has_key('xmlns') and attrs.get('xmlns') in [info[1] for info in self.__class__.cmfInfos]:
            for info in self.__class__.cmfInfos:
                if info[1] == attrs.get('xmlns'):
                    self.cmfInfoTuple = info
                    raise StopSAX
            
                    

# ----------------------------------------------------------------------------
class CMFConfigToolHandler(SaxBaseHandler):
    '''Parses any CMF version to retrieve:
    - all concept paths that carry xml types as hash keys to lists of mapping objects
    - the root alias
    - a list of static tables
    - and the "root" namespace of the schema.'''
    
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def __init__(self):
        # the final result data
        #
        self.concepts    = []
        self.mappings    = {}
        self.root        = None
        self.statics    = []
        self.namespace    = None
        #
        # temporary variables
        self._value     = ''
        self._lastPath     = None
        self._dbAttrObj    = None
        SaxBaseHandler.__init__(self)

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def startElement(self, name, attrs):
        name, attrs = SaxBaseHandler.startElement(self, name, attrs)
        if name == 'dataElement' and self.namespace == None:
            self.namespace = attrs.get('namespace', None)
        elif name in ['dataElement', 'attr', 'attribute']:
            if attrs.get('w3cType', None) <> None:
                self.concepts.append(attrs.get('path', None))
            self._lastPath = attrs.get('path', None)
        elif name == 'dbAttribute':
            self._dbAttrObj = CMFMappingClass()
            self._dbAttrObj.table = attrs.get('dbTable','')
            self._dbAttrObj.type = attrs.get('dbType', 'text')
            self._dbAttrObj.nosearch = isTrue(attrs.get('dbNoSearch', 0))
        elif name == 'mapDB':
            self._dbAttrObj = CMFMappingClass()
            self._dbAttrObj.attr  = attrs.get('attribute','')
            self._dbAttrObj.table = attrs.get('tablealias','')
            self._dbAttrObj.type  = attrs.get('type', 'text')
            self._dbAttrObj.nosearch = isTrue(attrs.get('nosearch', 0))
            
    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def endElement(self, name):
        name=SaxBaseHandler.endElement(self, name)
        if name == 'w3cType':
            self.concepts.append(self._lastPath)
        elif (name in ['dbAttribute','mapDB']):
            # check if this xpath has never been used before. then first set to empty list
            if not self.mappings.has_key(self._lastPath):
                self.mappings[self._lastPath] = []
            # add attribute object to this xpath list
            self.mappings[self._lastPath].append(self._dbAttrObj)
        # check if value exists for tag handler that work with real value
        if len(self._value) > 0:
            if (name == 'dbAttribute'):
                self._dbAttrObj.attr = self._value
            elif name == 'schemaNamespace':
                self._value = self._value.strip()
                self.namespace = self._value
            elif name == 'rootTableAlias':
                self.root = self._value
            elif name == 'staticTableAlias':
                self.statics.append(self._value)


        
        
# ----------------------------------------------------------------------------
if __name__ ==  "__main__":
    import xml.sax
    #ch = CMFhandler_2_1()
    ch = CMFVersionHandler()
    CMFpathname ="/Users/markus/Sites/bps2/templates/datasources/pontaurus/cmf_ABCD_2.06.xml"
    try:
        xml.sax.parse(CMFpathname, ch)
    except StopSAX:
        pass
    print "YEAH !!!"
    print ch.cmfInfoTuple
        