import time
from enum import Enum

import serial
from numpy import uint8

gps = False


class OutType(Enum):
    Off = 0x00
    Lane = 0x01
    LongerLane = 0x02
    LeftRoundabout = 0x04
    RightRoundabout = 0x08
    Flag = 0x40
    ArrowOnly = 0x80


class OutAngle(Enum):
    Down = 0x01
    SharpRight = 0x02
    Right = 0x04
    EasyRight = 0x08
    Straight = 0x10
    EasyLeft = 0x20
    Left = 0x40
    SharpLeft = 0x80
    LeftDown = 0x81
    RightDown = 0x82
    AsDirection = 0x00


class Unit(Enum):
    Any = 0
    Metres = 1
    Kilometres = 3
    Miles = 5
    Foot = 8


class Lane(Enum):
    DotsRight = 0x01
    OuterRight = 0x02
    MiddleRight = 0x04
    InnerRight = 0x08
    InnerLeft = 0x10
    MiddleLeft = 0x20
    OuterLeft = 0x40
    DotsLeft = 0x80


class Controller:

    def __init__(self, device: str = None):
        self.gps = False
        self.debug = device is None
        if not self.debug:
            self.ser = serial.Serial(device, 9600)
            time.sleep(2)
            print(self.ser.read_all())

    def clear(self):
        self.send_hud([0x03, 0, 0, 0, 0x00, 0, 0])

    def _as_dgt(self, number: float) -> int:
        n = int(number)
        if n == 0:
            return 0
        n %= 10
        return 10 if n == 0 else n

    def set_lines(self, outlines: list, arrows: list):
        self.send_hud([0x02, sum([lane.value for lane in outlines]), sum([lane.value for lane in arrows])])

    def set_direction(self, angle: OutAngle, out: OutType = OutType.Lane, roundabout: OutAngle = OutAngle.AsDirection):
        if angle == OutAngle.LeftDown:
            param_1 = 0x10
        elif angle == OutAngle.RightDown:
            param_1 = 0x20
        else:
            param_1 = out.value

        if out == OutType.RightRoundabout or out == OutType.LeftRoundabout:
            param_2 = angle.value if roundabout == OutAngle.AsDirection else roundabout.value
        else:
            param_2 = 0x00

        param_3 = 0x00 if angle == OutAngle.LeftDown or angle == OutAngle.RightDown else angle.value
        arr = [0x01, param_1, param_2, param_3]
        self.send_hud(arr)

    def set_distance(self, distance: float, unit: Unit = Unit.Any):
        is_float = int(distance * 10) == int(distance) * 10
        if not is_float:
            distance = distance * 10
        arr = [0x03,
               self._as_dgt(distance // 1000),
               self._as_dgt(distance // 100),
               self._as_dgt(distance // 10),
               0x00 if is_float else 0xff,
               self._as_dgt(distance),
               unit.value]
        self.send_hud(arr)

    def set_speed(self, speed: int, limit: int = 0, acc: bool = False):
        acc_char = 0xff if acc else 0x00
        if not self.gps:
            self.set_distance(speed)
            self.send_hud([0x06, 0, 0, 0, 0, 0, 0, 0, 0, acc_char])
            return
        if limit > 0:
            arr = [0x06, self._as_dgt(speed // 100), self._as_dgt(speed // 10), self._as_dgt(speed), 0xff,
                   self._as_dgt(limit // 100), self._as_dgt(limit // 10), self._as_dgt(limit),
                   0xff if speed > limit else 0x00, acc_char]
        else:
            arr = [0x06, 0x00, 0x00, 0x00, 0x00, self._as_dgt(speed // 100), self._as_dgt(speed // 10),
                   self._as_dgt(speed), 0x00, acc_char]
        self.send_hud(arr)

    def set_time(self, hours: int, minutes: int, traffic: bool = False, flag: bool = False):
        traffic_char = 0xff if traffic else 0x00
        flag_char = 0xff if flag else 0x00
        if hours > 99:
            arr = [0x05, traffic_char, self._as_dgt(hours // 1000), self._as_dgt(hours // 100), 0x00,
                   self._as_dgt(hours // 10), self._as_dgt(hours), 0xff, flag_char]
        else:
            arr = [0x05, traffic_char, self._as_dgt(hours // 10), self._as_dgt(hours), 0xff,
                   self._as_dgt(minutes // 10), self._as_dgt(minutes), 0x00, flag_char]
        self.send_hud(arr)

    def set_gps(self, state: bool):
        if state:
            self.send_hud([0x07, 0x01])
        self.gps = state

    def set_speed_control(self, on: bool = True):
        self.send_hud([0x04, 0x01 if on else 0x00])

    def set_compass(self, direction: float):
        if direction > 337.5 or direction <= 22.5:
            self.set_direction(OutAngle.Straight, OutType.ArrowOnly)
        elif 22.5 < direction <= 67.5:
            self.set_direction(OutAngle.EasyRight, OutType.ArrowOnly)
        elif 67.5 < direction <= 112.5:
            self.set_direction(OutAngle.Right, OutType.ArrowOnly)
        elif 112.5 < direction <= 157.5:
            self.set_direction(OutAngle.SharpRight, OutType.ArrowOnly)
        elif 157.5 < direction <= 202.5:
            self.set_direction(OutAngle.Down, OutType.ArrowOnly)
        elif 202.5 < direction <= 247.5:
            self.set_direction(OutAngle.SharpLeft, OutType.ArrowOnly)
        elif 247.5 < direction <= 292.5:
            self.set_direction(OutAngle.Left, OutType.ArrowOnly)
        elif 292.5 < direction <= 337.5:
            self.set_direction(OutAngle.EasyLeft, OutType.ArrowOnly)
        self.clear()

    def send_hud(self, buf: list):
        print("buf", buf)
        n = len(buf)
        chars = []
        crc = uint8(0xeb + n + n)
        chars.append(0x10)
        chars.append(0x7b)
        chars.append(n + 6)
        if n == 0xa:
            chars.append(0x10)
        chars.append(n)
        chars.append(0x00)
        chars.append(0x00)
        chars.append(0x00)
        chars.append(0x55)
        chars.append(0x15)
        for char in buf:
            crc = uint8(crc + char)
            chars.append(char)
            if char == 0x10:
                chars.append(0x10)
        chars.append((-crc) & 0xff)
        chars.append(0x10)
        chars.append(0x03)
        self.send_packet(chars)

    def send_packet(self, buff):
        encoded = [bytes(chr(char), 'raw_unicode_escape') for char in buff]
        if self.debug:
            print("raw", buff)
            print("enc", encoded)
        else:
            for char in buff:
                self.ser.write(bytes(chr(char), 'raw_unicode_escape'))
            time.sleep(0.2)
            print(self.ser.read_all())


if __name__ == '__main__':
    import sys
    device = sys.argv[1] if len(sys.argv) > 1 else None
    c = Controller(device)