#-------------------------------------------------------------------------------
# Name:        module1
# Purpose:
#
# Author:      Tomas
#
# Created:     27/08/2017
# Copyright:   (c) Tomas 2017
# Licence:     <your licence>
#-------------------------------------------------------------------------------

from   SmartMeshSDK.ApiException  import CommandError

class FieldFormats(object):
    '''
    \brief Enumeration of possible field formats.
    '''
    STRING              = 'string'
    INT                 = 'int'
    HEXDATA             = 'hex'
    FLOAT               = 'float'

class FieldOptions(object):
    '''
    \brief Possible options for a command field
    '''

    optionName     = None
    validOptions   = None
    optionDescs    = None

    def __init__(self,optionsDef,fieldOptions,fieldName):
        self.optionDescs = []
        if   not optionsDef:
            self.validOptions = None
        else:
            if optionsDef==True:
                self.optionName = fieldName
            else:
                self.optionName = optionsDef
            self.validOptions = []
            for i in fieldOptions[self.optionName]:
               self.validOptions.append(i[0])
               self.optionDescs.append(i[1])

    def isValidValue(self,val):
        if (
            (not self.validOptions)   or
            (val in self.validOptions)
           ):
            return True
        return False

    def valueToDesc(self,val):
        if self.validOptions:
            for i in range(len(self.validOptions)):
                if self.validOptions[i]==val:
                    return self.optionDescs[i]
        raise CommandError(CommandError.VALUE_NOT_IN_OPTIONS,
                           'option='+str(self.optionName)+' val='+str(val))

class Field(object):
    '''
    \brief Object representing one field of a command.
    '''

    def __init__(self,fieldDef,fieldOptions):
        self.name       = fieldDef[0]
        self.format     = fieldDef[1]
        self.length     = fieldDef[2]
        self.options    = FieldOptions(fieldDef[3],fieldOptions,self.name)

    def isValidValue(self,val):
        # check format and length
        if   self.format==FieldFormats.STRING:
            if ( (type(val) not in [types.StringType,types.UnicodeType]) or
                 len(val)>self.length
               ):
                return False
        elif self.format==FieldFormats.BOOL:
            if type(val)!=types.BooleanType:
                return False
        elif self.format==FieldFormats.INT:
            if ( (type(val)!=types.IntType and  type(val)!=types.LongType)  or
                 val>pow(2,8*self.length)
               ):
                return False
        elif self.format==FieldFormats.INTS:
            if ( (type(val)!=types.IntType and  type(val)!=types.LongType)  or
                 val>(pow(2,8*self.length)/2) or
                 val<(-pow(2,8*self.length)/2)
               ):
                return False
        elif self.format==FieldFormats.HEXDATA:
            if type(val)!=types.ListType and type(val)!=types.TupleType:
                return False
            if self.length and len(val)>self.length:
                return False
            for i in val:
                if type(i)!=types.IntType:
                    return False
                if i>=pow(2,8):
                    return False
        elif self.format==FieldFormats.FLOAT:
            if type(val)!=types.FloatType:
                return False
        else:
            raise SystemError('unknown field format='+self.format)

        # check options
        if self.options.isValidValue(val)==False:
            return False

        return True


#class HartCommandsDefinition(object)

