"provide postprocessing rotate controls for the scan dialog"

import gi
from tesseract import languages, get_tesseract_codes
from comboboxtext import ComboBoxText
from i18n import _

gi.require_version("Gtk", "3.0")
from gi.repository import GObject, Gtk  # pylint: disable=wrong-import-position

SIDE = [
    ["both", _("Both sides"), _("Both sides.")],
    ["facing", _("Facing side"), _("Facing side.")],
    ["reverse", _("Reverse side"), _("Reverse side.")],
]
ROTATE = [
    [90, _("90°"), _("Rotate image 90 degrees clockwise.")],
    [180, _("180°"), _("Rotate image 180 degrees clockwise.")],
    [270, _("270°"), _("Rotate image 90 degrees anticlockwise.")],
]


class RotateControlRow(Gtk.Box):
    "provide a row of postprocessing rotate controls for the scan dialog"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.cbutton = Gtk.CheckButton(label=_("Rotate"))
        self.cbutton.set_tooltip_text(_("Rotate image after scanning"))
        self.pack_start(self.cbutton, True, True, 0)
        self.side_cmbx = ComboBoxText(data=SIDE)
        self.side_cmbx.set_tooltip_text(_("Select side to rotate"))
        self.pack_start(self.side_cmbx, True, True, 0)
        self.angle_cmbx = ComboBoxText(data=ROTATE)
        self.angle_cmbx.set_tooltip_text(_("Select angle of rotation"))
        self.pack_start(self.angle_cmbx, True, True, 0)
        self.pack_end(Gtk.Label(label=_("Anticlockwise")), False, True, 0)


