""" Within this module is defined the class from which all other accounting types are defined
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__RCSID__ = "$Id$"

import six
from DIRAC import S_OK, S_ERROR
from DIRAC.Core.Utilities import Time
from DIRAC.Core.DISET.RPCClient import RPCClient
from DIRAC.AccountingSystem.Client.DataStoreClient import gDataStoreClient


class BaseAccountingType(object):
    def __init__(self):
        self.keyFieldsList = []
        self.valueFieldsList = []
        self.valuesList = []
        self.fieldsList = []
        self.startTime = 0
        self.endTime = 0
        self.dataTimespan = 0
        self.bucketsLength = [
            (86400 * 8, 3600),  # <1w+1 = 1h
            (15552000, 86400),  # >1w+1d <6m = 1d
            (31104000, 604800),  # >6m = 1w
        ]
        self.definitionKeyFields = []
        self.definitionAccountingFields = []

    def checkType(self):
        """
        Check that everything is defined
        """
        if not self.definitionKeyFields:
            raise Exception("definitionKeyFields has to be filled prior to utilization")
        if not self.definitionAccountingFields:
            raise Exception("definitionAccountingFields has to be filled prior to utilization")
        for key in self.definitionKeyFields:
            self.keyFieldsList.append(key[0])
        for value in self.definitionAccountingFields:
            self.valueFieldsList.append(value[0])
        self.fieldsList = []
        self.fieldsList.extend(self.keyFieldsList)
        self.fieldsList.extend(self.valueFieldsList)
        if len(self.valuesList) != len(self.fieldsList):
            self.valuesList = [None] * len(self.fieldsList)

    def getDataTimespan(self):
        """
        Get the data timespan for the time. Data older than dataTimespan will be deleted
        """
        return self.dataTimespan

    def setStartTime(self, startTime=False):
        """
        Give a start time for the report
        By default use now
        """
        if not startTime:
            self.startTime = Time.dateTime()
        else:
            self.startTime = startTime

    def setEndTime(self, endTime=False):
        """
        Give a end time for the report
        By default use now
        """
        if not endTime:
            self.endTime = Time.dateTime()
        else:
            self.endTime = endTime

    def setNowAsStartAndEndTime(self):
        """
        Set current time as start and end time of the report
        """
        self.startTime = Time.dateTime()
        self.endTime = self.startTime

    def setValueByKey(self, key, value):
        """
        Add value for key
        """
        if key not in self.fieldsList:
            return S_ERROR("Key %s is not defined" % key)
        keyPos = self.fieldsList.index(key)
        self.valuesList[keyPos] = value
        return S_OK()

    def setValuesFromDict(self, dataDict):
        """
        Set values from key-value dictionary
        """
        errKeys = []
        for key in dataDict:
            if key not in self.fieldsList:
                errKeys.append(key)
        if errKeys:
            return S_ERROR("Key(s) %s are not valid" % ", ".join(errKeys))
        for key in dataDict:
            self.setValueByKey(key, dataDict[key])
        return S_OK()

    def getValue(self, key):
        try:
            return S_OK(self.valuesList[self.fieldsList.index(key)])
        except IndexError:
            return S_ERROR("%s does not have a value" % key)
        except ValueError:
            return S_ERROR("%s is not a valid key" % key)

    def checkValues(self):
        """
        Check that all values are defined and valid
        """
        errorList = []
        for i in range(len(self.valuesList)):
            key = self.fieldsList[i]
            if self.valuesList[i] is None:
                errorList.append("no value for %s" % key)
            if key in self.valueFieldsList and not isinstance(self.valuesList[i], six.integer_types + (float,)):
                errorList.append("value for key %s is not numerical type" % key)
        if errorList:
            return S_ERROR("Invalid values: %s" % ", ".join(errorList))
        if not self.startTime:
            return S_ERROR("Start time has not been defined")
        if not isinstance(self.startTime, Time._dateTimeType):
            return S_ERROR("Start time is not a datetime object")
        if not self.endTime:
            return S_ERROR("End time has not been defined")
        if not isinstance(self.endTime, Time._dateTimeType):
            return S_ERROR("End time is not a datetime object")
        return self.checkRecord()

    def checkRecord(self):
        """To be overwritten by child class"""
        return S_OK()

    def getDefinition(self):
        """
        Get a tuple containing type definition
        """
        return (self.__class__.__name__, self.definitionKeyFields, self.definitionAccountingFields, self.bucketsLength)

    def getValues(self):
        """
        Get a tuple containing report values
        """
        return (self.__class__.__name__, self.startTime, self.endTime, self.valuesList)

    def getContents(self):
        """
        Get the contents
        """
        cD = {}
        if self.startTime:
            cD["starTime"] = self.startTime
        if self.endTime:
            cD["endTime"] = self.endTime
        for iPos in range(len(self.fieldsList)):
            if self.valuesList[iPos]:
                cD[self.fieldsList[iPos]] = self.valuesList[iPos]
        return cD

    def registerToServer(self):
        """
        Register type in server
        """
        rpcClient = RPCClient("Accounting/DataStore")
        return rpcClient.registerType(*self.getDefinition())

    def commit(self):
        """
        Commit register to server
        """
        retVal = gDataStoreClient.addRegister(self)
        if not retVal["OK"]:
            return retVal
        return gDataStoreClient.commit()

    def delayedCommit(self):
        """
        Commit register to the server. Delayed commit allows to speed up
        the operation as more registers will be sent at once.
        """

        retVal = gDataStoreClient.addRegister(self)
        if not retVal["OK"]:
            return retVal
        return gDataStoreClient.delayedCommit()

    def remove(self):
        """
        Remove a register from server
        """
        return gDataStoreClient.remove(self)
