import math
import webbrowser
from enum import auto, IntEnum

import gi

from uberwriter.helpers import get_builder
from uberwriter.preview_renderer import PreviewRenderer
from uberwriter.settings import Settings

gi.require_version('WebKit2', '4.0')
from gi.repository import WebKit2, GLib

from uberwriter.preview_converter import PreviewConverter
from uberwriter.web_view import WebView


class Step(IntEnum):
    CONVERT_HTML = auto()
    LOAD_WEBVIEW = auto()
    RENDER = auto()


class PreviewHandler:
    """Handles showing/hiding the preview, and allows the user to toggle between modes.

    The rendering itself is handled by `PreviewRendered`. This class handles conversion/loading and
    connects it all together (including synchronization, ie. text changes, scroll)."""

    def __init__(self, window, content, editor, text_view):
        self.text_view = text_view

        self.web_view = None
        self.web_view_pending_html = None

        builder = get_builder("Preview")
        preview = builder.get_object("preview")
        mode_button = builder.get_object("preview_mode_button")
        self.mode_revealer = builder.get_object("preview_mode_revealer")

        self.preview_converter = PreviewConverter()
        self.preview_renderer = PreviewRenderer(
            window, content, editor, text_view, preview, self.mode_revealer, mode_button)

        window.connect("style-updated", self.reload)

        self.text_changed_handler_id = None

        self.settings = Settings.new()
        self.web_scroll_handler_id = None
        self.text_scroll_handler_id = None

        self.loading = False
        self.shown = False

    def show(self):
        self.__show()

    def __show(self, html=None, step=Step.CONVERT_HTML):
        if step == Step.CONVERT_HTML:
            # First step: convert text to HTML.
            buf = self.text_view.get_buffer()
            self.preview_converter.convert(
                buf.get_text(buf.get_start_iter(), buf.get_end_iter(), False),
                self.__show, Step.LOAD_WEBVIEW)

        elif step == Step.LOAD_WEBVIEW:
            # Second step: load HTML.
            self.loading = True

            if not self.web_view:
                self.web_view = WebView()
                self.web_view.get_settings().set_allow_universal_access_from_file_urls(True)

                # Show preview once the load is finished
                self.web_view.connect("load-changed", self.on_load_changed)

                # All links will be opened in default browser, but local files are opened in apps.
                self.web_view.connect("decide-policy", self.on_click_link)

            if self.web_view.is_loading():
                self.web_view_pending_html = html
            else:
                self.web_view.load_html(html, "file://localhost/")

        elif step == Step.RENDER:
            # Last step: show the preview. This is a one-time step.
            if self.shown:
                return
            self.shown = True

            self.text_changed_handler_id = \
                self.text_view.get_buffer().connect("changed", self.__show)

            GLib.idle_add(self.web_view.set_scroll_scale, self.text_view.get_scroll_scale())

            self.preview_renderer.show(self.web_view)

            if self.settings.get_boolean("sync-scroll"):
                self.web_scroll_handler_id = \
                    self.web_view.connect("scroll-scale-changed", self.on_web_view_scrolled)
                self.text_scroll_handler_id = \
                    self.text_view.connect("scroll-scale-changed", self.on_text_view_scrolled)

    def reload(self, *_widget, reshow=False):
        if self.shown:
            if reshow:
                self.hide()
            self.show()

    def hide(self):
        if self.shown:
            self.shown = False

            self.text_view.get_buffer().disconnect(self.text_changed_handler_id)

            GLib.idle_add(self.text_view.set_scroll_scale, self.web_view.get_scroll_scale())

            self.preview_renderer.hide(self.web_view)

            if self.text_scroll_handler_id:
                self.text_view.disconnect(self.text_scroll_handler_id)
                self.text_scroll_handler_id = None
            if self.web_scroll_handler_id:
                self.web_view.disconnect(self.web_scroll_handler_id)
                self.web_scroll_handler_id = None

        if self.loading:
            self.loading = False

            self.web_view.destroy()
            self.web_view = None

    def update_preview_mode(self):
        self.preview_renderer.update_mode(self.web_view)

    def get_top_bottom_bar_revealers(self):
        if self.shown and not self.preview_renderer.window:
            return [self.mode_revealer]
        else:
            return []

    def on_load_changed(self, _web_view, event):
        if event == WebKit2.LoadEvent.FINISHED:
            self.loading = False
            if self.web_view_pending_html:
                self.__show(html=self.web_view_pending_html, step=Step.LOAD_WEBVIEW)
                self.web_view_pending_html = None
            else:
                self.__show(step=Step.RENDER)

    def on_text_view_scrolled(self, _text_view, scale):
        if self.shown and not math.isclose(scale, self.web_view.get_scroll_scale(), rel_tol=1e-5):
            self.web_view.set_scroll_scale(scale)

    def on_web_view_scrolled(self, _web_view, scale):
        if self.shown and self.text_view.get_mapped() and \
                not math.isclose(scale, self.text_view.get_scroll_scale(), rel_tol=1e-5):
            self.text_view.set_scroll_scale(scale)

    @staticmethod
    def on_click_link(web_view, decision, _decision_type):
        if web_view.get_uri().startswith(("http://", "https://", "www.")):
            webbrowser.open(web_view.get_uri())
            decision.ignore()
            return True
