from gi.repository import GObject, GLib, Gtk

import logging
from typing import Any, Callable, Optional

from iotas.note import Note
from iotas.pdf_exporter import PdfExporter


class WebKitPdfExporter(PdfExporter):

    __gsignals__ = {
        "finished": (GObject.SignalFlags.RUN_FIRST, None, ()),
        # Reason
        "failed": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
    }

    # webkit type hinting avoided to allow for module lazy loading
    def __init__(self, webkit, keep_webkit_process: bool) -> None:
        super().__init__()
        self.__render_view = webkit
        self.__render_view.connect("loaded", lambda _o: self.__on_web_view_loaded())
        self.__keep_webkit_process = keep_webkit_process
        self.__finished_callback = None
        self.__error_callback = None
        self.__active = False
        self.__in_error = False

    def set_callbacks(self, finished_callback: Callable, error_callback: Callable) -> None:
        """Set functions to be called upon export result.

        :param Callable finished_callback: Finished callback
        :param Callable error_callback: Error callback
        """
        self.__finished_callback = finished_callback
        self.__error_callback = error_callback

    def export(self, note: Note, location: str) -> None:
        """Export PDF of note.

        :param Note note: Note to export
        :param str location: Destination location
        """
        self.__note = note
        self.__location = location
        self.__active = True
        self.__in_error = False

        # Part of the effort to delay all WebKit initialisation
        if "WebKit" not in globals():
            import gi

            gi.require_version("WebKit", "6.0")
            global WebKit
            from gi.repository import WebKit

        self.__render_view.render_retaining_scroll(self.__note, "pdf")

    def __on_web_view_loaded(self) -> None:
        if not self.__active:
            return

        printer_name = self.__get_printer_name()
        if printer_name is None:
            self.__in_error = True
            # Theoretically rare, avoiding translation for now
            self.__error_callback("Failed to locate virtual printer for export to PDF")
            return

        settings = Gtk.PrintSettings()
        settings.set(Gtk.PRINT_SETTINGS_PRINTER, printer_name)
        settings.set(Gtk.PRINT_SETTINGS_OUTPUT_FILE_FORMAT, "pdf")
        settings.set(Gtk.PRINT_SETTINGS_OUTPUT_URI, f"file://{self.__location}")

        page_setup = Gtk.PageSetup()
        page_setup.set_left_margin(54, Gtk.Unit.POINTS)
        page_setup.set_right_margin(54, Gtk.Unit.POINTS)

        operation = WebKit.PrintOperation.new(self.__render_view)
        operation.set_print_settings(settings)
        operation.set_page_setup(page_setup)

        def finished(_print_operation):
            logging.info(f"Exported pdf to {self.__location}")
            if not self.__keep_webkit_process:
                logging.info("Terminating WebKit process as holding disabled in preference")
                self.__render_view.terminate_web_process()
            if not self.__in_error:
                self.__finished_callback()
            self.__active = False

        def failed(_print_operation, error: GLib.Error):
            logging.warning(f"Failed to export pdf to {self.__location}: %s", error.message)
            if not self.__keep_webkit_process:
                logging.info("Terminating WebKit process as holding disabled in preference")
                self.__render_view.terminate_web_process()
            self.__in_error = True
            self.__error_callback(error.message)

        operation.connect("finished", finished)
        operation.connect("failed", failed)
        operation.print_()

    def __get_printer_name(self) -> Optional[str]:
        default_language = Gtk.get_default_language().to_string()
        if default_language.startswith("en"):
            return "Print to File"
        else:
            virtual_printers = []

            def check_printer(printer: Gtk.Printer, _data: Any) -> None:
                if printer.is_virtual():
                    virtual_printers.append(printer.get_name())

            Gtk.enumerate_printers(check_printer, data=None, wait=True)
            if len(virtual_printers) == 1:
                return virtual_printers[0]
            elif not virtual_printers:
                logging.warning("No virtual printers found")
                return None
            else:
                logging.warning("See more than one virtual printer, can't identify Print to File")
                return None
