from font import cntr_st, rght_st, sz_to_w
from utils import colour
from bmp_file_reader import BMPFileReader
from api import getMediaPlayerData, getLightData, playPause, nextTrack, prevTrack, setVolume, toggleLight, setBrightness

class Screen():
    def __init__(self, n: str) -> None:
        self.name = n
        self.d = {}
        self.prev = {}

    def display(self, lcd) -> bool:
        lcd.fill(0x0000)
        cntr_st(lcd, lcd.width, self.name, 20, 2, 255, 255, 255)
        return False

    def update(self, lcd) -> bool:
        return False

    def handleButtons(self, up: bool, down: bool, left: bool, right: bool, keyA: bool, keyB: bool, keyX: bool, keyY: bool, ctrl: bool) -> bool:
        return False
    
    def _updateData(self) -> dict:
        self.prev = self.d.copy()
        return {}

    def _invalidConfig(self, lcd) -> None:
        cntr_st(lcd, lcd.width, "Invalid config", lcd.height//2, 2, 255, 255, 255)
        lcd.show()

class MediaScreen(Screen):
    def __init__(self, n: str, e: str) -> None:
        super().__init__(n)
        self.e = e
        self.valid = e != None and e != ""
        self.str_start_i = [-1, -1, -1]
    
    def display(self, lcd) -> bool:
        super().display(lcd)
        if (not self.valid):
            self._invalidConfig(lcd)
            return False
        if (self.d == {}):
            self.d = self._updateData()
        y_offset = 62
        if (self.d["playing"]):
            with open("images/play.bmp", "rb") as file_handle:
                reader = BMPFileReader(file_handle)
                x_offset = lcd.width//2 - reader.get_width()//2
                for row_i in range(0, reader.get_height()):
                    row = reader.get_row(row_i)
                    for col_i, color in enumerate(row):
                        if (color.red != 0 or color.green != 0 or color.blue != 0):
                            lcd.pixel(col_i + x_offset, row_i + y_offset, 0xffff)
        else:
            with open("images/pause.bmp", "rb") as file_handle:
                reader = BMPFileReader(file_handle)
                x_offset = lcd.width//2 - reader.get_width()//2
                for row_i in range(0, reader.get_height()):
                    row = reader.get_row(row_i)
                    for col_i, color in enumerate(row):
                        if (color.red != 0 or color.green != 0 or color.blue != 0):
                            lcd.pixel(col_i + x_offset, row_i + y_offset, 0xffff)
        self.__updateMediaPositionBar(lcd, self.d["media_position"], self.d["media_duration"])
        if (self.d["media_duration"] != None):
            mins = int(self.d["media_duration"] // 60)
            secs = int(self.d["media_duration"] % 60)
            rght_st(lcd, f"{mins}:{secs:02}", lcd.width, lcd.height - 16, 1, 180, 180, 180)

        txts = [
            {
                "s": "S" + str(self.d["media_season"]) + " E" + str(self.d["media_episode"]) if self.d["media_content_type"] == "tvshow" else self.d["media_album_name"],
                "sz": 2,
                "o": 106
            },
            {
                "s": self.d["media_series_title"] if self.d["media_content_type"] == "tvshow" else self.d["media_artist"],
                "sz": 2,
                "o": 82
            },
            {
                "s": self.d["media_title"],
                "sz": 3,
                "o": 56
            }
        ]
        scroll = False
        for i, txt in enumerate(txts):
            cs = lcd.width//sz_to_w(txt["sz"])
            if (len(txt["s"]) > cs):
                scroll = True
                if (self.str_start_i[i] + cs >= len(txt["s"])):
                    self.str_start_i[i] = 0
                else:
                    self.str_start_i[i] += 1
                s = txt["s"][self.str_start_i[i]:self.str_start_i[i] + cs]
            else:
                self.str_start_i[i] = -1
                s = txt["s"]
            cntr_st(lcd, lcd.width, s, lcd.height - txt["o"], txt["sz"], 255, 255, 255)

        lcd.show()
        if (scroll):
            self.d = {}
            return True
        return False

    def __updateMediaPositionBar(self, lcd, p: int, d: int):
        if (d > 0):
            for x in range (0, (lcd.width * p)//d):
                lcd.pixel(x, lcd.height - 5, 0xffff)
                lcd.pixel(x, lcd.height - 4, 0xffff)
                lcd.pixel(x, lcd.height - 3, 0xffff)
                lcd.pixel(x, lcd.height - 2, 0xffff)
                lcd.pixel(x, lcd.height - 1, 0xffff)
                lcd.pixel(x, lcd.height, 0xffff)

    def update(self, lcd) -> bool:
        if (not self.valid):
            super().display(lcd)
            self._invalidConfig(lcd)
            return False
        self._updateData()
        # if same media is playing (same title and duration), just update the position bar
        if (self.d["media_title"] == self.prev["media_title"] and self.d["media_duration"] == self.prev["media_duration"] and self.d["playing"] == self.prev["playing"]):
            self.__updateMediaPositionBar(lcd, self.d["media_position"], self.d["media_duration"])
            lcd.show()
            return False
        # otherwise redraw the whole screen
        else:
            return self.display(lcd)

    def _updateData(self) -> dict:
        super()._updateData()
        self.d = getMediaPlayerData(self.e)
        return self.d
    
    def handleButtons(self, up: bool, down: bool, left: bool, right: bool, keyA: bool, keyB: bool, keyX: bool, keyY: bool, ctrl: bool) -> bool:
        a = False
        v = self.d["volume_level"] if "volume_level" in self.d else None
        if (v != None and up and v < 1):
            vn = min(1.0, v + 0.08)
            setVolume(self.e, vn)
            self.d["volume_level"] = vn
            a = True
        elif (v != None and down and v > 0.01):
            vn = max(0.01, v - 0.08)
            setVolume(self.e, vn)
            self.d["volume_level"] = vn
            a = True
        if (keyX):
            nextTrack(self.e)
        elif (keyY):
            prevTrack(self.e)
        if (keyA):
            playPause(self.e)
        return a

class LightsScreen(Screen):
    def __init__(self, n: str, es: list) -> None:
        super().__init__(n)
        self.es = es
        self.valid = es != None and len(es) != 0
    
    def display(self, lcd) -> bool:
        super().display(lcd)
        if (not self.valid):
            self._invalidConfig(lcd)
            return False
        if (self.d == {}):
            self._updateData()
        # display up to four lights as defined in env.py
        # for each defined entity (up to a max total of 4), display its data in a 2x2 grid
        for i in range(0, len(self.d)):
            h = lcd.height - 30
            self.__displayLightEntity(lcd, i, lcd.width//2, h//2, lcd.width//2 * (i % 2), h//2 * (i//2) + 30, self.es[i]["name"], self.d[i])
        lcd.show()
        return False
    
    def __displayLightEntity(self, lcd, i: int, w: int, h: int, xo: int, yo: int, n: str, d) -> None:
        # if the light is turned on, display the filled-in lightbulb icon in the colour of the light, centrally in the light's grid square
        if (d["on"]):
            color = colour(d["rgb_color"][0], d["rgb_color"][1], d["rgb_color"][2])
            with open("images/lightbulb-on.bmp", "rb") as file_handle:
                reader = BMPFileReader(file_handle)
                img_height = reader.get_height()
                x_offset = w//2 + xo - reader.get_width()//2
                y_offset = h//2 + yo - reader.get_height()//2 - 4
                for row_i in range(0, reader.get_height()):
                    row = reader.get_row(row_i)
                    for col_i, color in enumerate(row):
                        r = d["rgb_color"][0] if (color.red) != 0 else 0
                        g = d["rgb_color"][1] if (color.green) != 0 else 0
                        b = d["rgb_color"][2] if (color.blue) != 0 else 0
                        if (color.red != 0 or color.green != 0 or color.blue != 0):
                            lcd.pixel(col_i + x_offset, row_i + y_offset, colour(r,g,b))
        # otherwise display the outline lightbulb icon in grey, centrally in the light's grid square
        else:
            color = colour(80, 80, 80)
            with open("images/lightbulb-off.bmp", "rb") as file_handle:
                reader = BMPFileReader(file_handle)
                img_height = reader.get_height()
                x_offset = w//2 + xo - reader.get_width()//2
                y_offset = h//2 + yo - reader.get_height()//2 - 4
                for row_i in range(0, reader.get_height()):
                    row = reader.get_row(row_i)
                    for col_i, color in enumerate(row):
                        if (color.red != 0 or color.green != 0 or color.blue != 0):
                            lcd.pixel(col_i + x_offset, row_i + y_offset, colour(color.red, color.green, color.blue))
                        else:
                            lcd.pixel(col_i + x_offset, row_i + y_offset, colour(0,0,0))
        # display the name of the light 8px below the lightbulb icon
        cntr_st(lcd, w, n, y_offset + img_height + 8, 2, 220, 220, 220, xo)

    def update(self, lcd) -> bool:
        if (not self.valid):
            super().display(lcd)
            self._invalidConfig(lcd)
            return False
        self._updateData()
        # for each light to be displayed
        for i in range(0, len(self.d)):
            # if its settings have changed, re-draw them without clearing the display
            if (self.d[i] != self.prev[i]):
                h = lcd.height - 30
                self.__displayLightEntity(lcd, i, lcd.width//2, h//2, lcd.width//2 * (i % 2), h//2 * (i//2) + 30, self.es[i]["name"], self.d[i])
        lcd.show()
        return False

    def _updateData(self) -> dict:
        super()._updateData()
        for i in range(0, min(len(self.es), 4)):
            self.d[i] = getLightData(self.es[i]["id"])
        return self.d
    
    def handleButtons(self, up: bool, down: bool, left: bool, right: bool, keyA: bool, keyB: bool, keyX: bool, keyY: bool, ctrl: bool) -> bool:
        e = min(len(self.es), 4)
        b = [keyA, keyB, keyX, keyY]
        a = False
        for i in range(0, e):
            # if button for light clicked, toggle the light
            if (b[i]):
                toggleLight(self.es[i]["id"])
                a = True
            # if up/down clicked, adjust brightness for all lights that are turned on
            pcfg = i in self.d and "on" in self.d[i] and self.d[i]["on"] and "brightness" in self.d[i]
            if (up and pcfg):
                v = min(255, self.d[i]["brightness"] + 35)
                setBrightness(self.es[i]["id"], v)
                self.d[i]["brightness"] = v
                a = True
            elif (down and pcfg):
                v = max(1, self.d[i]["brightness"] - 35)
                setBrightness(self.es[i]["id"], v)
                self.d[i]["brightness"] = v                
                a = True
        return a
    
class UnknownScreen(Screen):
    def __init__(self, n: str) -> None:
        super().__init__(n)
    
    def display(self, lcd) -> bool:
        super().display(lcd)
        self._invalidConfig(lcd)
        return False
    
    def update(self, lcd) -> bool:
        return False

    def _updateData(self) -> dict:
        return {}

    def handleButtons(self, up: bool, down: bool, left: bool, right: bool, keyA: bool, keyB: bool, keyX: bool, keyY: bool, ctrl: bool) -> bool:
        return False