class RotateControls(Gtk.Box):
    "provide postprocessing rotate controls for the scan dialog"

    _recomputing = False
    _rotate_facing = 0

    @GObject.Property(
        type=int,
        nick="Rotate facing",
        blurb="Angle to rotate facing side",
    )
    def rotate_facing(self):
        "getter for rotate_facing attribute"
        return self._rotate_facing

    @rotate_facing.setter
    def rotate_facing(self, newval):
        if newval == self._rotate_facing:
            return
        self._rotate_facing = newval
        self._update_gui()

    _rotate_reverse = 0

    @GObject.Property(
        type=int,
        nick="Rotate reverse",
        blurb="Angle to rotate reverse side",
    )
    def rotate_reverse(self):
        "getter for rotate_reverse attribute"
        return self._rotate_reverse

    @rotate_reverse.setter
    def rotate_reverse(self, newval):
        if newval == self._rotate_reverse:
            return
        self._rotate_reverse = newval
        self._update_gui()

    _can_duplex = True

    @GObject.Property(
        type=bool,
        default=True,
        nick="Can duplex",
        blurb="Scanner capable of duplex scanning",
    )
    def can_duplex(self):
        "getter for can_duplex attribute"
        return self._can_duplex

    @can_duplex.setter
    def can_duplex(self, newval):
        if newval == self._can_duplex:
            return
        self._can_duplex = newval
        if newval:
            self._side1.side_cmbx.show()
            self._side2.side_cmbx.show()
        else:
            self._side1.side_cmbx.hide()
            self._side2.side_cmbx.hide()

    def __init__(self, *args, **kwargs):

        # have to do this manually, since Gtk.Box messes with the initialization
        rotate_facing = kwargs.pop("rotate_facing", kwargs.pop("rotate-facing", 0))
        rotate_reverse = kwargs.pop("rotate_reverse", kwargs.pop("rotate-reverse", 0))
        can_duplex = kwargs.pop("can_duplex", kwargs.pop("can-duplex", True))

        super().__init__(*args, **kwargs)
        self.set_orientation(orientation=Gtk.Orientation.VERTICAL)
        self._side1 = RotateControlRow()
        self.pack_start(self._side1, True, True, 0)
        self._side2 = RotateControlRow()
        self.pack_start(self._side2, False, False, 0)
        self._side1.cbutton.connect("toggled", self._toggled_rotate_callback)
        self._side2.cbutton.connect("toggled", self._update_attributes)
        self._side1.side_cmbx.connect("changed", self._toggled_rotate_side_callback)
        self._side2.side_cmbx.connect("changed", self._update_attributes)
        self._side1.angle_cmbx.connect("changed", self._update_attributes)
        self._side2.angle_cmbx.connect("changed", self._update_attributes)

        # have to do this manually, since Gtk.Box messes with the initialization
        self.rotate_facing = rotate_facing
        self.rotate_reverse = rotate_reverse
        self.can_duplex = can_duplex

        # In case it isn't set elsewhere
        self._side1.side_cmbx.set_active_index("both")
        self._side1.angle_cmbx.set_active_index(90)

        # Sync GUI with properties set during instantiation
        self._update_gui()

    def _toggled_rotate_callback(self, _widget):
        self._update_attributes(None)

    def _toggled_rotate_side_callback(self, _widget):
        self._update_attributes(None)

    def _update_side2_options(self):
        side1_cmbx_i = self._side1.side_cmbx.get_active()
        angle1_cmbx_i = self._side1.angle_cmbx.get_active()

        # Empty combobox
        while self._side2.side_cmbx.get_num_rows() > 0:
            self._side2.side_cmbx.remove(0)

        side2 = []
        for s in SIDE:
            if s[0] not in ["both", SIDE[side1_cmbx_i][0]]:
                side2.append(s)
        for s in side2:
            self._side2.side_cmbx.append_text(s[1])
        self._side2.side_cmbx.data = side2
        self._side2.side_cmbx.set_active(0)

        # Empty combobox
        while self._side2.angle_cmbx.get_num_rows() > 0:
            self._side2.angle_cmbx.remove(0)

        angle2 = []
        for a in ROTATE:
            if angle1_cmbx_i != -1 and a[0] != ROTATE[angle1_cmbx_i][0]:
                angle2.append(a)
                self._side2.angle_cmbx.append_text(a[1])
        self._side2.angle_cmbx.data = angle2
        self._side2.angle_cmbx.set_active(0)

    def _update_attributes(self, _widget):
        if self._recomputing:
            return
        self._recomputing = True

        rotate_facing = 0
        rotate_reverse = 0
        if self._side1.cbutton.get_active():
            side1 = self._side1.side_cmbx.get_active_index()
            angle1 = self._side1.angle_cmbx.get_active_index()
            if side1 == "both":
                rotate_facing = angle1
                rotate_reverse = rotate_facing
            elif side1 == "facing":
                rotate_facing = angle1
            else:
                rotate_reverse = angle1

            if self._side2.cbutton.get_active():
                side2 = self._side2.side_cmbx.get_active_index()
                angle2 = self._side2.angle_cmbx.get_active_index()
                if side2 == "facing":
                    rotate_facing = angle2
                else:
                    rotate_reverse = angle2

        self.rotate_facing = rotate_facing
        self.rotate_reverse = rotate_reverse
        self._recomputing = False

    def _update_gui(self):
        self._recomputing = True
        if self.rotate_facing == 0 and self.rotate_reverse == 0:
            self._side1.cbutton.set_active(False)
            self._side2.set_sensitive(False)
            self._side2.cbutton.set_active(False)
        else:
            self._side1.cbutton.set_active(True)
            if self.rotate_facing == self.rotate_reverse:
                self._side1.side_cmbx.set_active_index("both")
                self._side1.angle_cmbx.set_active_index(self.rotate_facing)
                self._side2.set_sensitive(False)
                self._side2.cbutton.set_active(False)
            elif self.rotate_facing != 0 and self.rotate_reverse != 0:
                self._side1.side_cmbx.set_active_index("facing")
                self._side1.angle_cmbx.set_active_index(self.rotate_facing)
                self._side2.set_sensitive(True)
                self._side2.cbutton.set_active(True)
                self._update_side2_options()
                self._side2.side_cmbx.set_active_index("reverse")
                self._side2.angle_cmbx.set_active_index(self.rotate_reverse)
            elif self.rotate_facing != 0:
                self._side1.side_cmbx.set_active_index("facing")
                self._side1.angle_cmbx.set_active_index(self.rotate_facing)
                self._side2.set_sensitive(True)
                self._side2.cbutton.set_active(False)
                self._update_side2_options()
            else:  # self.rotate_reverse != 0
                self._side1.side_cmbx.set_active_index("reverse")
                self._side1.angle_cmbx.set_active_index(self.rotate_reverse)
                self._side2.set_sensitive(True)
                self._side2.cbutton.set_active(False)
                self._update_side2_options()

        self._recomputing = False


