class Address:
    """Datatype definition for INSTEON device address handling."""

    def __init__(self, addr):
        """Create an Address object."""
        self._is_x10 = False
        self.addr = self._normalize(addr)

    def __repr__(self):
        """Representation of the Address object."""
        return self.id

    def __str__(self):
        """Return the Address object as a string."""
        return self.id

    def __eq__(self, other):
        """Test for equality."""
        equals = False
        if hasattr(other, "addr"):
            equals = self.addr == other.addr
        return equals

    def __ne__(self, other):
        """Test for not equals."""
        not_equals = True
        if hasattr(other, "addr"):
            not_equals = self.addr != other.addr
        return not_equals

    def __lt__(self, other):
        """Test for less than."""
        if isinstance(other, Address):
            return str(self) < str(other)
        raise TypeError

    def __gt__(self, other):
        """Test for greater than."""
        if isinstance(other, Address):
            return str(self) > str(other)
        raise TypeError

    def __hash__(self):
        """Create a hash code for the Address object."""
        return hash(self.id)

    def matches_pattern(self, other):
        """Test Address object matches the pattern of another  object."""
        matches = False
        if hasattr(other, "addr"):
            if self.addr is None or other.addr is None:
                matches = True
            else:
                matches = self.addr == other.addr
        return matches

    def _normalize(self, addr):
        """Take any format of address and turn it into a hex string."""
        normalize = None
        if isinstance(addr, Address):
            normalize = addr.addr
            self._is_x10 = addr.is_x10

        elif isinstance(addr, bytearray):
            normalize = binascii.unhexlify(binascii.hexlify(addr).decode())

        elif isinstance(addr, bytes):
            normalize = addr

        elif isinstance(addr, str):
            addr = addr.replace(".", "")
            addr = addr[0:6]
            if addr[0:3].lower() == "x10":
                x10_addr = Address.x10(addr[3:4], int(addr[4:6]))
                normalize = x10_addr.addr
                self._is_x10 = True
            else:
                normalize = binascii.unhexlify(addr.lower())

        elif addr is None:
            normalize = None

        else:
            _LOGGER.warning(
                "Address class init with unknown type %s: %r", type(addr), addr
            )
        return normalize

    @property
    def human(self):
        """Emit the address in human-readible format (AA.BB.CC)."""
        addrstr = "00.00.00"
        if self.addr:
            if self._is_x10:
                housecode_byte = self.addr[1]
                housecode = insteonplm.utils.byte_to_housecode(housecode_byte)
                unitcode_byte = self.addr[2]
                unitcode = insteonplm.utils.byte_to_unitcode(unitcode_byte)
                addrstr = "X10.{}.{:02d}".format(housecode.upper(), unitcode)
            else:
                addrstr = "{}.{}.{}".format(
                    self.hex[0:2], self.hex[2:4], self.hex[4:6]
                ).upper()
        return addrstr

    @property
    def hex(self):
        """Emit the address in bare hex format (aabbcc)."""
        addrstr = "000000"
        if self.addr is not None:
            addrstr = binascii.hexlify(self.addr).decode()
        return addrstr

    @property
    def bytes(self):
        """Emit the address in bytes format."""
        addrbyte = b"\x00\x00\x00"
        if self.addr is not None:
            addrbyte = self.addr
        return addrbyte

    @property
    def id(self):
        """Return the ID of the device address."""
        dev_id = ""
        if self._is_x10:
            dev_id = "x10{}{:02d}".format(self.x10_housecode, self.x10_unitcode)
        else:
            dev_id = self.hex
        return dev_id

    @property
    def is_x10(self):
        """Test if this is an X10 address."""
        return self._is_x10

    @is_x10.setter
    def is_x10(self, val: bool):
        """Set if this is an X10 address."""
        self._is_x10 = val

    @property
    def x10_housecode_byte(self):
        """Emit the X10 house code byte value."""
        housecode = None
        if self.is_x10:
            housecode = self.addr[1]
        return housecode

    @property
    def x10_unitcode_byte(self):
        """Emit the X10 unit code byte value."""
        unitcode = None
        if self.is_x10:
            unitcode = self.addr[2]
        return unitcode

    @property
    def x10_housecode(self):
        """Emit the X10 house code."""
        housecode = None
        if self.is_x10:
            housecode = insteonplm.utils.byte_to_housecode(self.addr[1])
        return housecode

    @property
    def x10_unitcode(self):
        """Emit the X10 unit code."""
        unitcode = None
        if self.is_x10:
            unitcode = insteonplm.utils.byte_to_unitcode(self.addr[2])
        return unitcode

    @classmethod
    def x10(cls, housecode, unitcode):
        """Create an X10 device address."""
        if housecode.lower() in [
            "a",
            "b",
            "c",
            "d",
            "e",
            "f",
            "g",
            "h",
            "i",
            "j",
            "k",
            "l",
            "m",
            "n",
            "o",
            "p",
        ]:
            byte_housecode = insteonplm.utils.housecode_to_byte(housecode)
        else:
            if isinstance(housecode, str):
                _LOGGER.error("X10 house code error: %s", housecode)
            else:
                _LOGGER.error("X10 house code is not a string")
            raise ValueError

        # 20, 21 and 22 for All Units Off, All Lights On and All Lights Off
        # 'fake' units
        if unitcode in range(1, 17) or unitcode in range(20, 23):
            byte_unitcode = insteonplm.utils.unitcode_to_byte(unitcode)
        else:
            if isinstance(unitcode, int):
                _LOGGER.error("X10 unit code error: %d", unitcode)
            else:
                _LOGGER.error("X10 unit code is not an integer 1 - 16")
            raise ValueError

        addr = Address(bytearray([0x00, byte_housecode, byte_unitcode]))
        addr.is_x10 = True
        return addr class AllLinkCleanupFailureReport(Message):
    """INSTEON All-Link Failure Report Message.

    Message type 0x56
    """

    _code = MESSAGE_ALL_LINK_CEANUP_FAILURE_REPORT_0X56
    _sendSize = MESSAGE_ALL_LINK_CEANUP_FAILURE_REPORT_SIZE
    _receivedSize = MESSAGE_ALL_LINK_CEANUP_FAILURE_REPORT_SIZE
    _description = "INSTEON All-Link Failure Report Message"

    def __init__(self, group, address):
        """Init the AllLinkCleanupFailureReport Class."""
        self._group = group
        self._address = Address(address)
        self._failedFlag = 0x01

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from a raw byte stream."""
        return AllLinkCleanupFailureReport(rawmessage[3], rawmessage[4:7])

    @property
    def group(self):
        """Return the All-Link Group."""
        return self._group

    @property
    def address(self):
        """Return the device address."""
        return self._address

    def _message_properties(self):
        return [
            {"failedFlag": self._failedFlag},
            {"group": self._group},
            {"address": self._address},
        ] class AllLinkCleanupStatusReport(Message):
    """INSTEON All-Link Cleanup Status Report Message.

    Message type 0x58
    """

    _code = MESSAGE_ALL_LINK_CLEANUP_STATUS_REPORT_0X58
    _sendSize = MESSAGE_ALL_LINK_CLEANUP_STATUS_REPORT_SIZE
    _receivedSize = MESSAGE_ALL_LINK_CLEANUP_STATUS_REPORT_SIZE
    _description = "INSTEON All-Link Cleanup Status Report Message Received"

    def __init__(self, acknak):
        """Init the AllLinkCleanupStatusReport Class."""
        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        return AllLinkCleanupStatusReport(rawmessage[2])

    @property
    def acknak(self):
        """Return ACK/NAK byte."""
        return self._acknak

    @property
    def isack(self):
        """Test if this is an ACK message."""
        return self._acknak & MESSAGE_ACK == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if this is a NAK message."""
        return self._acknak & MESSAGE_NAK == MESSAGE_NAK

    def _message_properties(self):
        return [{"acknak": self._acknak}] class AllLinkComplete(Message):
    """INSTEON ALL-Linking Completed Message.

    Message type 0x53
    """

    _code = MESSAGE_ALL_LINKING_COMPLETED_0X53
    _sendSize = MESSAGE_ALL_LINKING_COMPLETED_SIZE
    _receivedSize = MESSAGE_ALL_LINKING_COMPLETED_SIZE
    _description = "INSTEON ALL-Linking Completed Message Received"

    def __init__(self, linkcode, group, address, cat, subcat, firmware):
        """Init the AllLinkComplete Class."""
        self._linkcode = linkcode
        self._group = group
        self._address = Address(address)
        self._category = cat
        self._subcategory = subcat
        self._firmware = firmware

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create a message from a raw byte stream."""
        return AllLinkComplete(
            rawmessage[2],
            rawmessage[3],
            rawmessage[4:7],
            rawmessage[7],
            rawmessage[8],
            rawmessage[9],
        )

    @property
    def linkcode(self):
        """Return link record link code."""
        return self._linkcode

    @property
    def group(self):
        """Return link record group."""
        return self._group

    @property
    def address(self):
        """Return the device address."""
        return self._address

    @property
    def category(self):
        """Return the device category."""
        return self._category

    @property
    def subcategory(self):
        """Return the device subcategory."""
        return self._subcategory

    @property
    def firmware(self):
        """Return the device firmware version."""
        return self._firmware

    @property
    def isresponder(self):
        """Return if the link record is a responder."""
        return bool(self.linkcode == 0)

    @property
    def iscontroller(self):
        """Return if the link record is a controller."""
        return bool(self.linkcode == 1)

    @property
    def isdeleted(self):
        """Return if the link record is deleted."""
        return bool(self.linkcode == 0xFF)

    def _message_properties(self):
        return [
            {"linkcode": self._linkcode},
            {"group": self._group},
            {"address": self._address},
            {"category": self._category},
            {"subcategory": self._subcategory},
            {"firmware": self._firmware},
        ] class AllLinkRecordResponse(Message):
    """INSTEON ALL-Link Record Response.

    Message type 0x57
    """

    _code = MESSAGE_ALL_LINK_RECORD_RESPONSE_0X57
    _sendSize = MESSAGE_ALL_LINK_RECORD_RESPONSE_SIZE
    _receivedSize = MESSAGE_ALL_LINK_RECORD_RESPONSE_SIZE
    _description = "INSTEON ALL-Link Record Response"

    def __init__(self, flags, group, address, linkdata1, linkdata2, linkdata3):
        """Init the AllLinkRecordResponse Class."""
        self._controlFlags = flags
        self._group = group
        self._address = Address(address)
        self._linkdata1 = linkdata1
        self._linkdata2 = linkdata2
        self._linkdata3 = linkdata3

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        return AllLinkRecordResponse(
            rawmessage[2],
            rawmessage[3],
            rawmessage[4:7],
            rawmessage[7],
            rawmessage[8],
            rawmessage[9],
        )

    @property
    def controlFlags(self):
        """Return the link record control flags."""
        return self._controlFlags

    @property
    def group(self):
        """Return the link record group."""
        return self._group

    @property
    def address(self):
        """Return the device address."""
        return self._address

    @property
    def linkdata1(self):
        """Return the first link data field."""
        return self._linkdata1

    @property
    def linkdata2(self):
        """Return the second link data field."""
        return self._linkdata2

    @property
    def linkdata3(self):
        """Return the third link data field."""
        return self._linkdata3

    @property
    def isRecordinuse(self):
        """Test if the link record is in use."""
        return (
            self._controlFlags & CONTROL_FLAG_RECORD_IN_USE
        ) == CONTROL_FLAG_RECORD_IN_USE

    @property
    def isController(self):
        """Test if the link group is a controller."""
        return (self._controlFlags & CONTROL_FLAG_CONTROLLER) == CONTROL_FLAG_CONTROLLER

    @property
    def isSlave(self):
        """Test if the link group is a slave or responder."""
        return not self.isController

    def _message_properties(self):
        return [
            {"controlFlags": self._controlFlags},
            {"group": self._group},
            {"address": self._address},
            {"linkdata1": self._linkdata1},
            {"linkdata2": self._linkdata2},
            {"linkdata3": self._linkdata3},
        ] class ButtonEventReport(Message):
    """Insteon Button Event Report Message Received.

    Message type 0x54
    """

    _code = MESSAGE_BUTTON_EVENT_REPORT_0X54
    _sendSize = MESSAGE_BUTTON_EVENT_REPORT_SIZE
    _receivedSize = MESSAGE_BUTTON_EVENT_REPORT_SIZE
    _description = "INSTEON Standard Message Received"

    def __init__(self, event):
        """Init the ButtonEventReport Class."""
        self._event = event

        self._events = {
            0x02: "SET button tapped",
            0x03: "SET button press and hold",
            0x04: "SET button released",
            0x12: "Button 2 tapped",
            0x13: "Button 2 press and hold",
            0x14: "Button 2 released",
            0x22: "Button 3 tapped",
            0x23: "Button 3 press and hold",
            0x24: "Button 3 released",
        }

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        return ButtonEventReport(rawmessage[2])

    @property
    def event(self):
        """Return the button event property."""
        return self._event

    @property
    def eventText(self):
        """Return the button event text."""
        return self._events.get(self.event, None)

    def _message_properties(self):
        return [{"event": self._event}] class CancelAllLinking(Message):
    """INSTEON Cancel All-Linking.

    Message type 0x65
    """

    _code = MESSAGE_CANCEL_ALL_LINKING_0X65
    _sendSize = MESSAGE_CANCEL_ALL_LINKING_SIZE
    _receivedSize = MESSAGE_CANCEL_ALL_LINKING_RECEIVED_SIZE
    _description = "INSTEON Cancel All-Linking"

    def __init__(self, acknak=None):
        """Init the CancelAllLinking Class."""
        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        return CancelAllLinking(rawmessage[2:3])

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @property
    def isack(self):
        """Test if this is an ACK message."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if this is a NAK message."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def _message_properties(self):
        return [{"acknak": self._acknak}] class ExtendedReceive(Message):
    """Insteon Extended Length Message Received.

    Message type 0x51
    """

    _code = MESSAGE_EXTENDED_MESSAGE_RECEIVED_0X51
    _sendSize = MESSAGE_EXTENDED_MESSAGE_RECEIVED_SIZE
    _receivedSize = MESSAGE_EXTENDED_MESSAGE_RECEIVED_SIZE
    _description = "INSTEON Extended Message Received"

    def __init__(self, address, target, commandtuple, userdata, cmd2=None, flags=0x10):
        """Init the ExtendedRecieve message class."""
        if commandtuple.get("cmd1", None) is not None:
            cmd1 = commandtuple["cmd1"]
            cmd2out = commandtuple["cmd2"]
        else:
            raise ValueError

        if cmd2 is not None:
            cmd2out = cmd2

        if cmd2out is None:
            raise ValueError

        self._address = Address(address)
        self._target = Address(target)
        self._messageFlags = MessageFlags(flags)
        # self._messageFlags.extended = 1
        self._cmd1 = cmd1
        self._cmd2 = cmd2out
        self._userdata = Userdata(userdata)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        userdata = Userdata.from_raw_message(rawmessage[11:25])
        return ExtendedReceive(
            rawmessage[2:5],
            rawmessage[5:8],
            {"cmd1": rawmessage[9], "cmd2": rawmessage[10]},
            userdata,
            flags=rawmessage[8],
        )

    # pylint: disable=protected-access
    @classmethod
    def template(
        cls,
        address=None,
        target=None,
        commandtuple=None,
        userdata=None,
        cmd2=-1,
        flags=None,
    ):
        """Create message template for callbacks."""
        msgraw = bytearray([0x02, cls._code])
        msgraw.extend(bytes(cls._receivedSize))
        msg = ExtendedReceive.from_raw_message(msgraw)

        if commandtuple:
            cmd1 = commandtuple.get("cmd1")
            cmd2out = commandtuple.get("cmd2")
        else:
            cmd1 = None
            cmd2out = None

        if cmd2 is not -1:
            cmd2out = cmd2

        msg._address = Address(address)
        msg._target = Address(target)
        msg._messageFlags = MessageFlags(flags)
        msg._cmd1 = cmd1
        msg._cmd2 = cmd2out
        msg._userdata = Userdata.create_pattern(userdata)
        return msg

    @property
    def address(self):
        """Return address of sending device."""
        return self._address

    @property
    def target(self):
        """Return address of target device."""
        return self._target

    @property
    def cmd1(self):
        """Return cmd1 property of message."""
        return self._cmd1

    @property
    def cmd2(self):
        """Return cmd2 property of message."""
        return self._cmd2

    @property
    def userdata(self):
        """Return user data of extended message."""
        return self._userdata

    @property
    def flags(self):
        """Return message flags."""
        return self._messageFlags

    @property
    def targetLow(self):
        """Return the low byte of the target address field.

        Used in All-Link Cleanup messages.
        """
        low_byte = None
        if self.target is not None and self._messageFlags.isBroadcast:
            low_byte = self.target.bytes[0]
        return low_byte

    @property
    def targetMed(self):
        """Return the middle byte of the target address field.

        Used in All-Link Cleanup messages.
        """
        med_byte = None
        if self.target is not None and self._messageFlags.isBroadcast:
            med_byte = self.target.bytes[1]
        return med_byte

    @property
    def targetHi(self):
        """Return the high byte of the target address field.

        Used in All-Link Cleanup messages.
        """
        hi_byte = None
        if self.target is not None and self._messageFlags.isBroadcast:
            hi_byte = self.target.bytes[2]
        return hi_byte

    def _message_properties(self):
        return [
            {"address": self._address},
            {"target": self._target},
            {"flags": self._messageFlags},
            {"cmd1": self._cmd1},
            {"cmd2": self._cmd2},
            {"userdata": self._userdata},
        ] class ExtendedSend(Message):
    """Send an INSTEON Extended message.

    address: A device hex address in any form.
    cmd1: Required command in the cmd1 field
    cmd2: Required command in the cmd2 field
    **kwarg: otional userdata dictionary with the following keys:
        'd1' - User data byte 0 as byte or int
        'd2' - user data byte 1 as byte or int
        ...
        'd14' - user data byte 14 as byte or int
        'd1' to 'd14' are assumed to equal 0x00 unless explicitly set
    """

    _code = MESSAGE_SEND_EXTENDED_MESSAGE_0X62
    _sendSize = MESSAGE_SEND_EXTENDED_MESSAGE_SIZE
    _receivedSize = MESSAGE_SEND_EXTENDED_MESSAGE_RECEIVED_SIZE
    _description = "INSTEON Standard Message Send"

    def __init__(
        self, address, commandtuple, userdata, cmd2=None, flags=0x10, acknak=None
    ):
        """Init the ExtendedSend message class."""
        if commandtuple.get("cmd1", None) is not None:
            cmd1 = commandtuple["cmd1"]
            cmd2out = commandtuple["cmd2"]
        else:
            raise ValueError

        if cmd2 is not None:
            cmd2out = cmd2

        if cmd2out is None:
            raise ValueError

        self._address = Address(address)
        self._messageFlags = MessageFlags(flags)
        self._messageFlags.extended = 1
        self._cmd1 = cmd1
        self._cmd2 = cmd2out
        self._userdata = Userdata(userdata)
        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create a message from a raw byte stream."""
        userdata_dict = Userdata(rawmessage[8:22])
        return ExtendedSend(
            rawmessage[2:5],
            {"cmd1": rawmessage[6], "cmd2": rawmessage[7]},
            userdata_dict,
            flags=rawmessage[5],
            acknak=rawmessage[22:23],
        )

    # pylint: disable=protected-access
    @classmethod
    def template(
        cls,
        address=None,
        commandtuple=None,
        userdata=None,
        cmd2=-1,
        flags=None,
        acknak=None,
    ):
        """Create a message template used for callbacks."""
        msgraw = bytearray([0x02, cls._code])
        msgraw.extend(bytes(cls._receivedSize))
        msg = ExtendedSend.from_raw_message(msgraw)

        if commandtuple:
            cmd1 = commandtuple.get("cmd1")
            cmd2out = commandtuple.get("cmd2")
        else:
            cmd1 = None
            cmd2out = None

        if cmd2 is not -1:
            cmd2out = cmd2

        msg._address = Address(address)
        msg._messageFlags = MessageFlags(flags)
        msg._messageFlags.extended = 1
        msg._cmd1 = cmd1
        msg._cmd2 = cmd2out
        msg._userdata = Userdata.template(userdata)
        msg._acknak = acknak
        return msg

    @property
    def address(self):
        """Return the address of the device."""
        return self._address

    @property
    def cmd1(self):
        """Return the cmd1 property of the message."""
        return self._cmd1

    @property
    def cmd2(self):
        """Return the cmd2 property of the message."""
        return self._cmd2

    @property
    def userdata(self):
        """Return the extended message user data."""
        return self._userdata

    @property
    def flags(self):
        """Return the message flags."""
        return self._messageFlags

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @acknak.setter
    def acknak(self, val):
        """Set the ACK/NAK byte."""
        if val in [None, 0x06, 0x15]:
            self._acknak = val
        else:
            raise ValueError

    @property
    def isack(self):
        """Test if message is an ACK."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if message is a NAK."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def set_checksum(self):
        """Set byte 14 of the userdata to a checksum value."""
        data_sum = self.cmd1 + self.cmd2
        for i in range(1, 14):
            data_sum += self._userdata["d{:d}".format(i)]
        chksum = 0xFF - (data_sum & 0xFF) + 1
        self._userdata["d14"] = chksum & 0xff

    def set_crc(self):
        """Set Userdata[13] and Userdata[14] to the CRC value."""
        data = self.bytes[6:20]
        crc = int(0)
        for b in data:
            # pylint: disable=unused-variable
            for bit in range(0, 8):
                fb = b & 0x01
                fb = fb ^ 0x01 if (crc & 0x8000) else fb
                fb = fb ^ 0x01 if (crc & 0x4000) else fb
                fb = fb ^ 0x01 if (crc & 0x1000) else fb
                fb = fb ^ 0x01 if (crc & 0x0008) else fb
                crc = ((crc << 1) | fb) & 0xFFFF
                b = b >> 1
        self._userdata["d13"] = (crc >> 8) & 0xFF
        self._userdata["d14"] = crc & 0xFF

    def _message_properties(self):
        return [
            {"address": self._address},
            {"flags": self._messageFlags},
            {"cmd1": self._cmd1},
            {"cmd2": self._cmd2},
            {"userdata": self._userdata},
            {"acknak": self._acknak},
        ] class GetFirstAllLinkRecord(Message):
    """Insteon Get First All Link Record Message.

    Message type 0x69
    """

    _code = MESSAGE_GET_FIRST_ALL_LINK_RECORD_0X69
    _sendSize = MESSAGE_GET_FIRST_ALL_LINK_RECORD_SIZE
    _receivedSize = MESSAGE_GET_FIRST_ALL_LINK_RECORD_RECEIVED_SIZE
    _description = "Insteon Get First All Link Record Message"

    def __init__(self, acknak=None):
        """Init the GetFirstAllLinkRecord Class."""
        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        return GetFirstAllLinkRecord(rawmessage[2:3])

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @property
    def isack(self):
        """Test if this is an ACK message."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if this is a NAK message."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def _message_properties(self):
        return [{"acknak": self._acknak}] class GetImConfiguration(Message):
    """Insteon Get IM Configuration Message.

    Message type 0x62
    """

    _code = MESSAGE_GET_IM_CONFIGURATION_0X73
    _sendSize = MESSAGE_GET_IM_CONFIGURATION_SIZE
    _receivedSize = MESSAGE_GET_IM_CONFIGURATION_RECEIVED_SIZE
    _description = "Insteon Get IM Configuration Message"

    def __init__(self, flags=None, acknak=None):
        """Init the GetImConfiguration Class."""
        self._imConfigurationFlags = flags
        self._spare1 = None
        self._spare2 = None
        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmesssage):
        """Create message from a raw byte stream."""
        return GetImConfiguration(rawmesssage[2], rawmesssage[5])

    @property
    def imConfigurationFlags(self):
        """Return the IM configuration flags."""
        return self._imConfigurationFlags

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @property
    def isack(self):
        """Test if this is an ACK message."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if this is a NAK message."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def _message_properties(self):
        if self._imConfigurationFlags is not None:
            self._spare1 = 0x00
            self._spare2 = 0x00
        else:
            self._spare1 = None
            self._spare2 = None
        return [
            {"imConfigurationFlags": self._imConfigurationFlags},
            {"spare1": self._spare1},
            {"spare2": self._spare2},
            {"acknak": self.acknak},
        ] class GetImInfo(Message):
    """INSTEON Get Insteon Modem Info Message.

    Message type 0x60
    """

    _code = MESSAGE_GET_IM_INFO_0X60
    _sendSize = MESSAGE_GET_IM_INFO_SIZE
    _receivedSize = MESSAGE_GET_IM_INFO_RECEIVED_SIZE
    _description = "INSTEON Get Insteon Modem Info Message Received"

    def __init__(self, address=None, cat=None, subcat=None, firmware=None, acknak=None):
        """Init the GetImInfo Class."""
        self._address = Address(address)
        self._category = cat
        self._subcategory = subcat
        self._firmware = firmware
        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        return GetImInfo(
            rawmessage[2:5], rawmessage[5], rawmessage[6], rawmessage[7], rawmessage[8]
        )

    @property
    def address(self):
        """Return the device address."""
        return self._address

    @property
    def category(self):
        """Return the IM device category."""
        return self._category

    @property
    def subcategory(self):
        """Return the IM device subcategory."""
        return self._subcategory

    @property
    def firmware(self):
        """Return the IM device firmware version."""
        return self._firmware

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @property
    def isack(self):
        """Test if this is an ACK message."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if this is a NAK message."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def _message_properties(self):
        return [
            {"address": self._address},
            {"category": self._category},
            {"subcategory": self._subcategory},
            {"firmware": self._firmware},
            {"acknak": self._acknak},
        ] class GetNextAllLinkRecord(Message):
    """Insteon Get Next All Link Record Message.

    Message type 0x6A
    """

    _code = MESSAGE_GET_NEXT_ALL_LINK_RECORD_0X6A
    _sendSize = MESSAGE_GET_NEXT_ALL_LINK_RECORD_SIZE
    _receivedSize = MESSAGE_GET_NEXT_ALL_LINK_RECORD_RECEIVED_SIZE
    _description = "Insteon Get Next All Link Record Message"

    def __init__(self, acknak=None):
        """Init the GetNextAllLinkRecord Class."""
        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        return GetNextAllLinkRecord(rawmessage[2:3])

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @property
    def isack(self):
        """Test if this is an ACK message."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if this is a NAK message."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def _message_properties(self):
        return [{"acknak": self._acknak}] class ResetIM(Message):
    """Insteon Reset IM Message.

    Message type 0x67
    """

    _code = MESSAGE_RESET_IM_0X67
    _sendSize = MESSAGE_RESET_IM_SIZE
    _receivedSize = MESSAGE_RESET_IM_RECEIVED_SIZE
    _description = "Insteon Reset IM Message"

    def __init__(self, acknak=None):
        """Init the ResetIM Class."""
        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        return ResetIM(rawmessage[2:3])

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @property
    def isack(self):
        """Test if this is an ACK message."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if this is a NAK message."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def _message_properties(self):
        return [{"acknak": self._acknak}] class SendAllLinkCommand(Message):
    """Insteon Send All Link Command Message.

    Message type 0x6A
    """

    _code = MESSAGE_SEND_ALL_LINK_COMMAND_0X61
    _sendSize = MESSAGE_SEND_ALL_LINK_COMMAND_SIZE
    _receivedSize = MESSAGE_SEND_ALL_LINK_COMMAND_RECEIVED_SIZE
    _description = "Insteon Get Next All Link Record Message"

    def __init__(self, group, allLinkCommand, broadcastCommand, acknak=None):
        """Init the SendAllLinkCommand Class."""
        self._group = group
        self._allLinkCommmand = allLinkCommand
        self._broadcastCommand = broadcastCommand

        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        return SendAllLinkCommand(
            rawmessage[2], rawmessage[3], rawmessage[4], rawmessage[5:6]
        )

    @property
    def group(self):
        """Return the All-Link group."""
        return self._group

    @property
    def allLinkCommmand(self):
        """Return the All-Link command."""
        return self._allLinkCommmand

    @property
    def broadcastCommand(self):
        """Return the broadcase command."""
        return self._broadcastCommand

    @property
    def isack(self):
        """Test if this is an ACK message."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if this is a NAK message."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def _message_properties(self):
        return [
            {"group": self._group},
            {"allLinkCommand": self._allLinkCommmand},
            {"broadcastCommand": self._broadcastCommand},
            {"acknak": self._acknak},
        ] class StandardReceive(Message):
    """Insteon Standard Length Message Received.

    Message type 0x50
    """

    _code = MESSAGE_STANDARD_MESSAGE_RECEIVED_0X50
    _sendSize = MESSAGE_STANDARD_MESSAGE_RECIEVED_SIZE
    _receivedSize = MESSAGE_STANDARD_MESSAGE_RECIEVED_SIZE
    _description = "INSTEON Standard Message Received"

    def __init__(self, address, target, commandtuple, cmd2=None, flags=0x00):
        """Init the StandardReceive message class."""
        if commandtuple.get("cmd1") is not None:
            cmd1 = commandtuple["cmd1"]
            cmd2out = commandtuple["cmd2"]
        else:
            raise ValueError

        if cmd2 is not None:
            cmd2out = cmd2

        if cmd2out is None:
            raise ValueError

        self._address = Address(address)
        self._target = Address(target)
        self._messageFlags = MessageFlags(flags)
        # self._messageFlags.extended = 0
        self._cmd1 = cmd1
        self._cmd2 = cmd2out

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from a raw byte stream."""
        return StandardReceive(
            rawmessage[2:5],
            rawmessage[5:8],
            {"cmd1": rawmessage[9], "cmd2": rawmessage[10]},
            flags=rawmessage[8],
        )

    # pylint: disable=protected-access
    @classmethod
    def template(
        cls, address=None, target=None, commandtuple=None, cmd2=-1, flags=None
    ):
        """Create a message template used for callbacks."""
        msgraw = bytearray([0x02, cls._code])
        msgraw.extend(bytes(cls._receivedSize))
        msg = StandardReceive.from_raw_message(msgraw)

        if commandtuple:
            cmd1 = commandtuple.get("cmd1")
            cmd2out = commandtuple.get("cmd2")
        else:
            cmd1 = None
            cmd2out = None

        if cmd2 is not -1:
            cmd2out = cmd2

        msg._address = Address(address)
        msg._target = Address(target)
        msg._messageFlags = MessageFlags(flags)
        msg._cmd1 = cmd1
        msg._cmd2 = cmd2out
        return msg

    @property
    def address(self):
        """Return the address of the device."""
        return self._address

    @property
    def target(self):
        """Return the address of the target device."""
        return self._target

    @property
    def cmd1(self):
        """Return the cmd1 property of the message."""
        return self._cmd1

    @property
    def cmd2(self):
        """Return the cmd2 property of the message."""
        return self._cmd2

    @property
    def flags(self):
        """Return the message flags."""
        return self._messageFlags

    @property
    def targetLow(self):
        """Return the low byte of the target message property.

        Used in All-Link Cleanup message types.
        """
        low_byte = None
        if self.target.addr is not None and self._messageFlags.isBroadcast:
            low_byte = self.target.bytes[0]
        return low_byte

    @property
    def targetMed(self):
        """Return the middle byte of the target message property.

        Used in All-Link Cleanup message types.
        """
        med_byte = None
        if self.target.addr is not None and self._messageFlags.isBroadcast:
            med_byte = self.target.bytes[1]
        return med_byte

    @property
    def targetHi(self):
        """Return the high byte of the target message property.

        Used in All-Link Cleanup message types.
        """
        hi_byte = None
        if self.target.addr is not None and self._messageFlags.isBroadcast:
            hi_byte = self.target.bytes[2]
        return hi_byte

    def _message_properties(self):
        return [
            {"address": self._address},
            {"target": self._target},
            {"flags": self._messageFlags},
            {"cmd1": self._cmd1},
            {"cmd2": self._cmd2},
        ] class StandardSend(Message):
    """Insteon Standard Length Message Send 0x62."""

    _code = MESSAGE_SEND_STANDARD_MESSAGE_0X62
    _sendSize = MESSAGE_SEND_STANDARD_MESSAGE_SIZE
    _receivedSize = MESSAGE_SEND_STANDARD_MESSAGE_RECEIVED_SIZE
    _description = "INSTEON Standard Message Send"

    def __init__(self, address, commandtuple, cmd2=None, flags=0x00, acknak=None):
        """Init the StandardSend message class."""
        if commandtuple.get("cmd1", None) is not None:
            cmd1 = commandtuple["cmd1"]
            cmd2out = commandtuple["cmd2"]
        else:
            raise ValueError

        if cmd2 is not None:
            cmd2out = cmd2

        if cmd2out is None:
            raise ValueError

        self._address = Address(address)
        self._messageFlags = MessageFlags(flags)
        self._messageFlags.extended = 0
        self._cmd1 = cmd1
        self._cmd2 = cmd2out

        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create a message from a raw byte stream."""
        if (rawmessage[5] & MESSAGE_FLAG_EXTENDED_0X10) == MESSAGE_FLAG_EXTENDED_0X10:
            if len(rawmessage) >= ExtendedSend.receivedSize:
                msg = ExtendedSend.from_raw_message(rawmessage)
            else:
                msg = None
        else:
            msg = StandardSend(
                rawmessage[2:5],
                {"cmd1": rawmessage[6], "cmd2": rawmessage[7]},
                flags=rawmessage[5],
                acknak=rawmessage[8:9],
            )
        return msg

    # pylint: disable=protected-access
    @classmethod
    def template(
        cls, address=None, commandtuple=None, cmd2=-1, flags=None, acknak=None
    ):
        """Create a message template for use in callbacks."""
        msgraw = bytearray([0x02, cls._code])
        msgraw.extend(bytes(cls._receivedSize))
        msg = StandardSend.from_raw_message(msgraw)

        if commandtuple:
            cmd1 = commandtuple.get("cmd1")
            cmd2out = commandtuple.get("cmd2")
        else:
            cmd1 = None
            cmd2out = None

        if cmd2 is not -1:
            cmd2out = cmd2

        msg._address = Address(address)
        msg._messageFlags = MessageFlags(flags)
        msg._cmd1 = cmd1
        msg._cmd2 = cmd2out
        msg._acknak = acknak
        return msg

    @property
    def address(self):
        """Return the address of the device."""
        return self._address

    @property
    def cmd1(self):
        """Return cmd1 property of the message."""
        return self._cmd1

    @property
    def cmd2(self):
        """Return cmd2 property of the message."""
        return self._cmd2

    @property
    def flags(self):
        """Return the message flags."""
        return self._messageFlags

    @property
    def acknak(self):
        """Return the ACK/NAK message flag."""
        return self._acknak

    @acknak.setter
    def acknak(self, val):
        """Set the ACK/NAK byte."""
        if val in [None, 0x06, 0x15]:
            self._acknak = val
        else:
            raise ValueError

    @property
    def isack(self):
        """Test if the message is a message ACK."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if the message is a message NAK."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def _message_properties(self):
        return [
            {"address": self._address},
            {"flags": self._messageFlags},
            {"cmd1": self._cmd1},
            {"cmd2": self._cmd2},
            {"acknak": self._acknak},
        ] class StartAllLinking(Message):
    """Insteon Start All Linking Message.

    Message type 0x64
    """

    _code = MESSAGE_START_ALL_LINKING_0X64
    _sendSize = MESSAGE_START_ALL_LINKING_SIZE
    _receivedSize = MESSAGE_START_ALL_LINKING_RECEIVED_SIZE
    _description = "Insteon Start All Linking Message"

    def __init__(self, linkCode, group, acknak=None):
        """Init the StartAllLinking Class."""
        self._linkCode = linkCode
        self._group = group

        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create message from raw byte stream."""
        return StartAllLinking(rawmessage[2], rawmessage[3], rawmessage[4:5])

    @property
    def linkCode(self):
        """Return the link code."""
        return self._linkCode

    @property
    def group(self):
        """Return the All-Link group."""
        return self._group

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @property
    def isack(self):
        """Test if this is an ACK message."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if this is a NAK message."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def _message_properties(self):
        return [
            {"linkCode": self._linkCode},
            {"group": self._group},
            {"acknak": self._acknak},
        ] class UserReset(Message):
    """Insteon User Reset Message Received.

    Message type 0x55
    """

    _code = MESSAGE_USER_RESET_DETECTED_0X55
    _sendSize = MESSAGE_USER_RESET_DETECTED_SIZE
    _receivedSize = MESSAGE_USER_RESET_DETECTED_SIZE
    _description = "INSTEON User Reset Message Received"

    # pylint: disable=unused-argument
    @classmethod
    def from_raw_messsage(cls, rawmessage):
        """Create message from raw byte stream."""
        return UserReset()

    def _message_properties(self):
        return [] class X10Received(Message):
    """Insteon X10 Received Message.

    Message type 0x52
    """

    _code = MESSAGE_X10_MESSAGE_RECEIVED_0X52
    _sendSize = MESSAGE_X10_MESSAGE_RECEIVED_SIZE
    _receivedSize = MESSAGE_X10_MESSAGE_RECEIVED_SIZE
    _description = "Insteon Get Next All Link Record Message"

    def __init__(self, rawX10, flag):
        """Init X10Received Class."""
        self._rawX10 = rawX10
        self._flag = flag

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create from raw byte stream."""
        return X10Received(rawmessage[2], rawmessage[3])

    @property
    def rawX10(self):
        """Return raw X10 message."""
        return self._rawX10

    @property
    def flag(self):
        """Return X10 flag."""
        return self._flag

    @staticmethod
    def unit_code_msg(housecode, unitcode):
        """Create an X10 message to send the house code and unit code."""
        house_byte = 0
        unit_byte = 0
        if isinstance(housecode, str):
            house_byte = insteonplm.utils.housecode_to_byte(housecode) << 4
            unit_byte = insteonplm.utils.unitcode_to_byte(unitcode)
        elif isinstance(housecode, int) and housecode < 16:
            house_byte = housecode << 4
            unit_byte = unitcode
        else:
            house_byte = housecode
            unit_byte = unitcode
        return X10Received(house_byte + unit_byte, 0x00)

    @staticmethod
    def command_msg(housecode, command):
        """Create an X10 message to send the house code and a command code."""
        house_byte = 0
        if isinstance(housecode, str):
            house_byte = insteonplm.utils.housecode_to_byte(housecode) << 4
        elif isinstance(housecode, int) and housecode < 16:
            house_byte = housecode << 4
        else:
            house_byte = housecode
        return X10Received(house_byte + command, 0x80)

    def _message_properties(self):
        return [{"rawX10": self._rawX10}, {"flag": self._flag}] class X10Send(Message):
    """Insteon Get Next All Link Record Message.

    Message type 0x6A
    """

    _code = MESSAGE_X10_MESSAGE_SEND_0X63
    _sendSize = MESSAGE_X10_MESSAGE_SEND_SIZE
    _receivedSize = MESSAGE_X10_MESSAGE_SEND_RECEIVED_SIZE
    _description = "Insteon Get Next All Link Record Message"

    def __init__(self, rawX10, flag, acknak=None):
        """Init the X10Send Class."""
        self._rawX10 = rawX10
        self._flag = flag
        self._acknak = self._setacknak(acknak)

    @staticmethod
    def from_raw_message(rawmessage):
        """Create message from raw byte stream."""
        return X10Send(rawmessage[2], rawmessage[3], rawmessage[4:5])

    @staticmethod
    def unit_code_msg(housecode, unitcode):
        """Create an X10 message to send the house code and unit code."""
        house_byte = 0
        unit_byte = 0
        if isinstance(housecode, str):
            house_byte = insteonplm.utils.housecode_to_byte(housecode) << 4
            unit_byte = insteonplm.utils.unitcode_to_byte(unitcode)
        elif isinstance(housecode, int) and housecode < 16:
            house_byte = housecode << 4
            unit_byte = unitcode
        else:
            house_byte = housecode
            unit_byte = unitcode
        return X10Send(house_byte + unit_byte, 0x00)

    @staticmethod
    def command_msg(housecode, command):
        """Create an X10 message to send the house code and a command code."""
        house_byte = 0
        if isinstance(housecode, str):
            house_byte = insteonplm.utils.housecode_to_byte(housecode) << 4
        elif isinstance(housecode, int) and housecode < 16:
            house_byte = housecode << 4
        else:
            house_byte = housecode
        return X10Send(house_byte + command, 0x80)

    @property
    def rawX10(self):
        """Return the raw x10 bytes."""
        return self._rawX10

    @property
    def flag(self):
        """Return the X10 flag."""
        return self._flag

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @property
    def isack(self):
        """Test if this is an ACK message."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if this is a NAK message."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def _message_properties(self):
        return [
            {"rawX10": self._rawX10},
            {"flag": self._flag},
            {"acknak": self._acknak},
        ]
import binascii
from insteonplm.address import Address
from insteonplm.messages.allLinkCleanupFailureReport import (
    AllLinkCleanupFailureReport)
from insteonplm.messages.allLinkCleanupStatusReport import (
    AllLinkCleanupStatusReport)
from insteonplm.messages.allLinkComplete import AllLinkComplete
from insteonplm.messages.allLinkRecordResponse import AllLinkRecordResponse
from insteonplm.messages.buttonEventReport import ButtonEventReport
from insteonplm.messages.cancelAllLinking import CancelAllLinking
from insteonplm.messages.extendedReceive import ExtendedReceive
from insteonplm.messages.extendedSend import ExtendedSend
from insteonplm.messages.getFirstAllLinkRecord import GetFirstAllLinkRecord
from insteonplm.messages.getImConfiguration import GetImConfiguration
from insteonplm.messages.getIMInfo import GetImInfo
from insteonplm.messages.getNextAllLinkRecord import GetNextAllLinkRecord
from insteonplm.messages.resetIM import ResetIM
from insteonplm.messages.sendAlllinkCommand import SendAllLinkCommand
from insteonplm.messages.standardReceive import StandardReceive
from insteonplm.messages.standardSend import StandardSend
from insteonplm.messages.startAllLinking import StartAllLinking
from insteonplm.messages.userReset import UserReset
from insteonplm.messages.x10received import X10Received
from insteonplm.messages.x10send import X10Send
"""Test all message classes."""



def test_allLinkCleanupStatusReport():
    """Test AllLinkCleanupStatusReport."""
    msg = AllLinkCleanupStatusReport(0x11)
    assert msg.acknak == 0x11
    assert msg.hex == hexmsg(0x02, 0x58, 0x11)
    assert len(msg.hex) / 2 == msg.sendSize
    assert len(msg.hex) / 2 == msg.receivedSize


def test_allLinkComplete():
    """Test AllLinkComplete."""
    linkcode = 0x11
    group = 0x22
    address = bytearray([0x33, 0x44, 0x55])
    cat = 0x66
    subcat = 0x77
    firmware = 0x88

    msg = AllLinkComplete(linkcode, group, address, cat, subcat, firmware)