import logging
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from math import pi
from ks_includes.KlippyGcodes import KlippyGcodes
from ks_includes.screen_panel import ScreenPanel


class Panel(ScreenPanel):

    def __init__(self, screen, title):
        super().__init__(screen, title)
        self.da_size = self._gtk.img_scale * 2
        self.preview = Gtk.DrawingArea(width_request=self.da_size, height_request=self.da_size)
        self.preview.set_size_request(-1, self.da_size * 2)
        self.preview.connect("draw", self.on_draw)
        self.preview_label = Gtk.Label(label='')
        self.preset_list = self._gtk.HomogeneousGrid()
        self.color_data = [0, 0, 0, 0]
        self.color_order = 'RGBW'
        self.presets = {"off": [0.0, 0.0, 0.0, 0.0]}
        self.scales = {}
        self.buttons = []
        self.leds = self._printer.get_leds()
        self.current_led = self.leds[0] if len(self.leds) == 1 else None
        self.open_selector(None, self.current_led)

    def color_available(self, idx):
        return (
            (idx == 0 and 'R' in self.color_order)
            or (idx == 1 and 'G' in self.color_order)
            or (idx == 2 and 'B' in self.color_order)
            or (idx == 3 and 'W' in self.color_order)
        )

    def activate(self):
        if self.current_led is not None:
            self.set_title(f"{self.current_led}")

    def set_title(self, title):
        self._screen.base_panel.set_title(self.prettify(title))

    def back(self):
        if len(self.leds) > 1 and self.current_led:
            self.set_title(self._screen.panels[self._screen._cur_panels[-1]].title)
            self.open_selector(led=None)
            return True
        return False

    def open_selector(self, widget=None, led=None):
        for child in self.content.get_children():
            self.content.remove(child)
        if led is None:
            self.content.add(self.led_selector())
        else:
            self.content.add(self.color_selector(led))
        self.content.show_all()

    def led_selector(self):
        self.current_led = None
        columns = 3 if self._screen.vertical_mode else 4
        grid = self._gtk.HomogeneousGrid()
        for i, led in enumerate(self.leds):
            name = led.split()[1] if len(led.split()) > 1 else led
            button = self._gtk.Button(None, name.upper(), style=f"color{(i % 4) + 1}")
            button.connect("clicked", self.open_selector, led)
            grid.attach(button, (i % columns), int(i / columns), 1, 1)
        scroll = self._gtk.ScrolledWindow()
        scroll.add(grid)
        return scroll

    def color_selector(self, led):
        logging.info(led)
        self.current_led = led
        self.set_title(f"{self.current_led}")
        grid = self._gtk.HomogeneousGrid()
        self.color_order = self._printer.get_led_color_order(led)
        if self.color_order is None:
            logging.error("Error: Color order is None")
            self.back()
            return
        on = [1 if self.color_available(i) else 0 for i in range(4)]
        self.presets["on"] = on
        scale_grid = self._gtk.HomogeneousGrid()
        for idx, col_value in enumerate(self.color_data):
            if not self.color_available(idx):
                continue
            color = [0, 0, 0, 0]
            color[idx] = 1
            button = self._gtk.Button()
            preview = Gtk.DrawingArea(width_request=self.da_size, height_request=self.da_size)
            preview.connect("draw", self.on_draw, color)
            button.set_image(preview)
            button.connect("clicked", self.apply_preset, color)
            button.set_hexpand(False)
            scale = Gtk.Scale.new_with_range(orientation=Gtk.Orientation.HORIZONTAL, min=0, max=255, step=1)
            scale.set_value(round(col_value * 255))
            scale.set_digits(0)
            scale.set_hexpand(True)
            scale.set_has_origin(True)
            scale.get_style_context().add_class("fan_slider")
            scale.connect("button-release-event", self.apply_scales)
            scale.connect("value_changed", self.update_preview)
            self.scales[idx] = scale
            scale_grid.attach(button, 0, idx, 1, 1)
            scale_grid.attach(scale, 1, idx, 3, 1)
        grid.attach(scale_grid, 0, 0, 3, 1)

        columns = 3 if self._screen.vertical_mode else 2
        data_misc = self._screen.apiclient.send_request(
            "server/database/item?namespace=mainsail&key=miscellaneous.entries")
        if data_misc:
            presets_data = data_misc['result']['value'][next(iter(data_misc["result"]["value"]))]['presets']
            if presets_data:
                self.presets.update(self.parse_presets(presets_data))
        for i, key in enumerate(self.presets):
            logging.info(f'Adding preset: {key}')
            preview = Gtk.DrawingArea(width_request=self.da_size, height_request=self.da_size)
            preview.connect("draw", self.on_draw, self.presets[key])
            button = self._gtk.Button()
            button.set_image(preview)
            button.connect("clicked", self.apply_preset, self.presets[key])
            self.preset_list.attach(button, i % columns, int(i / columns) + 1, 1, 1)

        scroll = self._gtk.ScrolledWindow()
        scroll.add(self.preset_list)
        preview_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        preview_box.add(self.preview_label)
        preview_box.add(self.preview)
        preview_box.set_homogeneous(True)
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.add(preview_box)
        box.add(scroll)
        if self._screen.vertical_mode:
            grid.attach(box, 0, 1, 3, 1)
        else:
            grid.attach(box, 3, 0, 2, 1)
        return grid

    def on_draw(self, da, ctx, color=None):
        if color is None:
            color = self.color_data
        ctx.set_source_rgb(*self.rgbw_to_rgb(color))
        # Set the size of the rectangle
        width = height = da.get_allocated_width() * .9
        x = da.get_allocated_width() * .05
        # Set the radius of the corners
        radius = width / 2 * 0.2
        ctx.arc(x + radius, radius, radius, pi, 3 * pi / 2)
        ctx.arc(x + width - radius, radius, radius, 3 * pi / 2, 0)
        ctx.arc(x + width - radius, height - radius, radius, 0, pi / 2)
        ctx.arc(x + radius, height - radius, radius, pi / 2, pi)
        ctx.close_path()
        ctx.fill()

    def update_preview(self, args):
        self.update_color_data()
        self.preview.queue_draw()
        self.preview_label.set_label(self.rgb_to_hex(self.rgbw_to_rgb(self.color_data)))

    def process_update(self, action, data):
        if action != 'notify_status_update':
            return
        if self.current_led in data and "color_data" in data[self.current_led]:
            self.update_scales(data[self.current_led]["color_data"][0])
            self.preview.queue_draw()

    def update_scales(self, color_data):
        for idx in self.scales:
            self.scales[idx].set_value(int(color_data[idx] * 255))
        self.color_data = color_data

    def update_color_data(self):
        for idx in self.scales:
            self.color_data[idx] = self.scales[idx].get_value() / 255

    def apply_preset(self, widget, color_data):
        self.update_scales(color_data)
        self.apply_scales()

    def apply_scales(self, *args):
        self.update_color_data()
        self.set_led_color(self.color_data)

    def set_led_color(self, color_data):
        name = self.current_led.split()[1] if len(self.current_led.split()) > 1 else self.current_led
        self._screen._send_action(None, "printer.gcode.script",
                                  {"script": KlippyGcodes.set_led_color(name, color_data)})

    @staticmethod
    def parse_presets(presets_data) -> {}:
        parsed = {}
        for i, preset in enumerate(presets_data.values()):
            name = i if preset["name"] == '' else preset["name"].lower()
            parsed[name] = []
            for color in ["red", "green", "blue", "white"]:
                if color not in preset or preset[color] is None:
                    parsed[name].append(0)
                    continue
                parsed[name].append(preset[color] / 255)
        return parsed

    @staticmethod
    def rgb_to_hex(color):
        hex_color = '#'
        for value in color:
            int_value = round(value * 255)
            hex_color += hex(int_value)[2:].zfill(2)
        return hex_color.upper()

    @staticmethod
    def rgbw_to_rgb(color):
        # The idea here is to use the white channel as a saturation control
        # The white channel 'washes' the color
        return (
            [color[3] for i in range(3)]  # Special case of only white channel
            if color[0] == 0 and color[1] == 0 and color[2] == 0
            else [color[i] + (1 - color[i]) * color[3] / 3 for i in range(3)]
        )