class OCRControls(Gtk.Box):
    "Provides post-processing OCR options for the scan dialog."

    available_engines = GObject.Property(
        type=object,
        nick="OCR engines",
        blurb="List of available OCR engines",
    )
    _engine = None

    @GObject.Property(
        type=str,
        default=None,
        nick="OCR engine",
        blurb="Currently selected OCR engine",
    )
    def engine(self):
        "getter for engine attribute"
        return self._engine

    @engine.setter
    def engine(self, newval):
        self._engine = newval

    _language = None

    @GObject.Property(
        type=str,
        default=None,
        nick="OCR language",
        blurb="Currently selected OCR language",
    )
    def language(self):
        "getter for language attribute"
        return self._language

    @language.setter
    def language(self, newval):
        self._language = newval

    _active = False

    @GObject.Property(
        type=bool,
        default=False,
        nick="Active",
        blurb="Whether OCR will be automatically performed",
    )
    def active(self):
        "getter for active attribute"
        return self._active

    @active.setter
    def active(self, newval):
        self._active = newval
        if hasattr(self, "_active_button"):
            self._active_button.set_active(newval)

    _threshold = False

    @GObject.Property(
        type=bool,
        default=False,
        nick="Threshold",
        blurb="Whether to threshold before performing OCR",
    )
    def threshold(self):
        "getter for threshold attribute"
        return self._threshold

    @threshold.setter
    def threshold(self, newval):
        self._threshold = newval
        if hasattr(self, "_threshold_button"):
            self._threshold_button.set_active(newval)

    _threshold_value = 20.0

    @GObject.Property(
        type=float,
        default=20.0,
        nick="Threshold value",
        blurb="Pixels that differ from the paper colour by more than this percentage are rendered black",
    )
    def threshold_value(self):
        "getter for threshold_value attribute"
        return self._threshold_value

    @threshold_value.setter
    def threshold_value(self, newval):
        self._threshold_value = newval
        if hasattr(self, "_threshold_spin"):
            self._threshold_spin.set_value(newval)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_orientation(orientation=Gtk.Orientation.VERTICAL)
        hboxo = Gtk.Box()
        self.pack_start(hboxo, False, False, 0)
        self._active_button = Gtk.CheckButton(label=_("OCR scanned pages"))
        self._active_button.set_tooltip_text(_("OCR scanned pages"))

        tesseract = False
        for engine in self.available_engines:
            if engine[0] == "tesseract":
                tesseract = True

        if not tesseract:
            hboxo.set_sensitive(False)
            self._active_button.set_active(False)
        elif self.active:
            self._active_button.set_active(self.active)

        hboxo.pack_start(self._active_button, True, True, 0)
        hboxtl = None

        if tesseract:
            hboxtl = self._add_tess_languages()

            if not self._active_button.get_active():
                hboxtl.set_sensitive(False)

            self._active_button.connect("toggled", self.on_toggled_active, hboxtl)

        # Checkbox & SpinButton for threshold
        hboxt = Gtk.Box()
        self.pack_start(hboxt, False, True, 0)
        self._threshold_button = Gtk.CheckButton(label=_("Threshold before OCR"))
        self._threshold_button.set_tooltip_text(
            _(
                "Threshold the image before performing OCR. "
                "This only affects the image passed to the OCR engine, and not the image stored."
            )
        )
        self._threshold_button.set_active(self.threshold)
        hboxt.pack_start(self._threshold_button, False, True, 0)
        labelp = Gtk.Label(label="%")
        hboxt.pack_end(labelp, False, True, 0)
        self._threshold_spin = Gtk.SpinButton.new_with_range(0, 100, 1)
        self._threshold_spin.set_tooltip_text(
            _(
                "Pixels that differ from the paper colour by more than this "
                "percentage are rendered black. Higher values only keep "
                "stronger marks."
            )
        )
        self._threshold_spin.set_value(self.threshold_value)
        self._threshold_spin.set_sensitive(self.threshold)
        hboxt.pack_end(self._threshold_spin, False, True, 0)
        self._threshold_button.connect(
            "toggled", self.on_toggled_threshold, self._threshold_spin
        )
        self._threshold_spin.connect("value-changed", self.on_threshold_changed)

    def on_toggled_active(self, checkbox, hboxtl):
        "callback for OCR active checkbox"
        self.active = checkbox.get_active()
        hboxtl.set_sensitive(self.active)

    def on_toggled_threshold(self, checkbox, spinbutton):
        "callback for threshold checkbox"
        self.threshold = checkbox.get_active()
        spinbutton.set_sensitive(self.threshold)

    def on_threshold_changed(self, _widget, value):
        "callback for threshold value spinbutton"
        self.threshold_value = value

    def on_language_changed(self, widget):
        "callback for OCR language combobox"
        self.language = widget.get_active_index()

    def _add_tess_languages(self):
        hbox = Gtk.Box()
        self.pack_start(hbox, False, False, 0)
        label = Gtk.Label(label=_("Language to recognise"))
        hbox.pack_start(label, False, True, 0)

        # Tesseract language files
        tesslang = []
        tesscodes = get_tesseract_codes()
        langs = languages(tesscodes)
        for lang in sorted(tesscodes):
            tesslang.append([lang, langs[lang]])

        combobox = ComboBoxText(data=tesslang)
        combobox.set_active_index(self.language)
        if not combobox.get_active_index():
            combobox.set_active(0)
            self.language = combobox.get_active_index()
        combobox.connect("changed", self.on_language_changed)
        hbox.pack_end(combobox, False, True, 0)
        return hbox
