import analogio
import microcontroller
from .is32fl3733 import IS31FL3733

try:
    # usebuilt-in matrix if it is available
    from matrix import Matrix
except ImportError:
    from ..matrix import Matrix
    from board import R1, R2, R3, R4, R5, R6, R7, R8, C1, C2, C3, C4, C5, C6, C7, C8

    Matrix.ROWS = (R1, R2, R3, R4, R5, R6, R7, R8)
    Matrix.COLS = (C1, C2, C3, C4, C5, C6, C7, C8)
    Matrix.ROW2COL = False

# fmt: off
# ESC   1   2   3   4   5   6   7   8   9   0   -   =  BACKSPACE
# TAB   Q   W   E   R   T   Y   U   I   O   P   [   ]   |
# CAPS  A   S   D   F   G   H   J   K   L   ;   "      ENTER
#LSHIFT Z   X   C   V   B   N   M   ,   .   /         RSHIFT
# LCTRL LGUI LALT          SPACE         RALT MENU  L1 RCTRL
COORDS = (
    0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,
    27,26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14,
    28,29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,     40,
    52,51, 50, 49, 48, 47, 46, 45, 44, 43, 42,         41,
    53,  54, 55,             56,           57, 58, 59, 60
)


BATTERY_LIMIT = 3100  # Cutoff voltage [mV].
BATTERY_FULLLIMIT = 4190  # Full charge definition [mV].
BATTERY_DELTA = 10  # mV between each element in the SoC vector.

BATTERY_VOLTAGE = (
    0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,
    2,  2,  2,  2,  2,  2,  2,  2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,
    4,  5,  5,  5,  6,  6,  7,  7,  8,  8,  9,  9, 10, 11, 12, 13, 13, 14, 15, 16,
    18, 19, 22, 25, 28, 32, 36, 40, 44, 47, 51, 53, 56, 58, 60, 62, 64, 66, 67, 69,
    71, 72, 74, 76, 77, 79, 81, 82, 84, 85, 85, 86, 86, 86, 87, 88, 88, 89, 90, 91,
    91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 100
)

battery_in = analogio.AnalogIn(microcontroller.pin.P0_02)


def battery_level():
    # (3300 * 2 * battery.value) >> 16
    voltage = (3300 * battery_in.value) >> 15
    i = (voltage - BATTERY_LIMIT) // BATTERY_DELTA
    if i >= len(BATTERY_VOLTAGE):
        i = len(BATTERY_VOLTAGE) - 1
    elif i < 0:
        i = 0
    return BATTERY_VOLTAGE[i]


leds_x = bytearray((
    0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 216,
    220,200, 184, 168, 152, 136, 120, 104, 88, 72, 56, 40, 24, 4,
    6, 27, 43, 59, 75, 91, 107, 123, 139, 155, 171, 187, 214,
    210, 180, 164, 148, 132, 116, 100, 84, 68, 52, 36, 10,
    2, 22, 42, 102, 162, 182, 202, 222,
    123, 82
))

leds_y = bytearray((
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
    48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
    64, 64, 64, 64, 64, 64, 64, 64,
    64, 64
))

angle = bytearray((
    180, 178, 176, 173, 168, 160, 146, 128, 109, 96, 87, 82, 79, 76,
    69, 71, 72, 75, 79, 87, 109, 146, 168, 176, 180, 183, 184, 186,
    170, 170, 170, 170, 170, 170, 170, 64, 64, 64, 64, 64, 64,
    57, 54, 51, 46, 36, 9, 229, 213, 206, 202, 200, 198,
    203, 205, 209, 243, 40, 46, 50, 52,
    13, 225
))

distance = bytearray((
    116, 101, 86, 71, 57, 45, 35, 32, 35, 45, 57, 71, 86, 108, 109, 89, 73,
    58, 43, 28, 17, 17, 28, 43, 58, 73, 89, 109, 106, 85, 69, 53, 37, 21, 5,
    11, 27, 43, 59, 75, 102, 99, 69, 54, 39, 25, 16, 20, 32, 46, 62, 77, 103,
    114, 95, 76, 33, 59, 76, 95, 114,
    33, 43
))
# fmt: on


def hsv_to_rgb(h, s, v):
    i = (h * 6) >> 8
    f = (h * 6) & 0xFF

    p = (v * (256 - s)) >> 8
    q = (v * (65536 - s * f)) >> 16
    t = (v * (65536 - s * (256 - f))) >> 16

    if i == 0:
        return (v, t, p)
    if i == 1:
        return (q, v, p)
    if i == 2:
        return (p, v, t)
    if i == 3:
        return (p, q, v)
    if i == 4:
        return (t, p, v)
    return (v, p, q)


def wheel(h):
    i = (h * 3) >> 8
    a = (h * 3) & 0xFF
    b = 255 - a

    if i == 0:
        return (b, a, 0)
    if i == 1:
        return (0, b, a)

    return (a, 0, b)


def wheel2(h, v):
    i = (h * 3) >> 8
    a = (h * 3) & 0xFF
    b = 255 - a

    a = a * v // 255
    b = b * v // 255

    if i == 0:
        return (b, a, 0)
    if i == 1:
        return (0, b, a)

    return (a, 0, b)


