class AnimationId(Id, Schedule, AnimationModifierList):
    '''
    Animation identifier container class
    '''
    name = None

    def __init__(self, name, state=None):
        Id.__init__(self)
        Schedule.__init__(self)
        AnimationModifierList.__init__(self)
        self.name = name
        self.type = 'Animation'
        self.second_type = 'A'
        self.state = state

    def __repr__(self):
        state = ""
        if self.state is not None:
            state = ", {}".format(self.state)
        schedule = self.strSchedule()
        if len(schedule) > 0:
            return "A[{0}{1}]({2})".format(self.name, state, self.strSchedule())
        if len(self.modifiers) > 0:
            return "A[{0}{1}]({2})".format(self.name, state, self.strModifiers())
        return self.base_repr()

    def base_repr(self):
        '''
        Returns string of just the identifier, exclude animation modifiers
        '''
        state = ""
        if self.state is not None:
            state = ", {}".format(self.state)
        return "A[{0}{1}]".format(self.name, state)

    def width(self):
        '''
        Returns the bit width of the AnimationId

        This is currently 2 bytes.
        '''
        return 2

    def json(self):
        '''
        JSON representation of AnimationId
        '''
        output = Id.json(self)
        output.update(AnimationModifierList.json(self))
        output.update(Schedule.json(self))
        output['name'] = self.name
        output['setting'] = "{}".format(self)
        output['state'] = self.state
        del output['uid']
        return output class AnimationFrameId(Id, AnimationModifierList):
    '''
    Animation Frame identifier container class
    '''

    def __init__(self, name, index):
        Id.__init__(self)
        AnimationModifierList.__init__(self)
        self.name = name
        self.index = index
        self.type = 'AnimationFrame'

    def __repr__(self):
        return "AF[{0}, {1}]".format(self.name, self.index)

    def kllify(self):
        '''
        Returns KLL version of the Id
        '''
        return "A[{0}, {1}]".format(self.name, self.index) class CapArgId(Id):
    '''
    Capability Argument identifier
    '''

    def __init__(self, name, width=None):
        '''
        @param name:  Name of argument
        @param width: Byte-width of the argument, if None, this is not port of a capability definition
        '''
        Id.__init__(self)
        self.name = name
        self.width = width
        self.type = 'CapArg'

    def __repr__(self):
        if self.width is None:
            return "{0}".format(self.name)
        else:
            return "{0}:{1}".format(self.name, self.width)

    def json(self):
        '''
        JSON representation of CapArgId
        '''
        return {
            'name' : self.name,
            'width' : self.width,
            'type' : self.type,
        } class CapArgValue(Id):
    '''
    Capability Argument Value identifier
    '''

    def __init__(self, value):
        '''
        @param value: Value of argument
        '''
        Id.__init__(self)
        self.value = value
        self.type = 'CapArgValue'

    def __repr__(self):
        return "{}".format(self.value)

    def json(self):
        '''
        JSON representation of CapArgValue
        '''
        return {
            'value' : self.value,
            'type' : self.type,
        } class CapId(Id):
    '''
    Capability identifier
    '''

    def __init__(self, name, type, arg_list=[]):
        '''
        @param name:     Name of capability
        @param type:     Type of capability definition, string
        @param arg_list: List of CapArgIds, empty list if there are none
        '''
        Id.__init__(self)
        self.name = name
        self.type = type
        self.arg_list = arg_list

    def __repr__(self):
        # Generate prettified argument list
        arg_string = ""
        for arg in self.arg_list:
            arg_string += "{0},".format(arg)
        if len(arg_string) > 0:
            arg_string = arg_string[:-1]

        return "{0}({1})".format(self.name, arg_string)

    def json(self):
        '''
        JSON representation of CapId
        '''
        return {
            'type' : self.type,
            'name' : self.name,
            'args' : [arg.json() for arg in self.arg_list]
        }

    def total_arg_bytes(self, capabilities_dict=None):
        '''
        Calculate the total number of bytes needed for the args

        @param capabilities_dict: Dictionary of capabilities used, just in case no widths have been assigned

        return: Number of bytes
        '''
        # Zero if no args
        total_bytes = 0
        for index, arg in enumerate(self.arg_list):
            # Lookup actual width if necessary (wasn't set explicitly)
            if capabilities_dict is not None and (arg.type == 'CapArgValue' or arg.width is None):
                # Check if there are enough arguments
                expected = len(capabilities_dict[self.name].association.arg_list)
                got = len(self.arg_list)
                if got != expected:
                    print("{0} incorrect number of arguments for {1}. Expected {2} Got {3}".format(
                        ERROR,
                        self,
                        expected,
                        got,
                    ))
                    print("\t{0}".format(capabilities_dict[self.name].kllify()))
                    raise AssertionError("Invalid arguments")

                total_bytes += capabilities_dict[self.name].association.arg_list[index].width

            # Otherwise use the set width
            else:
                total_bytes += arg.width

        return total_bytes class HIDId(Id, Schedule):
    '''
    HID/USB identifier container class
    '''
    secondary_types = {
        'USBCode': 'USB',
        'SysCode': 'SYS',
        'ConsCode': 'CONS',
        'IndCode': 'IND',
    }

    kll_types = {
        'USBCode': 'U',
        'SysCode': 'SYS',
        'ConsCode': 'CONS',
        'IndCode': 'I',
    }

    type_width = {
        'USBCode': 1,
        'SysCode': 1,
        'ConsCode': 2,
        'IndCode': 1,
    }

    type_locale = {
        'USBCode': 'to_hid_keyboard',
        'SysCode': 'to_hid_sysctrl',
        'ConsCode': 'to_hid_consumer',
        'IndCode': 'to_hid_led',
    }

    def __init__(self, type, uid, locale):
        '''
        @param type:   String type of the Id
        @param uid:    Unique integer identifier for the Id
        @param locale: Locale layout object used to decode used to decode uid
        '''
        Id.__init__(self)
        Schedule.__init__(self)
        self.type = type
        self.uid = uid
        self.locale = locale
        self.locale_type = self.type_locale[self.type]

        # Set secondary type
        self.second_type = self.secondary_types[self.type]

        # Set kll type
        self.kll_type = self.kll_types[self.type]

        # Determine hex_str padding
        self.padding = 2
        if self.type == 'ConsCode':
            self.padding = 3

        # Validate uid is in locale based on what type of HID field it is
        if self.hex_str() not in self.locale.json()[self.locale_type].keys():
            print("{} Unknown HID({}) UID('{}') in locale '{}'".format(
                WARNING,
                self.type,
                self.uid,
                self.locale.name()
            ))

    def hex_str(self):
        '''
        Returns hex string used by locale for uid lookup
        '''
        return "0x{0:0{1}X}".format(self.uid, self.padding)

    def get_hex_str(self):
        '''
        Returns hex string used by locale for uid lookup, uses self.get_uid() instead of self.uid
        '''
        return "0x{0:0{1}X}".format(self.get_uid(), self.padding)

    def width(self):
        '''
        Returns the bit width of the HIDId

        This is the maximum number of bytes required for each type of HIDId as per the USB spec.
        Generally this is just 1 byte, however, Consumer elements (ConsCode) requires 2 bytes.
        '''
        return self.type_width[self.type]

    def name(self):
        '''
        Return a human readable name from the current locale
        '''

        return self.locale.json()[self.locale_type][self.hex_str()]

    def __repr__(self):
        '''
        Use string name instead of integer, easier to debug
        '''
        try:
            name = self.name()
            schedule = self.strSchedule()
            if len(schedule) > 0:
                schedule = "({0})".format(schedule)

            output = 'HID({},{})"{}"{}{}'.format(self.type, self.locale.name(), self.uid, name, schedule)
            return output
        except Exception:
            print("{} '{}' is an invalid dictionary lookup.".format(
                WARNING,
                (self.second_type, self.uid),
            ))
            return "<INVALID>"

    def json(self):
        '''
        JSON representation of HIDId
        '''
        output = Id.json(self)
        output.update(Schedule.json(self))
        return output

    def kllify(self):
        '''
        Returns KLL version of the Id
        '''
        schedule = self.strSchedule()
        if len(schedule) > 0:
            schedule = "({0})".format(schedule)

        output = "{0}{1:#05x}{2}".format(self.kll_type, self.uid, schedule)
        return output class LayerId(Id, Schedule):
    '''
    Layer identifier container class
    '''

    def __init__(self, type, layer):
        Id.__init__(self)
        Schedule.__init__(self)
        self.type = type
        self.uid = layer

    def __repr__(self):
        schedule = self.strSchedule()
        if len(schedule) > 0:
            return "{0}[{1}]({2})".format(
                self.type,
                self.uid,
                schedule,
            )
        else:
            return "{0}[{1}]".format(
                self.type,
                self.uid,
            )

    def width(self):
        '''
        Returns the bit width of the LayerId

        This is currently 2 bytes.
        '''
        return 2

    def json(self):
        '''
        JSON representation of LayerId
        '''
        output = Id.json(self)
        output.update(Schedule.json(self))
        return output

    def kllify(self):
        '''
        Returns KLL version of the Id
        '''
        # The string __repr__ is KLL in this case
        return str(self) class NoneId(CapId):
    '''
    None identifier

    It's just a capability...that does nothing (instead of infering to do something else)
    '''

    def __init__(self):
        super().__init__('None', 'None')

    def json(self):
        '''
        JSON representation of NoneId
        '''
        return {
            'type' : self.type,
        }

    def __repr__(self):
        return "None" class PixelAddressId(Id):
    '''
    Pixel address identifier container class
    '''

    def __init__(self, index=None, col=None, row=None, relCol=None, relRow=None):
        Id.__init__(self)

        # Check to make sure index, col or row is set
        if index is None and col is None and row is None and relRow is None and relCol is None:
            print("{0} index, col or row must be set".format(ERROR))

        self.index = index
        self.col = col
        self.row = row
        self.relCol = relCol
        self.relRow = relRow

        self.type = 'PixelAddress'

    def inferred_type(self):
        '''
        Determine which PixelAddressType based on set values
        '''

        if self.index is not None:
            return 'PixelAddressId_Index'
        if self.col is not None and self.row is None:
            return 'PixelAddressId_ColumnFill'
        if self.col is None and self.row is not None:
            return 'PixelAddressId_RowFill'
        if self.col is not None and self.row is not None:
            return 'PixelAddressId_Rect'
        if self.relCol is not None and self.relRow is None:
            return 'PixelAddressId_RelativeColumnFill'
        if self.relCol is None and self.relRow is not None:
            return 'PixelAddressId_RelativeRowFill'
        if self.relCol is not None and self.relRow is not None:
            return 'PixelAddressId_RelativeRect'

        print("{0} Unknown PixelAddressId, this is a bug!".format(ERROR))
        return "<UNKNOWN PixelAddressId>"

    def uid_set(self):
        '''
        Returns a tuple of uids, depends on what has been set.
        '''
        if self.index is not None:
            return tuple([self.index])
        if self.col is not None and self.row is None:
            return tuple([self.col, self.row])
        if self.col is None and self.row is not None:
            return tuple([self.col, self.row])
        if self.col is not None and self.row is not None:
            return tuple([self.col, self.row])
        if self.relCol is not None and self.relRow is None:
            return tuple([self.relCol, self.relRow])
        if self.relCol is None and self.relRow is not None:
            return tuple([self.relCol, self.relRow])
        if self.relCol is not None and self.relRow is not None:
            return tuple([self.relCol, self.relRow])

        print("{0} Unknown uid set, this is a bug!".format(ERROR))
        return "<UNKNOWN uid set"

    def merge(self, address):
        '''
        Merge in a PixelAddressId

        Will error-out instead of overwriting

        @param address: PixelAddressId object to merge in
        '''
        # Make sure we are either index or col/row
        if not self.index is None or not address.index is None:
            print("{0} Cannot merge into index PixelAddressIds".format(ERROR))
            raise NoParseError

        # Make sure we don't overwrite
        if not self.col is None and not address.col is None:
            print("{0} Duplicate column fields '{1}' '{2}'".format(ERROR, self, address))
            raise NoParseError
        if not self.row is None and not address.row is None:
            print("{0} Duplicate row fields '{1}' '{2}'".format(ERROR, self, address))
            raise NoParseError

        # Merge over values
        if not address.col is None:
            self.col = address.col
        if not address.row is None:
            self.row = address.row
        if not address.relCol is None:
            self.relCol = address.relCol
        if not address.relRow is None:
            self.relRow = address.relRow

    def valueStr(self, value):
        '''
        Prepare numerical value based on type

        @param value: Float or Integer position

        @return: String
        '''
        output = ""

        # Check if float
        if isinstance(value, float):
            output += "{0}%".format(value * 100)
        else:
            output += "{0:03d}".format(value)

        return output

    def outputStrList(self):
        '''
        List of string items used for __repr__ and kllify

        @returns: List of strings
        '''
        output = []

        # Construct representation
        if not self.index is None:
            output.append("{0}".format(self.valueStr(self.index)))
        if not self.row is None:
            cur_out = "r:{0}".format(self.valueStr(self.row))
            output.append(cur_out)
        if not self.col is None:
            cur_out = "c:{0}".format(self.valueStr(self.col))
            output.append(cur_out)
        if not self.relRow is None:
            cur_out = "r:i"
            cur_out += self.relRow >= 0 and "+" or ""
            cur_out += "{0}".format(self.valueStr(self.relRow))
            output.append(cur_out)
        if not self.relCol is None:
            cur_out = "c:i"
            cur_out += self.relCol >= 0 and "+" or ""
            cur_out += "{0}".format(self.valueStr(self.relCol))
            output.append(cur_out)

        return output

    def __repr__(self):
        return "{0}".format(self.outputStrList())

    def kllify(self):
        '''
        KLL syntax compatible output for PixelAddress object
        '''
        return ",".join(self.outputStrList()) class PixelId(Id, Position, PixelModifierList, ChannelList):
    '''
    Pixel identifier container class
    '''

    def __init__(self, uid):
        Id.__init__(self)
        Position.__init__(self)
        PixelModifierList.__init__(self)
        ChannelList.__init__(self)
        self.uid = uid
        self.type = 'Pixel'

    def unique_key(self, kll=False):
        '''
        Returns the key string used for datastructure sorting

        @param kll: Kll output format mode
        '''
        if isinstance(self.uid, HIDId) or isinstance(self.uid, ScanCodeId):
            if kll:
                return "{0}".format(self.uid.kllify())
            return "P[{0}]".format(self.uid)

        if isinstance(self.uid, PixelAddressId):
            if kll:
                return "P[{0}]".format(self.uid.kllify())
            return "P[{0}]".format(self.uid)

        if kll:
            return "P{0:#05x}".format(self.uid)

        return "P{0}".format(self.uid)

    def __repr__(self):
        # Positions are a special case
        if self.positionSet():
            return "{0} <= {1}".format(self.unique_key(), self.strPosition())

        extra = ""
        if len(self.modifiers) > 0:
            extra += "({0})".format(self.strModifiers())
        if len(self.channels) > 0:
            extra += "({0})".format(self.strChannels())
        return "{0}{1}".format(self.unique_key(), extra)

    def kllify(self):
        '''
        KLL syntax compatible output for Pixel object
        '''
        # Positions are a special case
        if self.positionSet():
            return "{0} <= {1}".format(self.unique_key(kll=True), self.strPosition())

        extra = ""
        if len(self.modifiers) > 0:
            extra += "({0})".format(self.strModifiers())
        if len(self.channels) > 0:
            extra += "({0})".format(self.strChannels())
        return "{0}{1}".format(self.unique_key(kll=True), extra) class PixelLayerId(Id, PixelModifierList):
    '''
    Pixel Layer identifier container class
    '''

    def __init__(self, uid):
        Id.__init__(self)
        PixelModifierList.__init__(self)
        self.uid = uid
        self.type = 'PixelLayer'

    def __repr__(self):
        if len(self.modifiers) > 0:
            return "PL{0}({1})".format(self.uid, self.strModifiers())
        return "PL{0}".format(self.uid) class ScanCodeId(Id, Schedule, Position):
    '''
    Scan Code identifier container class
    '''

    def __init__(self, uid):
        Id.__init__(self)
        Schedule.__init__(self)
        Position.__init__(self)
        self.type = 'ScanCode'
        self.uid = uid

        # This uid is used for any post-processing of the uid
        # The original uid is maintained in case it is needed
        self.updated_uid = None

    def inferred_type(self):
        '''
        Always returns ScanCode (simplifies code when mixed with PixelAddressId)
        '''
        return 'PixelAddressId_ScanCode'

    def get_uid(self):
        '''
        Determine uid
        May have been updated due to connect_id setting for interconnect offsets
        '''
        uid = self.uid
        if self.updated_uid is not None:
            uid = self.updated_uid

        return uid

    def uid_set(self):
        '''
        Returns a tuple of uids, always a single element for ScanCodeId
        '''
        return tuple([self.get_uid()])

    def unique_key(self):
        '''
        Returns the key string used for datastructure sorting
        '''
        # Positions are a special case
        if self.positionSet():
            return "S{0:03d}".format(self.get_uid())

    def __repr__(self):
        # Positions are a special case
        if self.positionSet():
            return "{0} <= {1}".format(self.unique_key(), self.strPosition())

        schedule = self.strSchedule()
        if len(schedule) > 0:
            return "S{0:03d}({1})".format(self.get_uid(), schedule)
        else:
            return "S{0:03d}".format(self.get_uid())

    def json(self):
        '''
        JSON representation of ScanCodeId
        '''
        output = Id.json(self)
        output.update(Schedule.json(self))
        output.update(Position.json(self))
        return output

    def kllify(self):
        '''
        Returns KLL version of the Id
        '''
        schedule = self.strSchedule()
        if len(schedule) > 0:
            schedule = "({0})".format(schedule)

        output = "S{0:#05x}{1}".format(self.get_uid(), schedule)

        # Position enabled
        if self.isPositionSet():
            output += " <= {0}".format(self.strPosition())
        return output class TriggerId(Id, Schedule):
    '''
    Generic trigger identifier container class
    '''

    def __init__(self, idcode, uid):
        Id.__init__(self)
        Schedule.__init__(self)
        self.type = 'GenericTrigger'
        self.uid = uid
        self.idcode = idcode

    def __repr__(self):
        schedule = self.strSchedule()
        schedule_val = ""
        if len(schedule) > 0:
            schedule_val = "({})".format(schedule)

        return "T[{0},{1}]{2}".format(
            self.idcode,
            self.uid,
            schedule_val,
        )

    def json(self):
        '''
        JSON representation of TriggerId
        '''
        output = Id.json(self)
        output.update(Schedule.json(self))
        output['idcode'] = self.idcode
        return output

    def kllify(self):
        '''
        Returns KLL version of the Id
        '''
        # The string __repr__ is KLL in this case
        return str(self) class UTF8Id(Id, Schedule):
    '''
    Unicode/UTF-8 identifier container class

    This Id handles UTF-8 strings differently depending on whether there is a single character string
    or a multi character string.
    Single character strings can be associated with Press/Hold/Release events.
    Multi character strings are used more for single shot typing events.
    '''
    def __init__(self, ustr):
        '''
        @param ustr: UTF-8 string
        '''
        Id.__init__(self)
        Schedule.__init__(self)

        # Select the type based on the length of the UTF-8 string
        if len(ustr) == 1:
            # Single character
            self.type = 'UTF8State'
        else:
            self.type = 'UTF8Text'
        self.uid = ustr

    def __repr__(self):
        schedule = self.strSchedule()
        if schedule:
            return "u'{0}'({1})".format(self.uid, schedule)
        return "u'{0}'".format(self.uid)

    def width(self):
        '''
        Returns the bit width of the UTF8Id

        This is currently 2 bytes.
        '''
        return 2

    def json(self):
        '''
        JSON representation of UTF8Id
        '''
        output = Id.json(self)
        output.update(Schedule.json(self))
        return output

    def kllify(self):
        '''
        Returns KLL version of the Id

        For single characters, use code point format (U+123A)
        For multiple characters, use a UTF-8 string
        '''
        unicode_output = "u'{}'".format(self.uid)
        if len(self.uid) == 1:
            unicode_output = "U+{:X}".format(ord(self.uid[0]))

        schedule = self.strSchedule()
        if schedule:
            return "{0}({1})".format(unicode_output, schedule)
        return "{0}".format(unicode_output) class AnimationModifierList:
    '''
    Animation modification container list class

    Contains a list of modifiers, the order does not matter
    '''
    frameoption_modifiers = [
        'framestretch',
    ]

    def __init__(self):
        self.modifiers = []

    def setModifiers(self, modifier_list):
        '''
        Apply modifiers to Animation
        '''
        for modifier in modifier_list:
            self.modifiers.append(AnimationModifier(modifier[0], modifier[1]))

    def clean(self, new_modifier, new, old):
        '''
        Remove conflicting modifier if necessary
        '''
        if new_modifier.name == new:
            for index, modifier in enumerate(self.modifiers):
                if modifier.name == old:
                    return False
        return True

    def replace(self, new_modifier):
        '''
        Replace modifier

        If it doesn't exist already, just add it.
        '''
        # If new_modifier is loops and loop exists, remove loop
        if not self.clean(new_modifier, 'loops', 'loop'):
            return
        # If new_modifier is loop and loops exists, remove loops
        if not self.clean(new_modifier, 'loop', 'loops'):
            return

        # Check for modifier
        for modifier in self.modifiers:
            if modifier.name == new_modifier.name:
                modifier.value = new_modifier.value
                return

        # Otherwise just add it
        self.modifiers.append(new_modifier)

    def getModifier(self, name):
        '''
        Retrieves modifier

        Returns False if doesn't exist
        Returns argument if exists and has an argument, may be None
        '''
        for mod in self.modifiers:
            if mod.name == name:
                return mod.value
        return False

    def strModifiers(self):
        '''
        __repr__ of Position when multiple inheritance is used
        '''
        output = ""
        for index, modifier in enumerate(sorted(self.modifiers, key=lambda x: x.name)):
            if index > 0:
                output += ","
            output += "{0}".format(modifier)

        return output

    def __repr__(self):
        return self.strModifiers()

    def json(self):
        '''
        JSON representation of AnimationModifierList
        '''
        output = {
            'modifiers' : [],
        }
        # Output sorted list of modifiers
        for modifier in sorted(self.modifiers, key=lambda x: x.name):
            output['modifiers'].append(modifier.json())

        # Look for any frameoption modifiers
        frameoption_list = []
        for modifier in self.modifiers:
            if modifier.name in self.frameoption_modifiers:
                frameoption_list.append(modifier.name)
        output['frameoptions'] = frameoption_list
        return output

    def kllify(self):
        '''
        Returns KLL version of the ModifierList

        In most cases we can just the string representation of the object
        '''
        return "{0}".format(self) class AnalogScheduleParam(ScheduleParam):
    '''
    Analog Schedule Parameter

    Accepts:
    Value from 0 to 100, indicating a percentage pressed

    XXX: Might be useful to accept decimal percentages
    '''

    def __repr__(self):
        output = ""
        if self.state is not None:
            output += "{0}".format(self.state)
        if self.state is not None and self.timing is not None:
            output += ":"
        if self.timing is not None:
            output += "{0}".format(self.timing)
        return output

    def kllify(self):
        '''
        KLL representation of object
        '''
        return "{0}".format(self.state) class ScheduleParam:
    '''
    Schedule parameter

    In the case of a Timing parameter, the base type is unknown and must be inferred later
    '''

    def __init__(self, state, timing=None):
        '''
        @param state: State identifier (string)
        @param timing: Timing parameter
        '''
        self.state = state
        self.timing = timing
        self.parent = None

    def setType(self, parent):
        '''
        Change class type to match the Schedule object

        @param parent: Parent Schedule object
        '''
        self.parent = parent

        if self.parent.__class__.__name__ in ["HIDId"] and self.parent.type == 'IndCode':
            self.__class__ = IndicatorScheduleParam
        elif self.parent.__class__.__name__ in ["LayerId"]:
            self.__class__ = IndicatorScheduleParam
        elif self.parent.__class__.__name__ in ["HIDId", "ScanCodeId", "TriggerId"]:
            # Check if an analog
            if isinstance(self.state, numbers.Number):
                self.__class__ = AnalogScheduleParam
            else:
                self.__class__ = ButtonScheduleParam
        elif self.parent.__class__.__name__ in ["AnimationId"]:
            self.__class__ = AnimationScheduleParam

    def checkParam(self):
        '''
        Validate that parameter is valid

        @returns: If assigned state is valid for the assigned class
        '''
        # Check for invalid state
        invalid_state = True

        if self.state in ['P', 'H', 'R', 'O', 'UP', 'UR'] and self.__class__.__name__ == 'ButtonScheduleParam':
            invalid_state = False
        elif self.state in ['A', 'On', 'D', 'Off'] and self.__class__.__name__ == 'IndicatorScheduleParam':
            invalid_state = False
        elif self.state in ['D', 'R', 'O'] and self.__class__.__name__ == 'AnimationScheduleParam':
            invalid_state = False
        elif isinstance(self.state, numbers.Number) and self.__class__.__name__ == 'AnalogScheduleParam':
            invalid_state = False
        elif self.state is None and self.timing is not None:
            invalid_state = False

        if invalid_state:
            print("{0} Invalid {2} state '{1}'".format(ERROR, self.state, self.__class__.__name__))
        return not invalid_state

    def setTiming(self, timing):
        '''
        Set parameter timing
        '''
        self.timing = timing

    def json(self):
        '''
        JSON representation of ScheduleParam
        '''
        output = dict()
        output['state'] = self.state
        output['timing'] = self.timing
        return output

    def kllify(self):
        '''
        KLL representation of ScheduleParam object
        '''
        output = ""
        if self.state is None and self.timing is not None:
            output += "{0}".format(self.timing)
        return output

    def __repr__(self):
        output = ""
        if self.state is None and self.timing is not None:
            output += "{0}".format(self.timing)
        else:
            output += "??"
            print("{0} Unknown ScheduleParam state '{1}'".format(ERROR, self.state))
        return output class Time:
    '''
    Time parameter
    '''

    def __init__(self, time, unit):
        self.time = time
        self.unit = unit

    def __repr__(self):
        return "{0}{1}".format(self.time, self.unit) class Token(object):
    def __init__(self, type, value, start=None, end=None):
        self.type = type
        self.value = value
        self.start = start
        self.end = end

    def __repr__(self):
        return 'Token(%r, %r)' % (self.type, self.value)

    def __eq__(self, other):
        # FIXME: Case sensitivity is assumed here
        return self.type == other.type and self.value == other.value

    def _pos_str(self):
        if self.start is None or self.end is None:
            return ''
        else:
            sl, sp = self.start
            el, ep = self.end
            return '%d,%d-%d,%d:' % (sl, sp, el, ep)

    def __str__(self):
        s = "%s %s '%s'" % (self._pos_str(), self.type, self.value)
        return s.strip()

    @property
    def name(self):
        return self.value

    def pformat(self):
        return "%s %s '%s'" % (self._pos_str().ljust(20),
                                self.type.ljust(14),
                                self.value) def some(pred):
    """(a -> bool) -> Parser(a, a)

    Returns a parser that parses a token if it satisfies a predicate pred.
    """

    @Parser
    def _some(tokens, s):
        if s.pos >= len(tokens):
            raise NoParseError('no tokens left in the stream', s)
        else:
            t = tokens[s.pos]
            if pred(t):
                pos = s.pos + 1
                s2 = State(pos, max(pos, s.max))
                if debug:
                    log.debug('*matched* "%s", new state = %s' % (t, s2))
                return t, s2
            else:
                if debug:
                    log.debug('failed "%s", state = %s' % (t, s))
                raise NoParseError('got unexpected token', s, t)

    _some.name = '(some)'
    return _some def a(value):
    """Eq(a) -> Parser(a, a)

    Returns a parser that parses a token that is equal to the value value.
    """
    name = getattr(value, 'name', value)
    return some(lambda t: t == value).named('(a "%s")' % (name,)) def many(p):
    """Parser(a, b) -> Parser(a, [b])

    Returns a parser that infinitely applies the parser p to the input sequence
    of tokens while it successfully parsers them. The resulting parser returns a
    list of parsed values.
    """

    @Parser
    def _many(tokens, s):
        """Iterative implementation preventing the stack overflow."""
        res = []
        try:
            while True:
                (v, s) = p.run(tokens, s)
                res.append(v)
        except NoParseError as e:
            return res, State(s.pos, e.state.max)

    _many.name = '{ %s }' % p.name
    return _many def oneplus(p):
    """Parser(a, b) -> Parser(a, [b])

    Returns a parser that applies the parser p one or more times.
    """
    q = p + many(p) >> (lambda x: [x[0]] + x[1])
    return q.named('(%s , { %s })' % (p.name, p.name)) def skip(p):
    """Parser(a, b) -> Parser(a, _Ignored(b))

    Returns a parser which results are ignored by the combinator +. It is useful
    for throwing away elements of concrete syntax (e. g. ",", ";").
    """
    return p >> _Ignored def maybe(p):
    """Parser(a, b) -> Parser(a, b or None)

    Returns a parser that retuns None if parsing fails.

    NOTE: In a statically typed language, the type Maybe b could be more
    approprieate.
    """
    return (p | pure(None)).named('[ %s ]' % (p.name,))
