from com.orangescape.entity import HibernateMappingGenerator
from com.orangescape.entity import Constants
from orangescape.log import logging
from keywords import keywords

import tempfile
import os

logger = logging.getLogger('MappingGenerator.py')

class ApplicationMappingGenerator:
    appMetadata = None
    metadataRoot = None
    relationMap = dict()
    elementTable = dict()

    def __init__(self, appMetadata):
        self.appMetadata=appMetadata
        self.metadataRoot=self.appMetadata[self.appMetadata['root']]

    def addRelationship(self,sheetId,relationConfig):
        if self.relationMap.get(sheetId) is None:
            self.relationMap[sheetId]=[]
        self.relationMap.get(sheetId).append(relationConfig)

    def generateMapping(self,metadata,indexes):
        self.generateSheetMappings(metadata,indexes)
#        Update Relationships
        for sheetId in self.relationMap:
            for relation in self.relationMap[sheetId]:
                if(relation['type']=='public'):
                    oneElement=self.elementTable[sheetId]
                    manyElement=self.elementTable[relation['manySideSheetId']]
                elif(relation['type']=='system'):
                    oneElement=self.elementTable[sheetId].element("joined-subclass")
                    manyElement=self.elementTable[relation['manySideSheetId']].element("joined-subclass")
                else:
                    logger.error('relationship type \''+relation['type']+'\'undefined. Private relationship not implemented!')
                    raise NotImplementedError()
                HibernateMappingGenerator.addOneSideMapping(oneElement,self.appMetadata['root'],relation['cellId'],relation['entity-name'],relation['bagName'],relation['cellName'],relation['cascade'])
                HibernateMappingGenerator.addManySideMapping(manyElement,relation['cellName'],relation['oneSideSheetName'],relation['cellId'])

    def generateSheetMappings(self,metadata,indexes):
        for sheetId in self.metadataRoot['Application.SheetMetadata']:
            sheetMetadata = metadata.get(sheetId)
            sheetMappingGen = SheetMappingGenerator(sheetMetadata,indexes,self)
            mapping_class_element = sheetMappingGen.generateMapping()
            if mapping_class_element:
                self.elementTable[sheetId]=mapping_class_element

    def writeMappings(self,method,tempDirPath):
        for sheetId in self.elementTable:
            sheetName=self.metadataRoot['Application.SheetMetadata'][sheetId]
            mapping_xml=method(self.elementTable[sheetId])
            filepath=os.path.join(tempDirPath,sheetName+'.mapping.xml')
            map_file=file(filepath,'w')
            map_file.write(mapping_xml)
            map_file.close()

