from gi.repository import Adw, Gio, GLib, GObject, Gtk, GtkSource, Pango

from typing import Callable, Optional

from iotas.formatter import Formatter, LinkStateInfo
from iotas.link_dialog import LinkDialog
from iotas.table_dialog import TableDialog


@Gtk.Template(resource_path="/org/gnome/World/Iotas/ui/formatting_header_bar.ui")
class FormattingHeaderBar(Adw.BreakpointBin):
    """Editor formatting header bar."""

    __gtype_name__ = "FormattingHeaderBar"
    __gsignals__ = {
        "height-change": (GObject.SignalFlags.RUN_FIRST, None, ()),
    }

    _breakpoint = Gtk.Template.Child()
    _base_box = Gtk.Template.Child()
    _parent_box = Gtk.Template.Child()
    _extend_box = Gtk.Template.Child()
    _heading_menu = Gtk.Template.Child()
    _menu_button = Gtk.Template.Child()
    _header_bar = Gtk.Template.Child()
    _link_button = Gtk.Template.Child()
    _table_button = Gtk.Template.Child()

    def __init__(self) -> None:
        super().__init__()
        self._breakpoint.connect("apply", lambda _o: self.__apply_breakpoint())
        self._breakpoint.connect("unapply", lambda _o: self.__unapply_breakpoint())
        self.__formatter = Formatter()
        self.__link_dialog = None
        self.__table_dialog = None
        self.__active = False

    def setup(self, sourceview: GtkSource.View) -> None:
        """Later startup initialisation.

        :param GtkSource.View sourceview: Sourceview
        """
        self.__setup_actions()
        self.__sourceview = sourceview

    def prepare(self) -> None:
        """Initialise state entering editor."""
        self.__formatter.buffer = self.__sourceview.get_buffer()

        def callback():
            if self.get_width() == 0:
                GLib.idle_add(callback)
                return

            if self.get_width() < 500:
                self.__make_shorter()
            else:
                self.__make_taller()

        GLib.idle_add(callback)

    def focus(self) -> None:
        """Grab focus."""
        self._heading_menu.grab_focus()

    @GObject.Property(type=bool, default=False)
    def active(self) -> bool:
        return self.__active

    @active.setter
    def active(self, value: bool) -> None:
        self.__active = value
        self.__enable_actions(value)

    def __on_table_create(self, _obj: TableDialog, width: int, height: int) -> None:
        view_width = self.__sourceview.get_width()
        if self.__sourceview.line_length > 0:
            view_width = min(view_width, self.__sourceview.line_length)
        if self.__get_text_direction() == Gtk.TextDirection.RTL:
            pango_context = self.__sourceview.get_rtl_context()
        else:
            pango_context = self.__sourceview.get_ltr_context()
        metrics = pango_context.get_metrics()
        character_width = metrics.get_approximate_char_width()
        self.__formatter.table(width, height, view_width, Pango.units_to_double(character_width))

        # Can't directly call grab_focus from idle_add otherwise its returning of True will retain,
        # and repeat, the event
        def focus_sourceview():
            self.__sourceview.grab_focus()

        GLib.idle_add(focus_sourceview)

    def __on_link_apply(self, _obj: LinkDialog, info: LinkStateInfo) -> None:
        self.__formatter.link(info)
        self.__sourceview.grab_focus()

    def __setup_actions(self) -> None:
        action_group = Gio.SimpleActionGroup.new()
        app = Gio.Application.get_default()

        def wrap(func: Callable, param: Optional[int] = None) -> None:
            args = (param,) if param is not None else ()
            func(*args)
            self.__sourceview.grab_focus()

        action = Gio.SimpleAction.new("bold")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.bold))
        action_group.add_action(action)
        app.set_accels_for_action("format.bold", ["<Control>b"])

        action = Gio.SimpleAction.new("italic")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.italic))
        action_group.add_action(action)
        app.set_accels_for_action("format.italic", ["<Control>i"])

        action = Gio.SimpleAction.new("strikethrough")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.strikethrough))
        action_group.add_action(action)
        app.set_accels_for_action("format.strikethrough", ["<Control><Shift>x"])

        action = Gio.SimpleAction.new("quote")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.quote))
        action_group.add_action(action)
        app.set_accels_for_action("format.quote", ["<Control><Shift>q"])

        action = Gio.SimpleAction.new("heading-1")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.heading, 1))
        action_group.add_action(action)
        app.set_accels_for_action("format.heading-1", ["<Control>1"])

        action = Gio.SimpleAction.new("heading-2")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.heading, 2))
        action_group.add_action(action)
        app.set_accels_for_action("format.heading-2", ["<Control>2"])

        action = Gio.SimpleAction.new("heading-3")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.heading, 3))
        action_group.add_action(action)
        app.set_accels_for_action("format.heading-3", ["<Control>3"])

        action = Gio.SimpleAction.new("heading-4")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.heading, 4))
        action_group.add_action(action)
        app.set_accels_for_action("format.heading-4", ["<Control>4"])

        action = Gio.SimpleAction.new("heading-5")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.heading, 5))
        action_group.add_action(action)
        app.set_accels_for_action("format.heading-5", ["<Control>5"])

        action = Gio.SimpleAction.new("heading-6")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.heading, 6))
        action_group.add_action(action)
        app.set_accels_for_action("format.heading-6", ["<Control>6"])

        action = Gio.SimpleAction.new("heading-remove")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.heading, 0))
        action_group.add_action(action)
        app.set_accels_for_action("format.heading-remove", ["<Control>7"])

        action = Gio.SimpleAction.new("unordered-list")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.unordered_list))
        action_group.add_action(action)
        app.set_accels_for_action("format.unordered-list", ["<Control>u"])

        action = Gio.SimpleAction.new("ordered-list")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.ordered_list))
        action_group.add_action(action)
        app.set_accels_for_action("format.ordered-list", ["<Control>o"])

        action = Gio.SimpleAction.new("checkbox")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.checkbox))
        action_group.add_action(action)
        app.set_accels_for_action("format.checkbox", ["<Alt>c"])

        action = Gio.SimpleAction.new("toggle-checkbox")
        action.connect("activate", lambda _a, _p: self.__formatter.toggle_checkbox())
        action_group.add_action(action)
        app.set_accels_for_action("format.toggle-checkbox", ["<Control>t"])

        action = Gio.SimpleAction.new("link")
        action.connect("activate", lambda _a, _p: self.__link())
        action_group.add_action(action)
        app.set_accels_for_action("format.link", ["<Control>k"])

        action = Gio.SimpleAction.new("code")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.code))
        action_group.add_action(action)
        app.set_accels_for_action("format.code", ["<Control><Shift>c"])

        action = Gio.SimpleAction.new("horizontal-rule")
        action.connect("activate", lambda _a, _p: wrap(self.__formatter.horizontal_rule))
        action_group.add_action(action)
        app.set_accels_for_action("format.horizontal-rule", ["<Control><Shift>r"])

        action = Gio.SimpleAction.new("table")
        action.connect("activate", lambda _a, _p: self.__table())
        action_group.add_action(action)
        app.set_accels_for_action("format.table", ["<Control><Shift>t"])

        app.get_active_window().insert_action_group("format", action_group)
        self.__action_group = action_group

    def __enable_actions(self, enabled: bool) -> None:
        actions = self.__action_group.list_actions()
        for action in actions:
            self.__action_group.lookup_action(action).set_enabled(enabled)

    def __apply_breakpoint(self) -> None:
        self.__make_shorter()
        self.emit("height-change")

    def __make_shorter(self) -> None:
        self._extend_box.set_visible(False)
        self._menu_button.set_visible(True)
        self._base_box.set_spacing(4)
        self._parent_box.set_spacing(4)
        self._header_bar.add_css_class("formatting-small")
        self.set_property("height-request", 24)

    def __unapply_breakpoint(self) -> None:
        self.__make_taller()
        self.emit("height-change")

    def __make_taller(self) -> None:
        self._extend_box.set_visible(True)
        self._menu_button.set_visible(False)
        self._base_box.set_spacing(6)
        self._parent_box.set_spacing(6)
        self._header_bar.remove_css_class("formatting-small")
        self.set_property("height-request", 47)

    def __link(self) -> None:
        """Show link dialog."""
        info = self.__formatter.get_link_state()
        if not self.__link_dialog:
            self.__link_dialog = LinkDialog()
            self.__link_dialog.connect("apply", self.__on_link_apply)
        self.__link_dialog.show(info, self.get_root())

    def __table(self) -> None:
        """Show table dialog."""
        if not self.__table_dialog:
            self.__table_dialog = TableDialog()
            self.__table_dialog.connect("create", self.__on_table_create)
        self.__table_dialog.show(self.get_root())

    def __get_text_direction(self) -> Gtk.TextDirection:
        widget_direction = self.__sourceview.get_direction()
        if widget_direction:
            direction = widget_direction
        else:
            direction = Gtk.Widget.get_default_direction()
        return direction
