import machine
import micropython
from picographics import PicoGraphics, DISPLAY_INKY_PACK
import network
from network_manager import NetworkManager
import WIFI_CONFIG
import uasyncio
import time
import gc
import wakeup


BUTTON_DOWN = 11
BUTTON_A = 12
BUTTON_B = 13
BUTTON_C = 14
BUTTON_UP = 15
BUTTON_USER = None  # User button not available on W

BUTTON_MASK = 0b11111 << 11

SYSTEM_VERY_SLOW = 0
SYSTEM_SLOW = 1
SYSTEM_NORMAL = 2
SYSTEM_FAST = 3
SYSTEM_TURBO = 4

UPDATE_NORMAL = 0
UPDATE_MEDIUM = 1
UPDATE_FAST = 2
UPDATE_TURBO = 3

LED = 22
ENABLE_3V3 = 10
BUSY = 26

WIDTH = 296
HEIGHT = 128

SYSTEM_FREQS = [
    4000000,
    12000000,
    48000000,
    133000000,
    250000000
]

BUTTONS = {
    BUTTON_DOWN: machine.Pin(BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN),
    BUTTON_A: machine.Pin(BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN),
    BUTTON_B: machine.Pin(BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN),
    BUTTON_C: machine.Pin(BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN),
    BUTTON_UP: machine.Pin(BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN),
}

WAKEUP_MASK = 0


def woken_by_button():
    return wakeup.get_gpio_state() & BUTTON_MASK > 0


def pressed_to_wake(button):
    return wakeup.get_gpio_state() & (1 << button) > 0


def reset_pressed_to_wake():
    wakeup.reset_gpio_state()


def pressed_to_wake_get_once(button):
    global WAKEUP_MASK
    result = (wakeup.get_gpio_state() & ~WAKEUP_MASK & (1 << button)) > 0
    WAKEUP_MASK |= (1 << button)
    return result


def system_speed(speed):
    try:
        machine.freq(SYSTEM_FREQS[speed])
    except IndexError:
        pass


class Badger2040W():
    def __init__(self):
        self.display = PicoGraphics(DISPLAY_INKY_PACK)
        self._led = machine.PWM(machine.Pin(LED))
        self._led.freq(1000)
        self._led.duty_u16(0)
        self._update_speed = 0

    def __getattr__(self, item):
        # Glue to redirect calls to PicoGraphics
        return getattr(self.display, item)

    def update(self):
        t_start = time.ticks_ms()
        self.display.update()
        t_elapsed = time.ticks_ms() - t_start

        delay_ms = [4700, 2600, 900, 250][self._update_speed]

        if t_elapsed < delay_ms:
            time.sleep((delay_ms - t_elapsed) / 1000)

    def set_update_speed(self, speed):
        self.display.set_update_speed(speed)
        self._update_speed = speed

    def led(self, brightness):
        brightness = max(0, min(255, brightness))
        self._led.duty_u16(int(brightness * 256))

    def invert(self, invert):
        raise RuntimeError("Display invert not supported in PicoGraphics.")

    def thickness(self, thickness):
        raise RuntimeError("Thickness not supported in PicoGraphics.")

    def halt(self):
        time.sleep(0.05)
        enable = machine.Pin(ENABLE_3V3, machine.Pin.OUT)
        enable.off()
        while not self.pressed_any():
            pass

    def pressed(self, button):
        return BUTTONS[button].value() == 1 or pressed_to_wake_get_once(button)

    def pressed_any(self):
        for button in BUTTONS.values():
            if button.value():
                return True
        return False

    @micropython.native
    def icon(self, data, index, data_w, icon_size, x, y):
        s_x = (index * icon_size) % data_w
        s_y = int((index * icon_size) / data_w)

        for o_y in range(icon_size):
            for o_x in range(icon_size):
                o = ((o_y + s_y) * data_w) + (o_x + s_x)
                bm = 0b10000000 >> (o & 0b111)
                if data[o >> 3] & bm:
                    self.display.pixel(x + o_x, y + o_y)

    def image(self, data, w, h, x, y):
        for oy in range(h):
            row = data[oy]
            for ox in range(w):
                if row & 0b1 == 0:
                    self.display.pixel(x + ox, y + oy)
                row >>= 1

    def status_handler(self, mode, status, ip):
        print(mode, status, ip)
        self.display.set_pen(15)
        self.display.clear()
        self.display.set_pen(0)
        if status:
            self.display.text("Connected!", 10, 10, 300, 0.5)
            self.display.text(ip, 10, 30, 300, 0.5)
        else:
            self.display.text("Connecting...", 10, 10, 300, 0.5)
        self.update()

    def isconnected(self):
        return network.WLAN(network.STA_IF).isconnected()

    def ip_address(self):
        return network.WLAN(network.STA_IF).ifconfig()[0]

    def connect(self):
        if WIFI_CONFIG.COUNTRY == "":
            raise RuntimeError("You must populate WIFI_CONFIG.py for networking.")
        self.display.set_update_speed(2)
        network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=self.status_handler)
        uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
        gc.collect()