class hartDefinition(object):

    STRING              = 'string'

    INT                 = 'int'
    INTS                = 'ints'
    HEXDATA             = 'hex'
    FLOAT               = 'float'
    LIST                = 'list'
    RC                  = 'RC'
    FIELDS              = 'fields'
    PACKED              = 'packed'
    DATE                = 'date'

    def getCommandDescription(self, commandArray):
        # get the command
        commandDef = self.getCommand(commandArray)
        # return its description
        return commandDef['description']

    def getCommandId(self, commandArray):
        # get the command
        commandDef = self.getCommand(commandArray)
        # return its id
        return commandDef['id']

    def getCommandNames(self):
        return [command['name'] for command in self.commands]


    def getRequestFieldNames(self,commandArray):
        '''
        \brief Get the request fields of a (sub)command, from its name.

        \param commandArray   An array of the form [commandName, subCommandname]
                              The array can be of any length, and is of length 1
                              if no subcommands are used.

        \exception CommandError(NO_REQUEST) The (sub)command has
                   no request fields.
        \returns The list of request fields.
        '''

        # get the command
        commandDef = self.getCommand(commandArray)

        fields = [fieldRaw for fieldRaw in commandDef['request']]
        return [field[0] for field in fields]

    def getRequestFieldFormat(self,commandArray,fieldName):
        return self.getRequestField(commandArray,fieldName).format

    def getRequestFieldLength(self,commandArray,fieldName):
        return self.getRequestField(commandArray,fieldName).length

    def getRequestFieldOptions(self,commandArray,fieldName):
        return self.getRequestField(commandArray,fieldName).options

    def getRequestField(self,commandArray,fieldName):
        fields = self.getRequestFields(commandArray)
        for field in fields:
            if field.name==fieldName:
                return field
        raise CommandError(CommandError.UNKNOWN_FIELD,
                                        '%s in %s' % (fieldName, '.'.join(commandArray)))

    def getRequestFields(self,commandArray):
        # get the command
        commandDef = self.getCommand(commandArray)

        if 'request' not in commandDef:
            raise CommandError(CommandError.NO_REQUEST,
                                            '.'.join(commandArray))
        fields = [Field(fieldRaw,self.fieldOptions)
                             for fieldRaw in commandDef['request']]
        return fields

    def getResponseFieldNames(self, nameArray):
        '''
        \brief Get the response fields of a (sub)command, from its name.

        \param nameArray   An array of the form [commandName, subCommandname]
                           The array can be of any length, and is of length 1
                           if no subcommands are used.

        \exception CommandError(NO_RESPONSE) The (sub)command has
                   no request fields.
        \returns The list of request fields.
        '''
        fields = self.getResponseFields( nameArray)
        return [field.name for field in fields]

    def getResponseFieldFormat(self,  nameArray, fieldName):
        return self.getResponseField( nameArray, fieldName).format

    def getResponseFieldLength(self, nameArray, fieldName):
        return self.getResponseField(nameArray, fieldName).length

    def getResponseFieldOptions(self,  nameArray, fieldName):
        return self.getResponseField( nameArray, fieldName).options

    def getResponseField(self, nameArray, fieldName):
        for i in range(len(nameArray)):
            fields = self.getResponseFields(nameArray[:i + 1])
            for field in fields:
                if field.name == fieldName:
                    return field
        raise CommandError(CommandError.UNKNOWN_FIELD,
                           '%s in %s' % (fieldName, '.'.join(nameArray)))

    def getResponseFields(self, commandArray):

        # get the command
        commandDef = self.getCommand(commandArray)

        if 'response' not in commandDef:
            raise CommandError(CommandError.NO_RESPONSE,
                               '.'.join(commandArray))

        keys = commandDef['response'].keys()
        responseName = keys[0]

        fields = [Field(fieldRaw, self.fieldOptions)
                  for fieldRaw in commandDef['response'][responseName]]
        return fields

    def fieldValueToDesc(self,nameArray,fieldName,fieldValue):
        responseField = self.getResponseField(nameArray,fieldName)
        return responseField.options.valueToDesc(fieldValue)

    def getCommand(self, commandArray):
        # get the command
        for cmd in self.commands:
            if cmd['name'] == commandArray[0]:
                commandDef = cmd
        return commandDef

    def getCommandFromId(self, commandId):
        # get the command
        for cmd in self.commands:
            if cmd['id'] == commandId:
                commandDef = cmd
        return commandDef


    @classmethod
    def fieldFormatToString(self,fieldLength,fieldFormat):
        '''
        \brief Turns the field format into a human-readable string.

        \param fieldLength The number of bytes (an int) of the field
        \param fieldFormat The format of a field, expressed as a string

        \return A human-readable string.
        '''
        returnVal = ''

        if   fieldFormat==FieldFormats.INT  and fieldLength==1:
            returnVal       += 'INT8U'
        elif fieldFormat==FieldFormats.INT  and fieldLength==2:
            returnVal       += 'INT16U'
        elif fieldFormat==FieldFormats.INT  and fieldLength==4:
            returnVal       += 'INT32U'
        elif fieldFormat==FieldFormats.FLOAT:
             returnVal       += 'FLOAT'
        # elif fieldFormat==FieldFormats.INTS and fieldLength==2:
        #     returnVal       += 'INT16'
        # elif fieldFormat==FieldFormats.INTS and fieldLength==4:
        #     returnVal       += 'INT32'
        # elif fieldFormat==FieldFormats.BOOL:
        #     returnVal       += 'BOOL'
        else:
            if fieldLength:
                returnVal   += "{0}B ({1})".format(fieldLength,fieldFormat)
            else:
                returnVal   += "{0}".format(fieldFormat)

        return returnVal


   







def main():
    pass

if __name__ == '__main__':
    main()
