from sh1106 import SH1106_I2C
from rotary_irq_esp import RotaryIRQ

import uasyncio as asyncio
from time import ticks_ms, ticks_add, ticks_diff


from homie.constants import BOOLEAN, TRUE, FALSE, FLOAT
from homie.property import HomieProperty
from homie.node import HomieNode
from homie.device import await_ready_state
from machine import Pin
from primitives.pushbutton import Pushbutton


class PlantBoxControlPanel(HomieNode):

    def __init__(self, id, name, waterlevel_sensor, bmp280,
                 i2c, pin_clk, pin_dt, pin_sw):
        super().__init__(id=id, name=name, type="controller")
        self.i2c = i2c
        self.bmp280 = bmp280
        self.display = SH1106_I2C(
            i2c=self.i2c, width=128, height=64)
        self.display.rotate(True, False)

        self.rotary_encoder = RotaryIRQ(
            pin_num_clk=int(str(pin_clk)[4:][:-1]),
            pin_num_dt=int(str(pin_dt)[4:][:-1]),
            min_val=0,
            max_val=3,
            reverse=False,
            pull_up=True,
            range_mode=RotaryIRQ.RANGE_WRAP)

        self.waterlevel_sensor = waterlevel_sensor
        pin_sw.init(mode=Pin.IN, pull=Pin.PULL_UP)
        self.button = Pushbutton(pin_sw, suppress=True, sense=1)

        self._screen_on = False
        self.needs_redraw = False
        self.update_interval = 0.3

        self.property_button_pressed = HomieProperty(
            id="button_pressed",
            name="Knöpgen gedrückt",
            settable=True,
            default=FALSE,
            on_message=self._on_button_pressed_msg,
            datatype=BOOLEAN,
        )
        self.add_property(self.property_button_pressed)

        self.property_screen_on = HomieProperty(
            id="screen_on",
            name="Bildschrim angeschaltet",
            settable=True,
            default=FALSE,
            on_message=self.on_screen_on_msg,
            datatype=BOOLEAN,
        )
        self.add_property(self.property_screen_on)

        self.screen_timeout = 30
        self.screen_timeout_ticks = 0
        self.property_screen_timeout = HomieProperty(
            id="screen_timeout",
            name="Bildschrimtimeout",
            settable=True,
            default=str(self.screen_timeout),
            on_message=self.on_screen_timeout_msg,
            datatype=FLOAT,
        )
        self.add_property(self.property_screen_timeout)

        self.on_button_pressed = None
        self.on_button_released = None
        asyncio.create_task(self._update_data_async())

    def _on_button_pressed_msg(self,  topic, payload, retained):
        if {FALSE: False, TRUE: True}[payload] and not self.button.rawstate():
            self._on_button_pressed()
            self._on_button_released()

    def _on_button_pressed(self):
        self.property_button_pressed.value = TRUE
        self.needs_redraw = True
        if self.on_button_pressed:
            self.on_button_pressed()

    def _on_button_released(self):
        self.property_button_pressed.value = FALSE
        self._set_screen_on(True)
        self.needs_redraw = True
        if self.on_button_released:
            self.on_button_released()

    def on_screen_on_msg(self, topic, payload, retained):
        self._set_screen_on({FALSE: False, TRUE: True}[payload])

    def on_screen_timeout_msg(self, topic, payload, retained):
        new_screen_timeout = float(payload)
        self.screen_timeout_ticks = \
            ticks_add(
                ticks_diff(
                    self.screen_timeout_ticks,
                    int(self.screen_timeout * 1000)),
                int(new_screen_timeout * 1000))
        self.screen_timeout = new_screen_timeout

    def reset_screen_timout(self):
        self.screen_timeout_ticks = ticks_add(
            ticks_ms(),
            int(self.screen_timeout * 1000))

    # @await_ready_state
    async def _update_data_async(self):
        self.button.press_func(self._on_button_pressed)
        self.button.release_func(self._on_button_released)
        rotary_last = 0
        last_temp = 0
        while True:
            if ticks_diff(self.screen_timeout_ticks, ticks_ms()) <= 0:
                self._set_screen_on(False)
            if self._screen_on:
                if rotary_last != self.rotary_encoder.value():
                    rotary_last = self.rotary_encoder.value()
                    self.needs_redraw = True
                if last_temp != self.bmp280.temperature:
                    self.needs_redraw = True
                    last_temp = self.bmp280.temperature
                if self.needs_redraw:
                    self.needs_redraw = False
                    # self.display.fill(0)
                    # self.display.text(str(self.rotary_encoder.value()), 0, 20)
                    # self.display.text(str(self.button.rawstate()), 0, 30)
                    self.display.fill_rect(0, 0, 128, 10, 0) 
                    self.display.text("{:1.2f}°C".format(self.bmp280.temperature), 1, 1)
                    actions = [("Einst.", None), ("Wasser", lambda : self._set_screen_on(False))]
                    self.rotary_encoder.set(max_val=len(actions) - 1)
                    for i, (a, br) in enumerate(actions):
                        selected = i == self.rotary_encoder.value() 
                        color = 0 if selected else 1
                        screen_dif = int(128 / len(actions))
                        self.display.fill_rect(
                            screen_dif * i+1, 50, screen_dif-2, 10, 1-color)
                        self.display.text(a, screen_dif*i+2, 51, color)
                        if not self.screen_just_turned_on:
                            if selected: 
                                self.on_button_released = br
                    self.display.show()
                    self.screen_just_turned_on = False
            else:
                self.rotary_encoder.set(value=0)    
                self.on_button_released, self.on_button_released = None, None
            await asyncio.sleep_ms(int(self.update_interval*1000.0))

    @property
    def screen_on(self):
        return self._screen_on

    @screen_on.setter
    def set_screen_on(self, on):
        self._set_screen_on(on)

    def _set_screen_on(self, on):
        if on == self._screen_on:
            if on:
                self.reset_screen_timout()
        else:
            self._screen_on = on
            if on:
                # self.display.poweron()
                self.reset_screen_timout()
                self.needs_redraw = True
                self.screen_just_turned_on = True
            else:
                self.display.poweroff()
            self.property_screen_on = TRUE if on else FALSE
