"The page controls for the Scan dialog"

from gi.repository import Gtk, GObject
from dialog import Dialog
from scanner.options import Options
from scanner.profile import Profile
from comboboxtext import ComboBoxText
from i18n import _

MAX_PAGES = 9999
MAX_INCREMENT = 99


class PageControls(Dialog):
    "The page controls for the Scan dialog"

    __gsignals__ = {
        "changed-num-pages": (GObject.SignalFlags.RUN_FIRST, None, (int,)),
        "changed-page-number-start": (GObject.SignalFlags.RUN_FIRST, None, (int,)),
        "changed-page-number-increment": (GObject.SignalFlags.RUN_FIRST, None, (int,)),
        "changed-side-to-scan": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
    }
    _num_pages = 1
    max_pages = GObject.Property(
        type=int,
        minimum=-1,
        maximum=MAX_PAGES,
        default=0,
        nick="Maximum number of pages",
        blurb="Maximum number of pages that can be scanned before the "
        "last page of the document",
    )
    _batch_start = None  # page number the current facing batch starts at
    _batch_n = 0  # number of pages scanned in the current facing batch
    _page_number_start = 1
    _page_number_increment = 1
    _sided = "single"
    _side_to_scan = "facing"
    _document = None
    available_scan_options = Options([])
    allow_batch_flatbed = False
    ignore_duplex_capabilities = False
    adf_defaults_scan_all_pages = GObject.Property(
        type=bool,
        default=True,
        nick="Select # pages = all on selecting ADF",
        blurb="Select # pages = all on selecting ADF",
    )

    @GObject.Property(
        type=int,
        minimum=0,
        maximum=MAX_PAGES,
        default=1,
        nick="Number of pages",
        blurb="Number of pages to be scanned",
    )
    def num_pages(self):
        "getter for num_pages attribute"
        return self._num_pages

    @num_pages.setter
    def num_pages(self, newval):
        if newval == self._num_pages:
            return
        options = self.available_scan_options
        if (
            newval == 1
            or self.allow_batch_flatbed
            or (
                hasattr(self, "thread")  # in __init__(), thread may not yet exist
                and self.thread.device_handle is not None
                and not options.flatbed_selected(self.thread.get_option_value)
            )
        ):
            self._num_pages = newval
            self.current_scan_options.add_frontend_option("num_pages", newval)
            self.emit("changed-num-pages", newval)

    @GObject.Property(
        type=int,
        minimum=1,
        maximum=MAX_PAGES,
        default=1,
        nick="Starting page number",
        blurb="Page number of first page to be scanned",
    )
    def page_number_start(self):
        "getter for page_number_start attribute"
        return self._page_number_start

    @page_number_start.setter
    def page_number_start(self, newval):
        self._page_number_start = newval
        self.emit("changed-page-number-start", newval)

    @GObject.Property(
        type=int,
        minimum=-MAX_INCREMENT,
        maximum=MAX_INCREMENT,
        default=1,
        nick="Page number increment",
        blurb="Amount to increment page number when scanning multiple pages",
    )
    def page_number_increment(self):
        "getter for page_number_increment attribute"
        return self._page_number_increment

    @page_number_increment.setter
    def page_number_increment(self, newval):
        self._page_number_increment = newval
        self.emit("changed-page-number-increment", newval)

    # TODO: now we can use an enum here, now supported by the python bindings
    # GObject.TypeModule.register_enum( 'Scantpaper::Dialog::Scan::Sided',
    #         ["single","double"] )
    @GObject.Property(
        type=str, default="single", nick="Sided", blurb="Either single or double"
    )
    def sided(self):
        "getter for sided attribute"
        return self._sided

    @sided.setter
    def sided(self, newval):
        self._sided = newval
        widget = self.buttons
        if newval == "double":
            widget = self.buttond
        else:
            # selecting single-sided also selects facing page.
            self.side_to_scan = "facing"
        widget.set_active(True)

    # TODO: now we can use an enum here, now supported by the python bindings
    # GObject.TypeModule.register_enum( 'Scantpaper::Dialog::Scan::Side',
    #         ["facing","reverse"] )
    @GObject.Property(
        type=object, nick="Side to scan", blurb="Either facing or reverse"
    )
    def side_to_scan(self):
        "getter for side_to_scan attribute"
        return self._side_to_scan

    @side_to_scan.setter
    def side_to_scan(self, newval):
        if newval not in ["facing", "reverse"]:
            raise ValueError(f"Invalid value for side-to-scan: {newval}")
        self._side_to_scan = newval
        self.combobs.set_active(0 if newval == "facing" else 1)
        self.emit("changed-side-to-scan", newval)
        if newval == "facing":
            self._reset_batch()
        else:
            self._fix_batch()

    @GObject.Property(
        type=object, nick="Document", blurb="Document object for new scans"
    )
    def document(self):
        "getter for document attribute"
        return self._document

    @document.setter
    def document(self, newval):
        self._document = newval

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

        self.current_scan_options = Profile()

        # Notebook to collate options
        self.notebook = Gtk.Notebook()
        self.notebook.set_scrollable(True)
        self.get_content_area().pack_end(self.notebook, True, True, 0)

        # Notebook page 1
        scwin = Gtk.ScrolledWindow()
        self.notebook.append_page(
            child=scwin, tab_label=Gtk.Label(label=_("Page Options"))
        )
        scwin.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        self._notebook_pages = [Gtk.Box(orientation=Gtk.Orientation.VERTICAL)]
        border_width = (
            self.get_style_context().get_border(Gtk.StateFlags.NORMAL).left
        )  # ._get('content-area-border')
        self._notebook_pages[0].set_border_width(border_width)
        scwin.add(self._notebook_pages[0])

        # Frame for # pages
        self.framen = Gtk.Frame(label=_("# Pages"))
        self._notebook_pages[0].pack_start(self.framen, False, False, 0)
        vboxn = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vboxn.set_border_width(border_width)
        self.framen.add(vboxn)

        # the first radio button has to set the group,
        # which is None for the first button
        # All button
        bscanall = Gtk.RadioButton.new_with_label_from_widget(None, _("All"))
        bscanall.set_tooltip_text(_("Scan all pages"))
        vboxn.pack_start(bscanall, True, True, 0)
        bscanall.connect("clicked", self._do_clicked_scan_all, bscanall)

        # Entry button
        hboxn = Gtk.Box()
        vboxn.pack_start(hboxn, True, True, 0)
        self._bscannum = Gtk.RadioButton.new_with_label_from_widget(bscanall, "#:")
        self._bscannum.set_tooltip_text(_("Set number of pages to scan"))
        hboxn.pack_start(self._bscannum, False, False, 0)

        # Number of pages
        spin_buttonn = Gtk.SpinButton.new_with_range(1, MAX_PAGES, 1)
        spin_buttonn.set_tooltip_text(_("Set number of pages to scan"))
        hboxn.pack_end(spin_buttonn, False, False, 0)
        self._bscannum.connect("clicked", self._do_clicked_scan_number, spin_buttonn)
        self.connect(
            "changed-num-pages",
            self._do_changed_num_pages,
            bscanall,
            self._bscannum,
            spin_buttonn,
        )

        # Actively set a radio button to synchronise GUI and properties
        self._bscannum.set_active(True)

        # vbox for duplex/simplex page numbering in order to be able to show/hide
        # them together.
        self._vboxx = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self._notebook_pages[0].pack_start(self._vboxx, False, False, 0)

        # Switch between basic and extended modes
        hbox = Gtk.Box()
        label = Gtk.Label(label=_("Extended page numbering"))
        hbox.pack_start(label, False, False, 0)
        self.checkx = Gtk.Switch()
        hbox.pack_end(self.checkx, False, False, 0)
        self._vboxx.pack_start(hbox, False, False, 0)

        self._create_extended_mode(spin_buttonn, self._bscannum)

    def _create_extended_mode(self, spin_buttonn, bscannum):

        # Frame for extended mode
        self.framex = Gtk.Frame(label=_("Page number"))
        self._vboxx.pack_start(self.framex, False, False, 0)
        vboxx = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        border_width = (
            self.get_style_context().get_border(Gtk.StateFlags.NORMAL).left
        )  # ._get('content-area-border')
        vboxx.set_border_width(border_width)
        self.framex.add(vboxx)

        # SpinButton for starting page number
        spin_buttons = spinbutton_in_hbox(
            vboxx, _("Insert before page N"), 1, MAX_PAGES, 1
        )
        spin_buttons.set_tooltip_text(
            _("Page number before which scans will be inserted")
        )
        spin_buttons.connect("value-changed", self._do_start_page_changed)
        self.connect(
            "changed-page-number-start",
            self._do_changed_page_number_start,
            spin_buttons,
        )

        # SpinButton for page number increment
        spin_buttoni = spinbutton_in_hbox(
            vboxx, _("Position advance"), -MAX_INCREMENT, MAX_INCREMENT, 1
        )
        spin_buttoni.set_tooltip_text(
            _(
                "Position advance for each subsequent scan "
                "(0 = keep inserting before the same page)"
            )
        )
        spin_buttoni.connect("value-changed", self._do_spin_buttoni_value_changed)
        self.connect(
            "changed-page-number-increment",
            self._do_changed_page_number_increment,
            spin_buttoni,
        )

        # Setting this here to fire callback running update_start
        spin_buttons.set_value(self.page_number_start)
        spin_buttonn.connect("value-changed", self._do_num_pages_changed, bscannum)

        # Frame for standard mode
        self.frames = Gtk.Frame(label=_("Source document"))
        self._vboxx.pack_start(self.frames, False, False, 0)
        vboxs = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vboxs.set_border_width(border_width)
        self.frames.add(vboxs)

        # Single sided button
        self.buttons = Gtk.RadioButton.new_with_label_from_widget(
            None, _("Single sided")
        )
        self.buttons.set_tooltip_text(_("Source document is single-sided"))
        vboxs.pack_start(self.buttons, True, True, 0)
        self.buttons.connect("clicked", self._do_buttons_clicked)

        # Double sided button
        self.buttond = Gtk.RadioButton.new_with_label_from_widget(
            self.buttons, _("Double sided")
        )
        self.buttond.set_tooltip_text(_("Source document is double-sided"))
        vboxs.pack_start(self.buttond, False, False, 0)

        # Facing/reverse page button
        hboxs = Gtk.Box()
        vboxs.pack_start(hboxs, True, True, 0)
        labels = Gtk.Label(label=_("Side to scan"))
        hboxs.pack_start(labels, False, False, 0)
        self.combobs = ComboBoxText()
        for text in (_("Facing"), _("Reverse")):
            self.combobs.append_text(text)
        self.combobs.connect("changed", self._do_side_to_scan_combo_changed)
        self.connect("changed-side-to-scan", self._do_side_to_scan_changed)
        self.combobs.set_tooltip_text(
            _("Sets which side of a double-sided document is scanned")
        )
        self.combobs.set_active(0)

        # Have to do this here because setting the facing combobox switches it
        self.buttons.set_active(True)
        self.num_pages = 1
        hboxs.pack_end(self.combobs, False, False, 0)

        # Have to put the extended pagenumber checkbox here
        # to reference simple controls
        self.checkx.connect(
            "notify::active",
            _extended_pagenumber_checkbox_callback,
            [self],
        )

    def _do_clicked_scan_all(self, bscanall, _value):
        if bscanall.get_active():
            self.num_pages = 0

    def _do_start_page_changed(self, spin_buttons):
        self.page_number_start = spin_buttons.get_value()

    def _do_changed_page_number_start(self, _self, value, spin_buttons):
        spin_buttons.set_value(value)

    def _do_spin_buttoni_value_changed(self, spin_buttoni):
        self.page_number_increment = spin_buttoni.get_value()

    def _do_changed_page_number_increment(self, _self, value, spin_buttoni):
        spin_buttoni.set_value(value)

    def _do_clicked_scan_number(self, bscannum, spin_buttonn):
        if bscannum.get_active():
            self.num_pages = spin_buttonn.get_value()

    def _do_changed_num_pages(self, _self, value, bscanall, bscannum, spin_buttonn):
        if value == 0:
            bscanall.set_active(True)
        else:
            # if spin button is already $value, but pages = all is selected,
            # then the callback will not fire to activate # pages, so doing
            # it here
            bscannum.set_active(True)
            spin_buttonn.set_value(value)

    def _do_num_pages_changed(self, spin_buttonn, bscannum):
        "Callback on changing number of pages"
        self.num_pages = spin_buttonn.get_value()
        bscannum.set_active(True)  # Set the radiobutton active

    def _do_buttons_clicked(self, buttons):
        self.sided = "single" if buttons.get_active() == 1 else "double"

    def _do_side_to_scan_combo_changed(self, combobs):
        self.buttond.set_active(True)  # Set the radiobutton active
        self.side_to_scan = "facing" if combobs.get_active() == 0 else "reverse"

    def _do_side_to_scan_changed(self, _self, value):
        if self.sided != "double":
            return
        if value == "facing":
            self.num_pages = 0

    def _reset_batch(self):
        "Start tracking a new facing batch for the reverse pass"
        self._batch_start = None
        self._batch_n = 0
        self.max_pages = 0

    def _fix_batch(self):
        "Freeze the current facing batch as the limit for the reverse pass"
        self.max_pages = self._batch_n
        if self._batch_n > 0 and (
            self.num_pages == 0 or self.num_pages > self._batch_n
        ):
            self.num_pages = self._batch_n

    def _flatbed_or_duplex_callback(self):
        options = self.available_scan_options
        if options is not None and hasattr(self, "thread") and hasattr(self, "_vboxx"):
            if options.flatbed_selected(self.thread.get_option_value) or (
                options.can_duplex() and not self.ignore_duplex_capabilities
            ):
                self._vboxx.hide()
            else:
                self._vboxx.show()


def spinbutton_in_hbox(vbox, label, vmin, vmax, step):
    "pack a label and a spinbutton in an hbox"
    hbox = Gtk.Box()
    vbox.pack_start(hbox, False, False, 0)
    hbox.pack_start(Gtk.Label(label=label), False, False, 0)
    spin_button = Gtk.SpinButton.new_with_range(vmin, vmax, step)
    hbox.pack_end(spin_button, False, False, 0)
    return spin_button


def _extended_pagenumber_checkbox_callback(widget, _param, data):
    dialog = data[0]
    if widget.get_active():
        dialog.frames.hide()
        dialog.framex.show_all()
    else:
        dialog.frames.show_all()
        dialog.framex.hide()
