# Source code for canlib.kvadblib.attributedef

    import ctypes as ct

    from collections import namedtuple

    from . import enums
    from .exceptions import KvdNoAttribute
    from .wrapper import dll

    MinMaxDefinition = namedtuple('MinMaxDefinition', 'default min max')
    """Attribute definition for attributes defined using default, min and max."""

    DefaultDefinition = namedtuple('DefaultDefinition', 'default')
    """Attribute definition for attributes defined using only default."""

    EnumDefaultDefinition = namedtuple('EnumDefaultDefinition', 'default enums')
    """Attribute definition for enumeration attributes.

    Holds a definition using default and key-value pairs.
    """

    class AttributeDefinition(object):
        """Factory for creating different types of attribute definitions.

        This class is also the base class and thus contains all common properties.
        """
        def __new__(cls, db, handle, definition=None):
            """Create attribute definition class depending on type."""
            if cls != AttributeDefinition:
                obj = super(AttributeDefinition, cls).__new__(cls)
                return obj
            type = ct.c_int()
            dll.kvaDbGetAttributeDefinitionType(handle, ct.byref(type))
            if type.value == enums.AttributeType.INTEGER:
                cls_ = IntegerDefinition
            elif type.value == enums.AttributeType.FLOAT:
                cls_ = FloatDefinition
            elif type.value == enums.AttributeType.STRING:
                cls_ = StringDefinition
            elif type.value == enums.AttributeType.ENUM:
                cls_ = EnumDefinition
            else:
                type = enums.AttributeType(type.value)
                raise NotImplementedError('{} not implemented'.format(type.name))

            return cls_.__new__(cls_, db, handle, definition)

        def __init__(self, db, handle):
            self._handle = handle
            self._db = db

        def __repr__(self):
            txt = ("<{}(name='{}', definition={}, owner={!r})>".
                   format(self.__class__.__name__, self.name, self.definition, self.owner))
            return txt

        @property
        def name(self):
            """`str`: Name of attribute definition."""
            buf = ct.create_string_buffer(255)
            dll.kvaDbGetAttributeDefinitionName(
                self._handle, buf, ct.sizeof(buf))
            return buf.value.decode('utf-8')

        @name.setter
        def name(self, value):
            dll.kvaDbSetAttributeDefinitionName(
                self._handle, value.encode('utf-8'))

        @property
        def owner(self):
            """`AttributeOwner`: Return attribute owner"""
            owner = ct.c_int(0)
            dll.kvaDbGetAttributeDefinitionOwner(
                self._handle, ct.byref(owner))
            return enums.AttributeOwner(owner.value)

        @owner.setter
        def owner(self, owner):
            dll.kvaDbSetAttributeDefinitionOwner(self._handle, owner)

    class FloatDefinition(AttributeDefinition):
        """Definition of a float attribute."""

        def __init__(self, db, handle, definition=None):
            """Holds a float attribute definition.

            Args:
                db (:obj:`Dbc`): Database that holds attribute definitions
                definition (:obj:`MinMaxDefinition`): Min, max and default values
            """
            super(FloatDefinition, self).__init__(db, handle)
            # self._ah = None
            if definition is not None:
                self.definition = definition

        @property
        def definition(self):
            """`MinMaxDefinition`: Attribute definition"""
            default = ct.c_float()
            min = ct.c_float()
            max = ct.c_float()
            dll.kvaDbGetAttributeDefinitionFloat(
                self._handle, default, min, max)
            definition = MinMaxDefinition(default=default.value,
                                          min=min.value, max=max.value)
            return definition

        @definition.setter
        def definition(self, value):
            dll.kvaDbSetAttributeDefinitionFloat(
                self._handle, value.default, value.min, value.max)

    class IntegerDefinition(AttributeDefinition):
        """Definition of an integer attribute."""

        def __init__(self, db, handle, definition=None):
            """Define an integer attribute.

            Args:
                db (:obj:`Dbc`): Database that holds attribute definitions
                definition (:obj:`MinMaxDefinition`): Min, max and default values
            """
            super(IntegerDefinition, self).__init__(db, handle)
            if definition is not None:
                self.definition = definition

        @property
        def definition(self):
            """`MinMaxDefinition`: Return attribute definition"""
            default = ct.c_int()
            min = ct.c_int()
            max = ct.c_int()
            dll.kvaDbGetAttributeDefinitionInt(
                self._handle, default, min, max)
            definition = MinMaxDefinition(default=default.value,
                                          min=min.value, max=max.value)
            return definition

        @definition.setter
        def definition(self, value):
            dll.kvaDbSetAttributeDefinitionInt(
                self._handle, value.default, value.min, value.max)

    class StringDefinition(AttributeDefinition):
        """Definition of a string attribute."""

        def __init__(self, db, handle, definition=None):
            """Define a string attribute.

            Args:
                db (:obj:`Dbc`): Database that holds attribute definitions
                definition (:obj:`DefaultDefinition`): default value
            """
            super(StringDefinition, self).__init__(db, handle)
            if definition is not None:
                self.definition = definition

        @property
        def definition(self):
            """`DefaultDefinition`: Return attribute definition"""
            c_default = ct.create_string_buffer(255)
            dll.kvaDbGetAttributeDefinitionString(
                self._handle, c_default, ct.sizeof(c_default))
            definition = DefaultDefinition(default=c_default.value.decode('utf-8'))
            return definition

        @definition.setter
        def definition(self, value):
            c_default = ct.create_string_buffer(value.default.encode('utf-8'))
            dll.kvaDbSetAttributeDefinitionString(self._handle, c_default)

    class EnumDefinition(AttributeDefinition):
        """Definition of an enum attribute."""

        def __init__(self, db, handle, definition=None):
            """Define an enum attribute.

            Args:
                db (:obj:`Dbc`): Database that holds attribute definitions
                definition (:obj:`EnumDefaultDefinition`): default value and enums
            """
            super(EnumDefinition, self).__init__(db, handle)
            if definition is not None:
                self.definition = definition

        def add_enum_definition(self, enums):
            """Add enum definitions.

            Args:
                enums (dict): key - value pair(s), example: {'empty': 0}
            """
            # Iterate through key: value pairs, sorted by the value to always add
            # enum members in the same order.
            #
            # Sorting on values as a workaround for a bug in kvadblib v5.22.
            for key, value in sorted(enums.items(), key=lambda x: x[1]):
                c_key = ct.create_string_buffer(key.encode('utf-8'))
                c_value = ct.c_int(value)
                dll.kvaDbAddAttributeDefinitionEnum(
                    self._handle, c_key, c_value)

        def _enums(self):
            """Return a generator of all enum key - value pair(s)

            .. versionadded:: 1.6

            """
            buf = ct.create_string_buffer(255)
            c_value = ct.c_int()
            c_buf_size = ct.c_size_t(ct.sizeof(buf))
            try:
                dll.kvaDbGetAttributeDefinitionEnumFirst(self._handle,
                                                         ct.byref(c_value),
                                                         buf,
                                                         ct.byref(c_buf_size))
            except KvdNoAttribute:
                return
            while True:
                yield (buf.value.decode('utf-8'), c_value.value)
                try:
                    dll.kvaDbGetAttributeDefinitionEnumNext(self._handle,
                                                            ct.byref(c_value),
                                                            buf,
                                                            ct.byref(c_buf_size))
                except KvdNoAttribute:
                    return

        @property
        def definition(self):
            """`EnumDefaultDefinition`: Return attribute definition

            .. versionchanged:: 1.6

            """
            c_default = ct.c_int()
            dll.kvaDbGetAttributeDefinitionEnumeration(
                self._handle, ct.byref(c_default))
            enums = dict(list(self._enums()))
            definition = EnumDefaultDefinition(default=c_default.value, enums=enums)
            return definition

        @definition.setter
        def definition(self, value):
            dll.kvaDbSetAttributeDefinitionEnumDefault(self._handle, value.default)
            self.add_enum_definition(value.enums)