#!/usr/bin/python
#
#  Copyright (c) 2016, The OpenThread Authors.
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#  1. Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#  3. Neither the name of the copyright holder nor the
#     names of its contributors may be used to endorse or promote products
#     derived from this software without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#  POSSIBILITY OF SUCH DAMAGE.
#

import io
import struct

from binascii import hexlify
from enum import IntEnum

import common


class TlvType(IntEnum):
    TARGET_EID = 0
    MAC_EXTENDED_ADDRESS = 1
    RLOC16 = 2
    ML_EID = 3
    STATUS = 4
    TIME_SINCE_LAST_TRANSACTION = 6
    ROUTER_MASK = 7
    ND_OPTION = 8
    ND_DATA = 9
    THREAD_NETWORK_DATA = 10
    MLE_ROUTING = 11


class StatusValues(IntEnum):
    SUCCESS = 0
    NO_ADDRESS_AVAILABLE = 1
    TOO_FEW_ROUTERS = 2
    HAVE_CHILD_ID_REQUEST = 3
    PARENT_PARTITION_CHANGE = 4


class TargetEid(object):

    def __init__(self, eid):
        self._eid = eid

    @property
    def eid(self):
        return self._eid

    def __eq__(self, other):
        common.expect_the_same_class(self, other)
        return self.eid == other.eid

    def __repr__(self):
        return "TargetEid(eid={})".format(hexlify(self.eid))


class TargetEidFactory(object):

    def parse(self, data, message_info):
        eid = bytearray(data.read(16))

        return TargetEid(eid)


class MacExtendedAddress(object):

    def __init__(self, mac_address):
        self._mac_address = mac_address

    @property
    def mac_address(self):
        return self._mac_address

    def __eq__(self, other):
        common.expect_the_same_class(self, other)
        return self.mac_address == other.mac_address

    def __repr__(self):
        return "MacExtendedAddress(mac_address={})".format(hexlify(self.mac_address))


class MacExtendedAddressFactory(object):

    def parse(self, data, message_info):
        mac_address = bytearray(data.read(8))

        return MacExtendedAddress(mac_address)


class Rloc16(object):

    def __init__(self, rloc16):
        self._rloc16 = rloc16

    @property
    def rloc16(self):
        return self._rloc16

    def __eq__(self, other):
        common.expect_the_same_class(self, other)
        return self.rloc16 == other.rloc16

    def __repr__(self):
        return "Rloc16(rloc16={})".format(hex(self.rloc16))


class Rloc16Factory(object):

    def parse(self, data, message_info):
        rloc16 = struct.unpack(">H", data.read(2))[0]

        return Rloc16(rloc16)


class MlEid(object):

    def __init__(self, ml_eid):
        self._ml_eid = ml_eid

    @property
    def ml_eid(self):
        return self._ml_eid

    def __eq__(self, other):
        common.expect_the_same_class(self, other)
        return self.ml_eid == other.ml_eid

    def __repr__(self):
        return "MlEid(ml_eid={})".format(hexlify(self.ml_eid))


class MlEidFactory(object):

    def parse(self, data, message_info):
        ml_eid = bytearray(data.read(8))

        return MlEid(ml_eid)


class Status(object):

    def __init__(self, status):
        self._status = status

    @property
    def status(self):
        return self._status

    def __eq__(self, other):
        common.expect_the_same_class(self, other)
        return self.status == other.status

    def __repr__(self):
        return "Status(status={})".format(self.status)


class StatusFactory(object):

    def parse(self, data, message_info):
        status = StatusValues(ord(data.read(1)))

        return Status(status)


class TimeSinceLastTransaction(object):

    def __init__(self, seconds):
        self._seconds = seconds

    @property
    def seconds(self):
        return self._seconds

    def __eq__(self, other):
        common.expect_the_same_class(self, other)
        return self.seconds == other.seconds

    def __repr__(self):
        return "TimeSinceLastTransaction(seconds={})".format(self.seconds)


class TimeSinceLastTransactionFactory(object):

    def parse(self, data, message_info):
        seconds = struct.unpack(">L", data.read(4))[0]

        return TimeSinceLastTransaction(seconds)


class RouterMask(object):

    def __init__(self, id_sequence, router_id_mask):
        self._id_sequence = id_sequence
        self._router_id_mask = router_id_mask

    @property
    def id_sequence(self):
        return self._id_sequence

    @property
    def router_id_mask(self):
        return self._router_id_mask

    def __eq__(self, other):
        common.expect_the_same_class(self, other)
        return self.id_sequence == other.id_sequence and self.router_id_mask == other.router_id_mask

    def __repr__(self):
        return "RouterMask(id_sequence={}, router_id_mask={})".format(self.id_sequence, hex(self.router_id_mask))


class RouterMaskFactory(object):

    def parse(self, data, message_info):
        id_sequence = ord(data.read(1))
        router_id_mask = struct.unpack(">Q", data.read(8))[0]

        return RouterMask(id_sequence, router_id_mask)


class NdOption(object):

    def __init__(self, options):
        self._options = options

    @property
    def options(self):
        return self._options

    def __eq__(self, other):
        common.expect_the_same_class(self, other)
        return self.options == other.options

    def __repr__(self):
        return "NdOption(options=[{}])".format(", ".join([str(opt) for opt in self.options]))


class NdOptionFactory(object):

    def parse(self, data, message_info):
        options = [opt for opt in bytearray(data.read())]
        return NdOption(options)


class NdData(object):
    # TODO: Not implemented yet
    pass


class NdDataFactory(object):
    # TODO: Not implemented yet

    def parse(self, data, message_info):
        raise NotImplementedError("TODO: Not implemented yet")


class ThreadNetworkData(object):

    def __init__(self, tlvs):
        self._tlvs = tlvs

    @property
    def tlvs(self):
        return self._tlvs

    def __eq__(self, other):
        common.expect_the_same_class(self, other)
        return self.tlvs == other.tlvs

    def __repr__(self):
        return "ThreadNetworkData(tlvs=[{}])".format(", ".join([str(tlv) for tlv in self.tlvs]))


class ThreadNetworkDataFactory(object):

    def __init__(self, network_data_tlvs_factory):
        self._network_data_tlvs_factory = network_data_tlvs_factory

    def parse(self, data, message_info):
        tlvs = self._network_data_tlvs_factory.parse(data, message_info)
        return ThreadNetworkData(tlvs)


class NetworkLayerTlvsFactory(object):

    def __init__(self, tlvs_factories):
        self._tlvs_factories = tlvs_factories

    def parse(self, data, message_info):
        tlvs = []

        while data.tell() < len(data.getvalue()):
            _type = ord(data.read(1))
            length = ord(data.read(1))

            factory = self._tlvs_factories[_type]
            tlv = factory.parse(io.BytesIO(data.read(length)), message_info)

            tlvs.append(tlv)

        return tlvs