from kll.common.id import (
    AnimationId, AnimationFrameId,
    CapArgId, CapArgValue, CapId,
    HIDId,
    LayerId,
    NoneId,
    PixelAddressId, PixelId, PixelLayerId,
    ScanCodeId,
    TriggerId,
    UTF8Id
)
from kll.common.modifier import AnimationModifierList
from kll.common.schedule import AnalogScheduleParam, ScheduleParam, Time
from kll.extern.funcparserlib.lexer import Token
from kll.extern.funcparserlib.parser import (some, a, many, oneplus, skip, maybe)
        'abb' -> U"A", U"B", U"NoEvent", U"B"

        @param spec: 'lspec' or 'rspec'
        '''
        # Determine locale
        locale = token.locale

        # Compose string using set locale
        sequence = None
        if spec == 'lspec':
            sequence = locale.compose(token.value[1:-1], minimal_clears=True, no_clears=True)
        else:
            sequence = locale.compose(token.value[1:-1], minimal_clears=True)

        # Convert each element in sequence of combos to HIDIds
        hid_ids = []
        for combo in sequence:
            new_combo = []
            for elem in combo:
                # Lookup uid (usb code) from alias name (used in sequence)
                new_elem = HIDId('USBCode', int(locale.json()['from_hid_keyboard'][elem], 0), locale)
                new_combo.append(new_elem)
            hid_ids.append(new_combo)

        return hid_ids

    def seqStringL(token):
        '''
        Converts sequence string to a sequence of combinations
        lspec side

        'Ab'  -> U"Shift" + U"A", U"B"
        'abb' -> U"A", U"B", U"NoEvent", U"B"
        '''
        return Make.seqString(token, 'lspec')

    def seqStringR(token):
        '''
        Converts sequence string to a sequence of combinations
        rspec side

        'Ab'  -> U"Shift" + U"A", U"B"
        'abb' -> U"A", U"B", U"NoEvent", U"B"
        '''
        return Make.seqString(token, 'rspec')

    def string(token):
        '''
        Converts a raw string to a Python string

        "this string" -> this string
        '''
        return token[1:-1]

    def unseqString(token):
        '''
        Converts a raw sequence string to a Python string

        'this string' -> this string
        '''
        return token[1:-1]

    def number(token):
        '''
        Convert string number to Python integer
        '''
        return int(token, 0)

    def neg_number(dash, number):
        '''
        If a dash is provided, then the number is negative
        '''
        if dash is not None:
            number = number * -1
        return number

    def numberToken(token):
        '''
        Convert token value to Python integer
        '''
        try:
            token.value = int(token.value, 0)
        except TypeError:
            pass
        return token

    def percent(token):
        '''
        Convert string percent to Python float
        '''
        return int(token[:-1], 0) / 100.0

    def timing(token):
        '''
        Convert raw timing parameter to integer time and determine units

        1ms -> 1, ms
        '''
        # Find ms, us, or s
        if 'ms' in token:
            unit = 'ms'
            num = token.split('m')[0]
        elif 'us' in token:
            unit = 'us'
            num = token.split('u')[0]
        elif 'ns' in token:
            unit = 'ns'
            num = token.split('n')[0]
        elif 's' in token:
            unit = 's'
            num = token.split('s')[0]
        else:
            print("{0} cannot find timing unit in token '{1}'".format(ERROR, token))

        return Time(float(num), unit)

    def specifierTiming(timing):
        '''
        When only timing is given, infer state at a later stage from the context of the mapping
        '''

        return ScheduleParam(None, timing)