import sys
import logging
from orangescape.model.server.util.SerializeUtil import getDisplayCellName
from orangescape.model.engine.exception.Error import ReportEngineError
from orangescape.model.engine.exception.ErrorMessage import Report1000

MAX = 'max'
MIN = 'min'
SUM = 'sum'
AVG = 'avg'
COUNT = 'count'
defaultValues = {MIN:float(sys.maxint), MAX:float(-sys.maxint), SUM:0.0, AVG:(0.0, 0.0), COUNT:0.0}
SUM_FN = lambda x, y:x+(y or 0.0)
COUNT_FN = lambda x, y:x+(1.0 if y else 0.0)
execute_operations = {MIN:lambda x, y:min(x, y), MAX:lambda x, y:max(x, y), SUM:SUM_FN, COUNT:COUNT_FN, AVG:lambda x, y:(SUM_FN(x[0], y), COUNT_FN(x[1], y))}

class AggregateQuery():
    def __init__(self, session, metadata, entities, measures, dimensions, operations):
        self.session = session
        self.metadata = metadata
        self.entities = entities
        self.measures = measures
        self.dimensions = dimensions
        self.operations = operations

        if len(measures)!=len(operations):
            raise ReportEngineError, Report1000%(measures, operations)
        self.totalMeasures = len(measures)
        self.totalDimensions = len(dimensions)
        self.countIndex = -1

        self.replaceManyToOneIds = False
        self.manyToOneDimensions = {}
        self.manyToOneMeasures = {}
        self.getMultiIds = {}
        self.defaultGroup = None

        self.resolveOperationIndex()
        self.resolveManyToOne()

    def resolveOperationIndex(self):
        self.defaultGroup = [defaultValues[operation] for operation in self.operations]

    def resolveManyToOne(self):
        dimensionsCopy = dict([(d, d) for d in self.dimensions])
        for i in xrange(self.totalDimensions):
            cellName = self.dimensions[i]
            if cellName.find('.')>0:
                """Assign reference cell in dimension dict and fetch cell in many-to-one dimension dict with same index(i)"""
                self.dimensions[i], self.manyToOneDimensions[i] = cellName.split('.')
                if not self.replaceManyToOneIds:
                    """Avoid check if self.replaceManyToOneIds is already True"""
                    self.replaceManyToOneIds = not dimensionsCopy.has_key(self.dimensions[i])
            else:
                cell = self.metadata.getPublicCellMetadata(cellName)
                if cell.isReferenceCell():
                    self.manyToOneDimensions[i] = getDisplayCellName(cell)

        for i in xrange(self.totalMeasures):
            cellName = self.measures[i]
            if cellName.find('.')>0:
                """Assign reference cell in measures dict and fetch cell in many-to-one measures dict with same index(i)"""
                self.measures[i], self.manyToOneMeasures[i] = cellName.split('.')
                if not self.replaceManyToOneIds:
                    """Avoid check if self.replaceManyToOneIds is already True"""
                    self.replaceManyToOneIds = not dimensionsCopy.has_key(self.measures[i])
            else:
                cell = self.metadata.getPublicCellMetadata(cellName)
                if cell.isReferenceCell():
                    self.manyToOneMeasures[i] = getDisplayCellName(cell)

    def calculateAvg(self, data):
        """Execute average operation if exists"""
        if AVG in self.operations:
            for i in xrange(self.totalMeasures):
                if self.operations[i]==AVG:
                    data[i] = (data[i][0]/data[i][1]) if data[i][1] else 0.0

