#!/usr/bin/python3

import os
import subprocess
import threading
import time

import gi
gi.require_version("Gtk", "3.0")

from gi.repository import GObject, Gtk


CANNOT = -1
IDLE = 0
SCANNING = 1


def grid():
    g = Gtk.Grid()
    g.set_column_spacing(8)
    g.set_row_spacing(8)
    return g


def dialog(parent, messagetype, buttonstype, primary_markup, secondary_markup):
    d = Gtk.MessageDialog(
        parent,
        Gtk.DialogFlags.DESTROY_WITH_PARENT,
        messagetype,
        buttonstype,
        "",
    )
    d.set_markup(primary_markup)
    d.format_secondary_markup(secondary_markup)
    ret = d.run()
    d.destroy()
    return ret


class MainWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="QuikScan")
        self.connect("delete-event", lambda *_: Gtk.main_quit())

        header = Gtk.HeaderBar()
        header.set_title("Enter a file name for your scan")
        header.set_has_subtitle(False)
        header.set_show_close_button(True)
        self.header = header

        savebutton = Gtk.Button.new_with_mnemonic("_Save")
        savebutton.connect("clicked", lambda *_: self.user_wants_scan())
        self.savebutton = savebutton
        header.pack_end(savebutton)

        sourcepopover = Gtk.Popover()

        def show_popover():
            sourcepopover.show_all()
            if duplex.get_active():
                duplex.grab_focus()
            else:
                adf.grab_focus()

        sourcebutton = Gtk.MenuButton()
        sourcebuttongrid = Gtk.Grid()
        sourcebuttonlabel = Gtk.Label()
        sourcebuttonlabel.set_label("_Options")
        sourcebuttonlabel.set_use_underline(True)
        sourcebuttonicon = Gtk.Image.new_from_icon_name("pan-down-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
        sourcebuttongrid.attach(sourcebuttonlabel, 0, 0, 1, 1)
        sourcebuttongrid.attach(sourcebuttonicon, 1, 0, 1, 1)
        sourcebutton.add(sourcebuttongrid)
        
        header.pack_start(sourcebutton)
        sourcebutton.set_popover(sourcepopover)
        sourcebutton.connect("clicked", lambda *_: show_popover())

        duplex = Gtk.RadioButton(label="Double-sided scan from automatic document feeder")
        adf = Gtk.RadioButton.new_from_widget(duplex)
        adf.set_label("Single-sided scan from automatic document feeder")
        self.duplex = duplex
        self.adf = adf

        a4 = Gtk.RadioButton(label="A4")
        letter = Gtk.RadioButton.new_from_widget(a4)
        letter.set_label("Letter")
        self.a4 = a4
        self.letter = letter

        for i in [a4, letter, duplex, adf]:
            i.set_margin_start(8)

        scansource = Gtk.Label()
        scansource.set_markup("<b>Scan source</b>")
        originalssize = Gtk.Label()
        originalssize.set_markup("<b>Originals size</b>")

        popoverbox = grid()
        popoverbox.set_border_width(8)
        popoverbox.attach(scansource, 0, 0, 1, 1)
        popoverbox.attach(duplex, 0, 1, 1, 1)
        popoverbox.attach(adf, 0, 2, 1, 1)
        popoverbox.attach(originalssize, 0, 3, 1, 1)
        popoverbox.attach(a4, 0, 4, 1, 1)
        popoverbox.attach(letter, 0, 5, 1, 1)
        sourcepopover.add(popoverbox)

        chooser = Gtk.FileChooserWidget()
        chooser.set_action(Gtk.FileChooserAction.SAVE)
        self.chooser = chooser
        chooser.connect("selection-changed", lambda *_: self.update_can_scan())
        choosergrid = grid()
        choosergrid.attach(chooser, 0, 0, 1, 1)

        progressbar = Gtk.ProgressBar()
        progressbar.set_no_show_all(True)
        self.progressbar = progressbar

        maingrid = grid()
        maingrid.attach(choosergrid, 0, 1, 1, 1)
        self.maingrid = maingrid

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.pack_start(header, False, True, 0)
        box.pack_start(progressbar, False, True, 0)
        box.pack_start(maingrid, True, True, 0)
        box.set_focus_chain([maingrid, header])

        self.add(box)
        self.set_scan_mode(CANNOT)

    def update_can_scan(self):
        if not self.chooser.get_filename():
            self.set_scan_mode(CANNOT)
        else:
            self.set_scan_mode(IDLE)

    def set_scan_mode(self, mode):
        if mode == CANNOT:
            self.savebutton.show()
            self.savebutton.set_sensitive(False)
            self.progressbar.hide()
            self.header.set_sensitive(True)
            self.maingrid.set_sensitive(True)
            self.savebutton.set_can_default(False)
        elif mode == IDLE:
            self.savebutton.show()
            self.savebutton.set_sensitive(True)
            self.progressbar.hide()
            self.header.set_sensitive(True)
            self.maingrid.set_sensitive(True)
            self.savebutton.set_can_default(True)
            self.savebutton.grab_default()
        elif mode == SCANNING:
            self.savebutton.hide()
            self.progressbar.show()
            self.header.set_sensitive(False)
            self.maingrid.set_sensitive(False)
        else:
            assert 0, "not reached"

    def user_wants_scan(self):
        f = self.chooser.get_filename()
        scan = True
        if os.path.exists(f):
            ret = dialog(self,
                         Gtk.MessageType.QUESTION,
                         Gtk.ButtonsType.YES_NO,
                         "<span size='x-large' weight='bold'>The file you selected already exists</span>",
                         "Do you want to overwrite the existing file?")
            if ret != Gtk.ResponseType.YES:
                scan = False
        if scan:
            self.scan()
    
    def scan(self):
        cmd = ['hp-scan']
        cmd += ['-o', self.chooser.get_filename()]
        cmd += ['--pdf=true']
        if self.a4.get_active():
            cmd += ['--size=a4']
        elif self.letter.get_active():
            cmd += ['--size=letter']
        else:
            assert 0, "not reached"
        if self.duplex.get_active():
            cmd += ['--duplex']
        elif self.adf.get_active():
            cmd += ['--adf']
        else:
            assert 0, "not reached"
        cmd += ['-s', 'file']
        cmd += ['-m', 'color']

        try:
            p = subprocess.Popen(cmd)
        except Exception as e:
            dialog(self,
                   Gtk.MessageType.ERROR,
                   Gtk.ButtonsType.CLOSE,
                   "<span size='x-large' weight='bold'>Scanning failed</span>",
                   "The hp-scan program could not be started: %s" % e)
            return

        self.set_scan_mode(SCANNING)

        def wait_for_scan():
            while True:
                time.sleep(0.1)
                ret = p.poll()
                if ret is not None:
                    break
                GObject.idle_add(self.progressbar.pulse)
            if ret == 0:
                GObject.idle_add(self.scan_finished)
            else:
                GObject.idle_add(self.scan_failed, (ret,))
        t = threading.Thread(target=wait_for_scan)
        t.setDaemon(True)
        t.start()

    def scan_finished(self):
        self.set_scan_mode(IDLE)
        
    def scan_failed(self, retval):
        dialog(self,
               Gtk.MessageType.ERROR,
               Gtk.ButtonsType.CLOSE,
               "<span size='x-large' weight='bold'>Scanning failed</span>",
               "The hp-scan process exited with error %s" % retval)
        self.set_scan_mode(IDLE)
        

def main():
    w = MainWindow()
    GObject.idle_add(w.show_all)
    Gtk.main()


if __name__ == "__main__":
    main()