class SheetMappingGenerator:
    appGenerator = None
    generator = HibernateMappingGenerator()
    sheetMetadata = None
    metadataRoot = None
    indexes = None
    pvt_cells = None
    system_cells = None
    public_cells = None

    def __init__(self, sheetmetadata,indexes,appGenerator):
        self.appGenerator=appGenerator
        self.sheetMetadata=sheetmetadata
        self.metadataRoot=self.sheetMetadata[self.sheetMetadata['root']]
        self.indexes=indexes
        all_cell_ids = filter(lambda cellId:cellId.find('Ce')==0,self.sheetMetadata.keys())
        all_cells = map(lambda cellId:self.sheetMetadata[cellId], all_cell_ids)
        self.pvt_cells = filter(lambda cell:cell.get('CellName') is None,all_cells)
        self.system_cells = filter(lambda cell:cell['RowNumber']<0,all_cells)
        self.public_cells = filter(lambda cell:cell.get('CellName') is not None and cell['RowNumber']>0,all_cells)
        self.cell_name_list={}

    def generateMapping(self):
        if self.metadataRoot['SheetName']=='':
            logger.warn('unable to generate mapping as the model Sheet Name is empty!')
            return None
        logger.debug('generating database mapping for model \''+self.metadataRoot['SheetName']+'\'')
        map_element = self.generator.addClassMapping(self.metadataRoot['SheetName'],self.metadataRoot['Application'].keys()[0])
        self.addPublicCellMapping(map_element)
        sys_element = self.generator.addInheritedClassMapping(map_element,Constants.SYSTEM_TABLE_PREFIX+self.metadataRoot['SheetName'],self.metadataRoot['SheetName']+Constants.ID_SUFFIX)
        self.addSysCellMapping(sys_element)
        pvt_element = self.generator.addInheritedClassMapping(sys_element,Constants.PRIVATE_TABLE_PREFIX+self.metadataRoot['SheetName'],self.metadataRoot['SheetName']+Constants.ID_SUFFIX)
        self.addPvtCellMapping(pvt_element)
        return map_element

    def addConditionIndices(self):
        all_conditions = filter(lambda conditionId:conditionId.find('Condition')>-1,self.sheetMetadata.keys())

    def addPublicCellMapping(self,map_element):
        for cell in self.public_cells:
            dataType = getDataType(cell,self.metadataRoot['SheetName'],self.sheetMetadata)
            if cell.get('CellName').lower()==self.metadataRoot['SheetName'].lower()+Constants.ID_SUFFIX.lower():
                columnName =  'Duplicate_'+cell.get('CellName')
                logger.warn(cell.get('CellName')+' is a primary key for database table \''+Constants.PUBLIC_TABLE_PREFIX+self.metadataRoot['SheetName']+'\'. Using \''+columnName+'\' instead')
            elif not keywords.has_key(cell.get('CellName').lower()):
                if self.cell_name_list.has_key(cell.get('CellName')):
                    from random import randint
                    r = str(randint(1000,9999))
                    columnName=cell.get('CellName')+'_'+r
                    logger.warn(cell.get('CellName')+' is repeated in table \''+Constants.PUBLIC_TABLE_PREFIX+self.metadataRoot['SheetName']+'\'. Using \''+columnName+'\' instead')
                else:
                    columnName =  cell.get('CellName')
            else:
                columnName =  self.metadataRoot['SheetName']+'_'+cell.get('CellName')
                logger.warn(cell.get('CellName')+' is a key word for database. Using \''+columnName+'\' instead, in table \''+Constants.PUBLIC_TABLE_PREFIX+self.metadataRoot['SheetName']+'\'')
            self.cell_name_list[columnName]=columnName

            if cell.get('ReferredSheetMetadata'):
                for sheetId in cell['ReferredSheetMetadata']:
                    relationObj = {'entity-name':self.metadataRoot['SheetName'],
                                                       'type':'public',
                                                       'cellName':columnName,
                                                       'bagName':cell.get('CellName'),
                                                       'cellId':cell['SheetId'],
                                                       'manySideSheetId':self.metadataRoot['SheetId'],
                                                       'oneSideSheetName':cell['ReferredSheetMetadata'][sheetId]}
                    relationObj['cascade']="cascade" if cell.get('Cascade')==1 else "none"
                    self.appGenerator.addRelationship(sheetId,relationObj)
            else:
                isIndexed = True if self.indexes[self.metadataRoot['SheetName']].get(cell['SheetId']) else False
                columnLength = cell.get('TextLength')
                columnLength = "%d"%columnLength if columnLength else None
                isDBAutogeneratedColumn = True if columnName.lower().startswith('db_generated_') else False
                self.generator.addPropertyMapping(map_element,cell['SheetId'],dataType,columnLength,columnName,Constants.PUBLIC_TABLE_PREFIX,isIndexed,isDBAutogeneratedColumn)

    def addSysCellMapping(self,map_element):
        for cell in self.system_cells:
            dataType = getDataType(cell,self.metadataRoot['SheetName'],self.sheetMetadata)
            if cell.get('ReferredSheetMetadata'):
                for sheetId in cell['ReferredSheetMetadata']:
                    relationObj = {'entity-name':self.metadataRoot['SheetName'],
                                                       'type':'system',
                                                       'cellName':cell.get('CellName'),
                                                       'bagName':cell.get('CellName'),
                                                       'cellId':cell['SheetId'],
                                                       'manySideSheetId':self.metadataRoot['SheetId'],
                                                       'oneSideSheetName':cell['ReferredSheetMetadata'][sheetId]}
                    if cell.get('Cascade'):
                        relationObj['cascade']='cascade' #if cell.get('Cascade')==1 else 'none'
                    else:
                        relationObj['cascade']='none'
                    self.appGenerator.addRelationship(sheetId,relationObj)
            else:
                isIndexed = True if self.indexes[self.metadataRoot['SheetName']].get(cell['SheetId']) else False
                columnLength = cell.get('TextLength')
                columnLength = "%d"%columnLength if columnLength else None
                isDBAutogeneratedColumn = False    # Autogenaration will not be applicable for sysTable Columns
                self.generator.addPropertyMapping(map_element,cell['SheetId'],dataType,columnLength,cell['CellName'],Constants.SYSTEM_TABLE_PREFIX,isIndexed,isDBAutogeneratedColumn)

    def addPvtCellMapping(self,map_element):
        for cell in self.pvt_cells:
            dataType = getDataType(cell,self.metadataRoot['SheetName'],self.sheetMetadata)
            isIndexed = True if self.indexes[self.metadataRoot['SheetName']].get(cell['SheetId']) else False
            columnLength = cell.get('TextLength')
            columnLength = "%d"%columnLength if columnLength else None
            isDBAutogeneratedColumn = False    # Autogenaration will not be applicable for private Table Columns
            self.generator.addPropertyMapping(map_element,cell['SheetId'],dataType,columnLength,cell['CellDisplayName'],Constants.PRIVATE_TABLE_PREFIX,isIndexed,isDBAutogeneratedColumn)

    def getGenerator(self):
        return self.generator


def getDataType(cell,sheetName,metadata):
    if cell.get('CellName'):
        if cell.get('CellName')=='CreatedAt' or cell.get('CellName')=='ModifiedAt' or cell.get('CellName').count('Date')>0:
            return 'Date'
    if cell.get('FinalCategory')=="Category005":
        return 'Date'
    elif cell.get('FinalCategory')=="Category001" or cell.get('FinalCategory')=="Category010":
        return 'Text'
    elif cell.get('FinalCategory') is None:
        logger.warn('No Final Category specified. Assuming Number type in model '+sheetName+': Public Cell Name:'+str(cell.get('CellName'))+',Cell Display Name:'+cell.get('CellDisplayName')+',Is String:'+str(cell.get('IsStr'))+',Final Category:'+str(cell.get('FinalCategory'))+',Input Format:'+str(cell.get('FinalFormat'))+',Output Format:'+str(cell.get('OutputFormat')))
    return 'Number'