#    def __construct_composite_key__(self, entity):
#        compKey = list()
#        for i in xrange(self.totalDimensions):
#            column = self.dimensions[i]
#            value = entity.getValue(column)
#            if value and self.manyToOneDimensions.has_key(i):
#                self.getMultiIds[value] = entity.getCellMetadata(column).getReferredSheetMetadata().getSheetId()
#            compKey.append(value)
#        return tuple(compKey)
#
#    def resolve_many_to_one_values(self, groupByData):
#        manyToOneEntities = self.session.getMulti(self.getMultiIds)
#        rowList = []
#
#        for compositeKey, data in groupByData.iteritems():
#            row = list(compositeKey)
#            for index, manyToOneCellName in self.manyToOneDimensions.iteritems():
#                foreignKeyValue = row[index]
#                if foreignKeyValue:
#                    parentRecord = manyToOneEntities.get(foreignKeyValue)
#                    if parentRecord:
#                        row[index] = parentRecord.getValue(manyToOneCellName)
#                    else:
#                        logging.warn('Parent record not found for the id %s'%(foreignKeyValue))
#            self.calculateAvg(data)
#            row.extend(data)
#            rowList.append(row)
#        return rowList
#
#    def execute(self):
#        if self.replaceManyToOneIds:
#            """Code is rewritten even for slight change because even a simple if inside a loop will be a performance problem
#            This is to many to one cells exists in measures & dimensions but with out reference its corresponding ref cell in dimension"""
#            return self.execute2()
#        if self.manyToOneMeasures:
#            """Code is rewritten even for slight change because even a simple if inside a loop will be a performance problem
#            This is to many to one cells exists in measures & dimensions its corresponding ref cell exists in dimension also"""
#            self.countIndex = self.totalMeasures
#            self.defaultGroup.append(defaultValues[COUNT])
#            return self.execute3()
#        groupByData = {}
#        for entity in self.entities:
#            compKey = self.__construct_composite_key__(entity)
#            group = groupByData.setdefault(compKey, list(self.defaultGroup))
#            for i in xrange(self.totalMeasures):
#                group[i] = execute_operations[self.operations[i]](group[i], entity.getValue(self.measures[i]))
#            if self.countIndex == self.totalMeasures:
#                group[self.countIndex]+=1
#        rs = self.resolve_many_to_one_values(groupByData)
#        return rs

    def construct_composite_key2(self, entity, getMulti):
        compKey = list()
        for i in xrange(self.totalDimensions):
            column = self.dimensions[i]
            value = entity.getValue(column)
            if value and self.manyToOneDimensions.has_key(i):
                parent = getMulti.get(value)
                value = parent.getValue(self.manyToOneDimensions[i]) if parent else None
            compKey.append(value)
        return tuple(compKey)

    def resolve_rows(self, groupByData):
        rowList = []
        for compositeKey, data in groupByData.iteritems():
            row = list(compositeKey)
            self.calculateAvg(data)
            row.extend(data)
            rowList.append(row)
        return rowList

    def execute(self):
        """Code is rewritten even for slight change because even a simple if inside a loop will be a performance problem
        This is to many to one cells exists in measures & dimensions but with out reference its corresponding ref cell in dimension"""
        manyToOnes = set(self.dimensions[i] for i in self.manyToOneDimensions).union(self.measures[i] for i in self.manyToOneMeasures)
        for entity in self.entities:
            for manyToOneCell in manyToOnes:
                if entity.getValue(manyToOneCell):
                    self.getMultiIds[entity.getValue(manyToOneCell)] = entity.getCellMetadata(manyToOneCell).getReferredSheetMetadata().getSheetId()
        getMulti = self.session.getMulti(self.getMultiIds)
        groupByData = {}
        for entity in self.entities:
            compKey = self.construct_composite_key2(entity, getMulti)
            group = groupByData.setdefault(compKey, list(self.defaultGroup))
            for i in xrange(self.totalMeasures):
                value = entity.getValue(self.measures[i])
                if value and self.manyToOneMeasures.has_key(i):
                    parent = getMulti.get(value)
                    value = parent.getValue(self.manyToOneMeasures[i]) if parent else None
                group[i] = execute_operations[self.operations[i]](group[i], value)

        return self.resolve_rows(groupByData)

#    def resolve_many_to_one_values3(self, groupByData):
#        manyToOneEntities = self.session.getMulti(self.getMultiIds)
#        rowList = []
#
#        for compositeKey, data in groupByData.iteritems():
#            row = list(compositeKey)
#            for index, manyToOneCellName in self.manyToOneDimensions.iteritems():
#                foreignKeyValue = row[index]
#                if foreignKeyValue:
#                    parentRecord = manyToOneEntities.get(foreignKeyValue)
#                    if parentRecord:
#                        row[index] = parentRecord.getValue(manyToOneCellName)
#                    else:
#                        logging.warn('Parent record not found for the id %s'%(foreignKeyValue))
#
#            for i in xrange(self.totalMeasures):
#                if self.manyToOneMeasures.has_key(i):
#                    value = None
#                    if data[i]:
#                        parent = manyToOneEntities.get(data[i])
#                        value = parent.getValue(self.manyToOneMeasures[i])
#                    if self.operations[i]==SUM:
#                        data[i] = (value or 0.0) * data[self.countIndex]
#                    else:
#                        data[i] = value
#                elif self.operations[i]==AVG:
#                    data[i] = (data[i][0]/data[i][1]) if data[i][1] else 0.0
#
#            row.extend(data)
#            rowList.append(row)
#        return rowList
#
#    def execute3(self):
#        """Code is rewritten even for slight change because even a simple if inside a loop will be a performance problem
#        This is to many to one cells exists in measures & dimensions its corresponding ref cell exists in dimension also"""
#
#        groupByData = {}
#        for entity in self.entities:
#            compKey = self.__construct_composite_key__(entity)
#            group = groupByData.setdefault(compKey, list(self.defaultGroup))
#            for i in xrange(self.totalMeasures):
#                group[i] = entity.getValue(self.measures[i]) if self.manyToOneMeasures.has_key(i) else execute_operations[self.operations[i]](group[i], entity.getValue(self.measures[i]))
#            if self.countIndex == self.totalMeasures:
#                group[self.countIndex]+=1
#        rs = self.resolve_many_to_one_values3(groupByData)
#        return rs