class Backlight:
    def __init__(self):
        self.dev = IS31FL3733()
        self._hid_leds = 0
        self._bt_led = None
        self.pixel = self.dev.pixel

        self.enabled = True
        self.hsv = [0, 255, 255]
        self.keys = {}
        self.n = 0

        self.modes = (
            self.off,
            self.mono,
            self.gradient,
            self.spectrum,
            self.spectrum_x,
            self.spectrum_y,
            self.elapse,
            self.broadcast,
            self.blackhole,
            self.pinwheel,
            self.beacon,
            self.beacon2
        )
        self.set_mode(6)
        self.enabled = False

    @property
    def hue(self):
        return self.hsv[0]

    @hue.setter
    def hue(self, h):
        self.hsv[0] = h & 0xFF
        self.refresh()

    @property
    def sat(self):
        return self.hsv[1]

    @sat.setter
    def sat(self, s):
        self.hsv[1] = 0 if s < 0 else (255 if s > 255 else s)
        self.refresh()

    @property
    def val(self):
        return self.dev.brightness

    @val.setter
    def val(self, v):
        self.set_brightness(v)

    def set_brightness(self, v):
        if v <= 0:
            self.enabled = False
            self.off()
        else:
            self.enabled = True
            self.dev.brightness = v if v < 0xFF else 0xFF
            if not self.dynamic:
                self.mode_function()

    def on(self, r=0xFF, g=0xFF, b=0xFF):
        for i in range(63):
            self.pixel(i, r, g, b)
        self.update()

    def off(self):
        self.dev.clear()
        self.update()

    def toggle(self):
        self.enabled = not self.enabled
        if self.enabled:
            self.set_mode(self.mode)
        else:
            self.off()

    def mono(self):
        self.on(*hsv_to_rgb(*self.hsv))

    def gradient(self):
        h0, s0, v0 = self.hsv
        for i in range(63):
            h = (leds_y[i] + h0) & 0xFF
            self.pixel(i, *hsv_to_rgb(h, s0, v0))
        self.update()

    def spectrum(self, offset=0):
        self.n = (self.n + 1) & 0xFF
        r, g, b = wheel(self.n)
        for i in range(63):
            self.pixel(i, r, g, b)
        self.update()
        return True

    def spectrum_x(self):
        n = self.n
        for i in range(63):
            h = (leds_x[i] + n) & 0xFF
            self.pixel(i, *wheel(h))
        self.update()
        self.n = (n + 1) & 0xFF
        return True

    def spectrum_y(self):
        n = self.n
        for i in range(63):
            h = (leds_y[i] + n) & 0xFF
            self.pixel(i, *wheel(h))
        self.update()
        self.n = (n + 1) & 0xFF
        return True

    def broadcast(self):
        n = self.n
        for i in range(63):
            self.pixel(i, *wheel((distance[i] - n) & 0xFF))
        self.update()
        self.n = (n + 2) & 0xFF
        return True

    def blackhole(self):
        n = self.n
        for i in range(63):
            self.pixel(i, *wheel2((distance[i] + n) & 0xFF, distance[i] * 2 - 10))
        self.update()
        self.n = (n + 2) & 0xFF
        return True

    def pinwheel(self):
        n = self.n
        for i in range(63):
            self.pixel(i, *wheel((angle[i] + n) & 0xFF))
        self.update()
        self.n = (n + 2) & 0xFF
        return True

    def beacon(self):
        n = self.n
        for i in range(63):
            offset = (angle[i] + n) & 0xFF
            if offset < 64:
                offset <<= 2
            else:
                offset = 0
            self.pixel(i, *wheel(offset))
        self.update()
        self.n = (n + 2) & 0xFF
        return True

    def beacon2(self):
        n = self.n
        for i in range(63):
            offset = (angle[i] + n) & 0xFF
            if offset < 64:
                offset <<= 2
            elif 128 < offset and offset < 192:
                offset = (offset - 128) << 2
            else:
                offset = 0
            self.pixel(i, *wheel(offset))
        self.update()
        self.n = (n + 2) & 0xFF
        return True

    def handle_key(self, key, pressed):
        if pressed and self.enabled and self.mode == 6:
            self.keys[key] = 255

    def elapse(self):
        if 0 == len(self.keys):
            return False
        for i in self.keys.keys():
            t = self.keys[i]
            self.pixel(i, *wheel2(255 - t, t))
            if t >= 4:
                self.keys[i] = t - 4
            elif t > 0:
                self.keys[i] = t - 1
            else:
                self.keys.pop(i)
        self.update()
        return True

    def set_hid_leds(self, v):
        if self._hid_leds != v:
            self._hid_leds = v
            g = 128 if (self._hid_leds & 2) else 0
            self.dev.update_pixel(28, 0, g, 0)
            self.refresh()

    def set_bt_led(self, v):
        if v == 0:
            v = 10
        if v is not None:
            self.dev.set_mode(v, 2)
        if self._bt_led is not None:
            self.dev.set_mode(self._bt_led, 0)
        self._bt_led = v

    def update(self):
        if self._hid_leds & 2:
            self.pixel(28, 0, 0x80, 0)
        self.dev.update()

    def check(self):
        if self.enabled and self.dynamic:
            return self.mode_function()
        return False

    def refresh(self):
        if self.enabled and not self.dynamic:
            self.mode_function()

    def next(self):
        self.set_mode(self.mode + 1)

    def set_mode(self, mode):
        self.enabled = True
        self.dev.clear()
        self.mode = mode if mode < len(self.modes) else 0
        self.mode_function = self.modes[self.mode]
        if self.mode == 6:
            self.keys.clear()
        if self.mode >= 3:
            self.dynamic = True
        else:
            self.dynamic = False
            self.mode_function()