import logging

import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Pango

from ks_includes.KlippyGcodes import KlippyGcodes
from ks_includes.screen_panel import ScreenPanel


class Panel(ScreenPanel):
    widgets = {}
    distances = [".01", ".05", ".1", ".5", "1", "5"]
    distance = distances[-2]

    def __init__(self, screen, title):
        title = title or _("Nozzle Offset")
        super().__init__(screen, title)

        self.start_z_calibrate = False
        self.z_hop_speed = 15.0
        self.z_hop = 5.0
        self.showing_input_box = False

        self.offset_data = [
            (_("Z Offset"), "z_offset_val", "0"),
            (_("X Offset"), "x_offset_val", "0"),
            (_("Y Offset"), "y_offset_val", "0"),
        ]

        for label_text, offset_key, value_text in self.offset_data:
            self.widgets[offset_key[:8]] = Gtk.Label(label=label_text)
            self.widgets[offset_key] = Gtk.Label(label=value_text)
            event_box = Gtk.EventBox()
            event_box.add(self.widgets[offset_key])
            event_box.connect("button-release-event", self.change_offset, label_text, self.widgets[offset_key])
            setattr(self, f"{offset_key[0]}_event_box", event_box)

        pos = Gtk.Grid(row_homogeneous=True, column_homogeneous=True)
        pos.attach(self.widgets["z_offset"], 0, 1, 2, 1)
        pos.attach(self.z_event_box, 0, 2, 2, 1)
        pos.attach(self.widgets["x_offset"], 0, 3, 1, 1)
        pos.attach(self.x_event_box, 0, 4, 1, 1)
        pos.attach(self.widgets["y_offset"], 1, 3, 1, 1)
        pos.attach(self.y_event_box, 1, 4, 1, 1)
        for label in pos.get_children():
            if isinstance(label, Gtk.Label):
                label.set_ellipsize(Pango.EllipsizeMode.END)
        self.buttons = {
            "z+": self._gtk.Button("z-farther", "Z+", "color4"),
            "z-": self._gtk.Button("z-closer", "Z-", "color1"),
            "start_z_offset": self._gtk.Button("offset_z", _("Z offset Calibrate"), "color3"),
            "start_xy_offset": self._gtk.Button("resume", _("XY offset Calibrate"), "color3"),
            "complete": self._gtk.Button("complete", _("Save"), "color3"),
            "cancel": self._gtk.Button("cancel", _("Cancel"), "color2"),
        }
        self.buttons["z+"].connect("clicked", self.move, "z", "+")
        self.buttons["z-"].connect("clicked", self.move, "z", "-")
        self.buttons["complete"].connect("clicked", self.accept)
        script = {"script": "ABORT"}
        self.buttons["cancel"].connect("clicked", self.cancel)

        self.popover = Gtk.Popover(position=Gtk.PositionType.BOTTOM)
        script = {"script": "_NOZZLE_Z_OFFSET_CALIBRATE"}
        self.buttons["start_z_offset"].connect("clicked", self.nozzle_z_offset)
        script = {"script": "_NOZZLE_XY_OFFSET_CALIBRATE"}
        self.buttons["start_xy_offset"].connect("clicked", self.nozzle_xy_offset)

        distgrid = Gtk.Grid()
        for j, i in enumerate(self.distances):
            self.widgets[i] = self._gtk.Button(label=i)
            self.widgets[i].set_direction(Gtk.TextDirection.LTR)
            self.widgets[i].connect("clicked", self.change_distance, i)
            ctx = self.widgets[i].get_style_context()
            ctx.add_class("horizontal_togglebuttons")
            if i == self.distance:
                ctx.add_class("horizontal_togglebuttons_active")
            distgrid.attach(self.widgets[i], j, 0, 1, 1)

        self.widgets["move_dist"] = Gtk.Label(_("Move Distance (mm)"))
        distances = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        distances.pack_start(self.widgets["move_dist"], True, True, 0)
        distances.pack_start(distgrid, True, True, 0)

        self.grid = Gtk.Grid(column_homogeneous=True)
        if self._screen.vertical_mode:
            self.grid.attach(self.buttons["z+"], 0, 1, 1, 1)
            self.grid.attach(self.buttons["z-"], 0, 0, 1, 1)
            self.grid.attach(self.buttons["start_z_offset"], 1, 0, 1, 1)
            self.grid.attach(pos, 1, 1, 2, 1)
            self.grid.attach(self.buttons["start_xy_offset"], 2, 0, 1, 1)
            self.grid.attach(self.buttons["complete"], 3, 0, 1, 1)
            self.grid.attach(self.buttons["cancel"], 3, 1, 1, 1)
            self.grid.attach(distances, 0, 2, 4, 1)
        else:
            self.grid.attach(self.buttons["z+"], 0, 2, 1, 1)
            self.grid.attach(self.buttons["z-"], 0, 1, 1, 1)
            self.grid.attach(self.buttons["start_z_offset"], 0, 0, 2, 1)
            self.grid.attach(self.buttons["start_xy_offset"], 2, 0, 2, 1)
            self.grid.attach(pos, 1, 1, 2, 2)
            self.grid.attach(self.buttons["complete"], 3, 1, 1, 1)
            self.grid.attach(self.buttons["cancel"], 3, 2, 1, 1)
            self.grid.attach(distances, 0, 3, 4, 1)
        self.content.add(self.grid)

    def nozzle_z_offset(self, widget):
        text = (
            _("Start testing the Z offset value of the second nozzle?\n")
            + "\n\n"
            + _("Please ensure that the Probe Calibrate has been performed")
        )
        label = Gtk.Label(wrap=True, vexpand=True)
        label.set_markup(text)
        buttons = [
            {"name": _("Accept"), "response": Gtk.ResponseType.OK, "style": "dialog-info"},
            {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": "dialog-error"},
        ]
        self._gtk.Dialog(_("Calibrate Nozzle Z Offset"), buttons, label, self.confirm_nozzle_z_offset)

    def confirm_nozzle_z_offset(self, dialog, response_id):
        self._gtk.remove_dialog(dialog)
        if response_id == Gtk.ResponseType.OK:
            self.start_z_calibrate = True
            self.buttons_calibration_start()
            self._screen._send_action(
                self.buttons["start_z_offset"], "printer.gcode.script", {"script": "_NOZZLE_Z_OFFSET_CALIBRATE"}
            )

    def nozzle_xy_offset(self, widget):
        text = (
            _("This operation is about to print the model")
            + "\n\n"
            + _("Please load two different colored PLA filaments!")
        )

        label = Gtk.Label(wrap=True, vexpand=True)
        label.set_markup(text)
        buttons = [
            {"name": _("Accept"), "response": Gtk.ResponseType.OK, "style": "dialog-info"},
            {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": "dialog-error"},
        ]
        self._gtk.Dialog(_("Calibrate Nozzle XY Offset"), buttons, label, self.confirm_nozzle_xy_offset)

    def confirm_nozzle_xy_offset(self, dialog, response_id):
        self._gtk.remove_dialog(dialog)
        if response_id == Gtk.ResponseType.OK:
            self._screen.offset_fine_tune_mode = True
            self.buttons_calibration_start()
            self._screen._send_action(
                self.buttons["start_z_offset"], "printer.gcode.script", {"script": "_NOZZLE_XY_OFFSET_CALIBRATE"}
            )

    def change_offset(self, widget, event, title_label, offset_label):
        self._create_input_box(title_label, offset_label)

    def _create_input_box(self, title_label, offset_label):
        current_val = offset_label.get_text()
        title_label += "        " + _("Current value:") + f"{current_val}"
        for child in self.content.get_children():
            self.content.remove(child)
        lbl = Gtk.Label(label=title_label, halign=Gtk.Align.START, hexpand=False)
        self.labels["entry"] = Gtk.Entry(hexpand=True)
        self.labels["entry"].connect("focus-in-event", self._screen.show_keyboard)
        save = self._gtk.Button("complete", _("Save"), "color3")
        save.set_hexpand(False)
        save.connect("clicked", self.store_value, offset_label)
        box = Gtk.Box()
        box.pack_start(self.labels["entry"], True, True, 5)
        box.pack_start(save, False, False, 5)
        self.labels["input_box"] = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL, spacing=5, hexpand=True, vexpand=True, valign=Gtk.Align.CENTER
        )
        self.labels["input_box"].pack_start(lbl, True, True, 5)
        self.labels["input_box"].pack_start(box, True, True, 5)
        self.content.add(self.labels["input_box"])
        self.labels["entry"].grab_focus_without_selecting()
        self.showing_input_box = True

    def hide_input_box(self):
        self._screen.remove_keyboard()
        for child in self.content.get_children():
            self.content.remove(child)
        self.content.add(self.grid)
        self.content.show()
        self.showing_input_box = False

    def store_value(self, widget, offset_label):
        val_text = self.labels["entry"].get_text()
        try:
            val = round(float(val_text), 3)
            name = None
            for label_text, offset_key, _ in self.offset_data:
                if offset_label == self.widgets[offset_key]:
                    name = offset_key
                    break
            if name is not None:
                name = "nozzle_" + name
                self.set_nozzle_offset(name, val)
            if self.showing_input_box:
                self.hide_input_box()
        except ValueError:
            self._screen.show_popup_message(_("Please enter a valid number"))

    def set_nozzle_offset(self, option, value):
        script = KlippyGcodes.set_save_variables(option, value)
        self._screen._send_action(None, "printer.gcode.script", {"script": script})
        logging.info(f"Set {option}:{value}")

    def back(self):
        if self.showing_input_box:
            self.hide_input_box()
            return True
        return False

    def _add_button(self, label, method, pobox):
        popover_button = self._gtk.Button(label=label)
        pobox.pack_start(popover_button, True, True, 5)

    def on_popover_clicked(self, widget):
        self.popover.set_relative_to(widget)
        self.popover.show_all()

    def activate(self):
        is_sensitive = self.buttons["start_z_offset"].get_sensitive()
        if is_sensitive:
            self.buttons_not_z_calibration()
        else:
            self.buttons_calibration_start()

    def deactivate(self):
        if self.start_z_calibrate:
            self.start_z_calibrate = False

    def process_update(self, action, data):

        if action == "notify_gcode_response":
            if self.start_z_calibrate and "extruder1" in data:
                self.buttons_z_calibration()
            elif "action:resumed" in data:
                return
        if action != "notify_status_update":
            return
        if "save_variables" in data and "variables" in data["save_variables"]:
            variables = data["save_variables"]["variables"]
            nozzle_offsets = ["nozzle_z_offset_val", "nozzle_x_offset_val", "nozzle_y_offset_val"]

            for offset in nozzle_offsets:
                if offset in variables:
                    self.widgets[f'{offset.split("_")[1]}_offset_val'].set_text(f"{variables[offset]}")

        if "gcode_move" in data or "toolhead" in data and "homed_axes" in data["toolhead"]:
            homed_axes = self._printer.get_stat("toolhead", "homed_axes")

            if "z" in homed_axes:
                self.pos_z = round(data["gcode_move"]["gcode_position"][2], 3)
                if self.start_z_calibrate:
                    self.widgets["z_offset_val"].set_text(f"{self.pos_z}")

    def change_distance(self, widget, distance):
        logging.info(f"### Distance {distance}")
        self.widgets[f"{self.distance}"].get_style_context().remove_class("horizontal_togglebuttons_active")
        self.widgets[f"{distance}"].get_style_context().add_class("horizontal_togglebuttons_active")
        self.distance = distance

    def move(self, widget, axis, direction):
        dist = f"{direction}{self.distance}"
        script = f"{KlippyGcodes.MOVE_RELATIVE}\nG0 {axis}{dist} F300"
        self._screen._send_action(widget, "printer.gcode.script", {"script": script})
        if self._printer.get_stat("gcode_move", "absolute_coordinates"):
            self._screen._ws.klippy.gcode_script("G90")

    def accept(self, widget):
        logging.info("Accepting nozzle Z offset")
        script = f"SET_GCODE_OFFSET Z={self.pos_z}"
        self._screen._send_action(None, "printer.gcode.script", {"script": script})
        script = f"G91\n G0 Z5 F6000"
        self._screen._send_action(None, "printer.gcode.script", {"script": script})

        self.set_nozzle_offset("nozzle_z_offset_val", self.pos_z)
        self.buttons_not_z_calibration()
        self.start_z_calibrate = False

    def cancel(self, widget):
        self.start_z_calibrate = False
        self.buttons_not_z_calibration()
        variables = self._printer.get_stat("save_variables", "variables")
        if "nozzle_z_offset_val" in variables:
            self.widgets["z_offset_val"].set_text(f"{variables['nozzle_z_offset_val']}")

    def buttons_calibration_start(self):
        self.buttons["start_z_offset"].set_sensitive(False)
        self.buttons["start_xy_offset"].set_sensitive(False)

        self.buttons["z+"].set_sensitive(False)
        self.buttons["z-"].set_sensitive(False)
        self.buttons["complete"].set_sensitive(False)
        self.buttons["cancel"].set_sensitive(False)

    def buttons_z_calibration(self):
        self.buttons["start_z_offset"].set_sensitive(False)
        self.buttons["start_xy_offset"].set_sensitive(False)
        self.buttons["z+"].set_sensitive(True)
        self.buttons["z-"].set_sensitive(True)
        self.buttons["complete"].set_sensitive(True)
        self.buttons["cancel"].set_sensitive(True)

    def buttons_not_z_calibration(self):
        self.buttons["start_z_offset"].set_sensitive(True)
        self.buttons["start_xy_offset"].set_sensitive(True)
        self.buttons["z+"].set_sensitive(False)
        self.buttons["z-"].set_sensitive(False)
        self.buttons["complete"].set_sensitive(False)
        self.buttons["cancel"].set_sensitive(False)
