import random
import crc16
from . import const

MESSAGE_OFFSET = const.MESSAGE_OFFSET


class Message(object):

    def __init__(self, device, data=bytearray()):
        self.device = device
        self.data = bytearray(data)
        self.body = data[MESSAGE_OFFSET['BODY']:]

    def get(self, offset, length=4, value_type=None, header=False):
        if not header:
            offset += MESSAGE_OFFSET['BODY']

        value = self.data[offset : offset + length]

        if value_type == int:
            return int.from_bytes(value, 'big')
        else:
            return value

    def set(self, value, offset, length=4, header=False):
        if not header:
            offset += MESSAGE_OFFSET['BODY']

        self.data[offset : offset + length] = value.to_bytes(length, 'big')

    def set_checksum(self):
        crc = crc16.crc16xmodem(bytes(self.data[0x64:]))
        self.set(crc, MESSAGE_OFFSET['CHECK_SUM'], header=True)

    def encrypt(self):
        if self.device is None or not self.device.is_init:
            return

        password = bytearray(20)
        mac = self.device.mac_raw

        for i in range(0, 20):
            password[i] = mac[i % mac.__len__()]

        for i, v in enumerate(self.body):
            self.data[i + MESSAGE_OFFSET['BODY']] = v ^ password[i % password.__len__()]

    def decrypt(self):
        return self.encrypt()

    def get_request_sn(self):
        return self.data[MESSAGE_OFFSET['REQ_SERIAL_NUM'] : MESSAGE_OFFSET['REQ_SERIAL_NUM'] + 4]

    def get_response_sn(self):
        return self.data[MESSAGE_OFFSET['RES_SERIAL_NUM'] : MESSAGE_OFFSET['RES_SERIAL_NUM'] + 4]

    def to_bytes(self):
        return bytes(self.data)


def build_message(msg_type, body=None, device=None):
    sn = random.randint(1, 1000)
    body = bytearray() if body is None else bytearray(body)
    data = bytearray(MESSAGE_OFFSET['BODY']) + body

    message = Message(device, data)

    message.set(0x03F2, MESSAGE_OFFSET['L2_TYPE'], header=True)
    message.set(0x2775, MESSAGE_OFFSET['L3_VERSION'], header=True)
    message.set(0x0001, MESSAGE_OFFSET['L3_ID'], header=True)
    message.set(0x0002, MESSAGE_OFFSET['OFFSET'], header=True)
    message.set(0x0003, MESSAGE_OFFSET['TTL'], header=True)
    message.set(0x0005, MESSAGE_OFFSET['L3_CHECKSUM'], header=True)
    message.set(0x0001, MESSAGE_OFFSET['DEST_OBJ_TYPE'], header=True)
    message.set(0x6A68, MESSAGE_OFFSET['SRC_ID'], header=True)

    message.set(sn, MESSAGE_OFFSET['REQ_SERIAL_NUM'], header=True)
    message.set(msg_type, MESSAGE_OFFSET['MSG_TYPE'], header=True)
    message.set(body.__len__() + 0x68, MESSAGE_OFFSET['PKG_LENGTH'], header=True)
    message.set(body.__len__() + 0x18, MESSAGE_OFFSET['MSG_LENGTH'], header=True)

    if device and device.is_init:
        message.set(device.server_port, MESSAGE_OFFSET['DEST_PORT'], header=True)
        message.set(device.id, MESSAGE_OFFSET['DEST_ID'], header=True)

    message.set_checksum()
    message.encrypt()

    return message


def parse_message(data, device=None):
    message = Message(device, data)
    message.decrypt()
    return message
