#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# (c) Copyright 2003-2007 Hewlett-Packard Development Company, L.P.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Author: Don Welch
#

from __future__ import division

__version__ = '1.0'
__title__ = 'Scan Utility'
__doc__ = "SANE-based scan utility for HPLIP." 

# Std Lib
import sys
import os
import os.path
import getopt
import signal
import time
import socket

# Local
from base.g import *
from base import tui
import base.utils as utils
from base import device

log.set_module('hp-scan')

prop.prog = sys.argv[0]

device_uri = None
username = prop.username
mode = GUI_MODE
mode_specified = False
res = 300
scan_mode = 'color'
tlx = None
tly = None
brx = None
bry = None
units = "mm"
output = ''
dest = []
email_from = ''
email_to = []
email_subject = 'hp-scan from %s' % socket.gethostname()
email_note = ''
fax = ''
resize = 100
contrast = 0
brightness = 0
printer = ''
page_size = ''
size_desc = ''
page_units = 'mm'
valid_res = (75, 150, 300, 600, 1200, 2400, 4800)
default_res = 300
scanner_compression = 'JPEG'
adf = False

PAGE_SIZES = { # in mm
    '5x7' : (127, 178, "5x7 photo", 'in'),
    '4x6' : (102, 152, "4x6 photo", 'in'),
    '3x5' : (76, 127, "3x5 index card", 'in'),
    'a2_env' : (111, 146, "A2 Envelope", 'in'),
    'a3' : (297, 420, "A3", 'mm'),
    "a4" : (210, 297, "A4", 'mm'),
    "a5" : (148, 210, "A5", 'mm'),
    "a6" : (105, 148, "A6", 'mm'),
    "b4" : (257, 364, "B4", 'mm'),
    "b5" : (182, 257, "B5", 'mm'),
    "c6_env" : (114, 162, "C6 Envelope", 'in'),
    "dl_env" : (110, 220, "DL Envelope", 'in'),
    "exec" : (184, 267, "Executive", 'in'),
    "flsa" : (216, 330, "Flsa", 'mm'),
    "higaki" : (100, 148, "Hagaki", 'mm'),
    "japan_env_3" : (120, 235, "Japanese Envelope #3", 'mm'),
    "japan_env_4" : (90, 205, "Japanese Envelope #4", 'mm'),
    "legal" : (215, 356, "Legal", 'in'),
    "letter" : (215, 279, "Letter", 'in'),
    "no_10_env" : (105, 241, "Number 10 Envelope", 'in'),
    "oufufu-hagaki" : (148, 200, "Oufuku-Hagaki", 'mm'),
    "photo" : (102, 152, "Photo", 'in'),
    "super_b" : (330, 483, "Super B", 'in'),
    }

USAGE = [(__doc__, "", "name", True),
         ("Usage: hp-scan [SANE-DEVICE-URI] [MODE] [-n OPTIONS] [OPTIONS]", "", "summary", True),
         ("[SANE-DEVICE-URI]", "", "header", False),
         ("SANE device URI:", "-d<sane_device_uri> or --device=<sane_device_uri>", "option", False),
         ("", "URI format: hpaio:/<bus>/<model>?<identification>", "option", False),

         utils.USAGE_SPACE,
         ("[MODE]", "", "header", False),
         ("Enter graphical UI mode:", "-u or --gui (Default)", "option", False),
         ("Run in non-interactive mode:", "-n or --non-interactive", "option", False),

         utils.USAGE_SPACE,
         ("[-n OPTIONS] (General) (Not applicable to GUI mode)", "", "header", False),
         ("Scan destinations:", "-s<dest_list> or --dest=<dest_list>", "option", False),
         ("", "where <dest_list> is a comma separated list containing one or more of: 'file'\*, ", "option", False),
         ("", "'viewer', 'editor', 'pdf', 'fax', or 'print'. Use only commas between values, no spaces.", "option", False), 
         ("Scan mode:", "-m<mode> or --mode=<mode>. Where <mode> is 'color'\*, 'gray' or 'lineart'.", "option", False),
         ("Scanning resolution:", "-r<resolution_in_dpi> or --res=<resolution_in_dpi> or --resolution=<resolution_in_dpi>", "option", False),
         ("", "where <resolution_in_dpi> is %s (300 is default)." % ', '.join([str(x) for x in valid_res]), "option", False),
         ("Image resize:", "--resize=<scale_in_%> (min=1%, max=400%, default=100%)", "option", False),
         ("Image contrast:", "--contrast=<contrast>", "option", False),
         ("ADF mode (EXPERIMENTAL):", "--adf (Note, only PDF output is supported when using the ADF)", "option", False),

         utils.USAGE_SPACE,
         ("[-n OPTIONS] (Scan area) (Not applicable to GUI mode)", "", "header", False),
         ("Specify the units for area/box measurements:", "-t<units> or --units=<units>", "option", False),
         ("", "where <units> is 'mm'\*, 'cm', 'in', 'px', or 'pt' ('mm' is default).", "option", False),
         ("Scan area:", "-a<tlx>,<tly>,<brx>,<bry> or --area=<tlx>,<tly>,<brx>,<bry>", "option", False),
         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
         ("", "Units for tlx, tly, brx, and bry are specified by -t/--units (default is 'mm').", "option", False),
         ("", "Use only commas between values, no spaces.", "option", False),
         ("Scan box:", "--box=<tlx>,<tly>,<width>,<height>", "option", False),
         ("", "tlx and tly coordinates are relative to the upper left corner of the scan area.", "option", False),
         ("", "Units for tlx, tly, width, and height are specified by -t/--units (default is 'mm').", "option", False),         
         ("", "Use only commas between values, no spaces.", "option", False),
         ("Top left x of the scan area:", "--tlx=<tlx>", "option", False),
         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
         ("Top left y of the scan area:", "--tly=<tly>", "option", False),
         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
         ("Bottom right x of the scan area:", "--brx=<brx>", "option", False),
         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
         ("Bottom right y   of the scan area:", "--bry=<bry>", "option", False),
         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
         ("Specify the scan area based on a paper size:", "--size=<paper size name>", "option", False),
         ("", "where <paper size name> is one of: %s" % ', '.join(PAGE_SIZES.keys()), "option", False), 
         
         utils.USAGE_SPACE,
         ("[-n OPTIONS] ('file' dest) (Not applicable to GUI mode)", "", "header", False),
         ("Filename for 'file' destination:", "-o<file> or -f<file> or --file=<file> or --output=<file>", "option", False),

         utils.USAGE_SPACE,
         ("[-n OPTIONS] ('pdf' dest) (Not applicable to GUI mode)", "", "header", False),
         ("PDF viewer application:", "--pdf=<pdf_viewer>", "option", False),
         
         utils.USAGE_SPACE,
         ("[-n OPTIONS] ('viewer' dest) (Not applicable to GUI mode)", "", "header", False),
         ("Image viewer application:", "-v<viewer> or --viewer=<viewer>", "option", False),
         utils.USAGE_SPACE,
         
         ("[-n OPTIONS] ('editor' dest) (Not applicable to GUI mode)", "", "header", False),
         ("Image editor application:", "-e<editor> or --editor=<editor>", "option", False),
         utils.USAGE_SPACE,

         ("[-n OPTIONS] ('email' dest) (Not applicable to GUI mode)", "", "header", False),
         ("From: address for 'email' dest:", "--email-from=<email_from_address> (required for 'email' dest.)", "option", False),

         ("To: address for 'email' dest:", "--email-to=<email__to_address> (required for 'email' dest.)", "option", False),
         ("Email subject for 'email' dest:", '--email-subject="<subject>"', "option", False),
         ("", 'Use double quotes (") around the subject if it contains space characters.', "option", False),
         ("Note or message for the 'email' dest:", '--email-msg="<msg>" or --email-note="<note>"', "option", False),
         ("", 'Use double quotes (") around the note/message if it contains space characters.', "option", False),

         utils.USAGE_SPACE,
         ("[-n OPTIONS] ('fax' dest) (Not applicable to GUI mode)", "", "header", False),
         ("Fax queue/printer:", "--fax=<fax_printer_name>", "option", False),
         utils.USAGE_SPACE,
         ("[-n OPTIONS] ('printer' dest) (Not applicable to GUI mode)", "", "header", False),
         ("Printer queue/printer:", "--printer=<printer_name>", "option", False),
         utils.USAGE_SPACE,
         ("[-n OPTIONS] (advanced) (Not applicable to GUI mode)", "", "header", False),
         ("Set the scanner compression mode:", "-x<mode> or --compression=<mode>, <mode>='raw', 'none' or 'jpeg' ('jpeg' is default) ('raw' and 'none' are equivalent)", "option", False),
         utils.USAGE_SPACE,
         utils.USAGE_OPTIONS,
         utils.USAGE_LOGGING1, utils.USAGE_LOGGING2, utils.USAGE_LOGGING3,
         utils.USAGE_HELP,
         utils.USAGE_NOTES,
         ("1. If no dest is provided, the 'file' dest will be automatically invoked.", "", "note", False),
         ("2. If applications for viewer, editor, or pdf viewer are not provided, reasonable defaults will be used.", "", "note", False),
         ("3. If --printer is not specified, the CUPS default will be used if available.", "", "note", False),
         ("4. If an output file is not specified with the 'file' dest, a reasonable default will be used.", "", "note", False),
         ("5. Some options may not be valid on some scanning devices.", "", "note", False),
         ("6. The following features are not yet implemented: GUI mode, batch scanning, film/negative scanning, contrast adjustment, brightness adjustment, autocrop, resize to axb, resize to xKB, ", "", "note", False),

         utils.USAGE_SPACE,
         utils.USAGE_EXAMPLES,
         ("Quickly (low-res) scan entire page in color to file:", "$ hp-scan -n -r75",  "example", False),
         ("Scan upper left 1in corner and send as email:", '$ hp-scan -n --box=0,0,1,1 -tin -semail --email-from=foo@bar.org --email-to=bar@foo.org --email-note="Test scan" --email-subject="Test scan email"', "example", False),
         ("Scan entire page in 300dpi grayscale and then edit:", "$ hp-scan -n -seditor -mgray",  "example", False),
         ("Launch the hp-scan GUI:", "$ hp-scan",  "example", False),
         ("Scan into The GIMP:", "$ hp-scan -n --editor=gimp",  "example", False),
        ]

def usage(typ='text'):
    if typ == 'text':
        utils.log_title(__title__, __version__)

    utils.format_text(USAGE, typ, __title__, 'hp-scan', __version__)
    sys.exit(0)

try:
    viewer = ''
    viewer_list = ['kview', 'display', 'gwenview', 'eog', 'kuickshow',]
    for v in viewer_list:
        vv = utils.which(v)
        if vv:
            viewer = os.path.join(vv, v)
            break


    editor = ''
    editor_list = ['kolourpaint', 'gimp', 'krita', 'cinepaint', 'mirage',]
    for e in editor_list:
        ee = utils.which(e)
        if ee:
            editor = os.path.join(ee, e)
            break

    pdf_viewer = ''
    pdf_viewer_list = ['kpdf', 'acroread', 'xpdf', 'evince',]
    for v in pdf_viewer_list:
        vv = utils.which(v)
        if vv:
            pdf_viewer = os.path.join(vv, v)
            break

    try:
        opts, args = getopt.getopt(sys.argv[1:],'l:hd:p:b:gunr:m:t:o:s:f:v:e:c:a:x:', 
            ['device=', 'printer=', 'level=', 
             'help', 'help-rest', 
             'help-man', 'logfile=', 
             'gui', 'non-interactive', 'logging=',
              'help-desc', 'resolution=', 'res=', 'mode=',
              'tlx=', 'tly=', 'brx=', 'bry=', 'units=', 
              'area=', 'box=', 'output=', 'dest=', 'destination=',
              'file=', 'pdf=', 'viewer=', 'editor=',
              'email-from=', 'email-to=', 'resize=',
              'email-subject=', 'email-note=', 'email-msg=',
              'contrast=', 'brightness=', 'size=', 'compression=',
              'adf', 'fax='])



    except getopt.GetoptError, e:
        log.error(e.msg)
        usage()
        sys.exit(1)

    if os.getenv("HPLIP_DEBUG"):
        log.set_level('debug')

    for o, a in opts:
        if o in ('-l', '--logging'):
            log_level = a.lower().strip()
            if not log.set_level(log_level):
                usage()

        elif o == '-g':
            log.set_level('debug')

        elif o in ('-h', '--help'):
            usage()

        elif o == '--help-rest':
            usage('rest')

        elif o == '--help-man':
            usage('man')

        elif o == '--help-desc':
            print __doc__,
            sys.exit(0)

        elif o in ('-d', '--device'):
            device_uri = a

        elif o in ('-n', '--non-interactive'):
            if mode_specified:
                log.error("You may only specify a single mode as a parameter (-n or -u).")
                sys.exit(1)

            mode = NON_INTERACTIVE_MODE
            mode_specified = True

        elif o in ('-u', '--gui'):
            if mode_specified:
                log.error("You may only specify a single mode as a parameter (-n or -u).")
                sys.exit(1)

            mode = GUI_MODE
            mode_specified = True

        elif o in ('-x', '--compression'):
            a = a.strip().lower()

            if a in ('jpeg', 'jpg'):
                scanner_compression = 'JPEG'

            elif a in ('raw', 'none'):
                scanner_compression = 'None'

            else:
                log.error("Invalid compression value. Valid values are 'jpeg', 'raw', and 'none'.")
                log.error("Using default value of 'jpeg'.")
                scanner_compression = 'JPEG'

        elif o in ('-m', '--mode'):
            a = a.strip().lower()

            if a in ('color', 'colour'):
                scan_mode = 'color'

            elif a in ('lineart', 'bw', 'b&w'):
                scan_mode = 'lineart'

            elif a in ('gray', 'grayscale', 'grey', 'greyscale'):
                scan_mode = 'gray'

            else:
                log.error("Invalid mode. Using default of 'color'.")
                log.error("Valid modes are 'color', 'lineart', or 'gray'.")
                scan_mode = 'color'

        elif o in ('--res', '--resolution', '-r'):
            try:
                r = int(a.strip())
            except ValueError:
                log.error("Invalid resolution. Using default of %s dpi." % default_res)
                log.error("Valid resolutions are %s dpi." % ', '.join([str(x) for x in valid_res]))
                res = default_res
            else:
                if r in valid_res: 
                    res = r
                else:
                    res = valid_res[0]
                    min_dist = sys.maxint
                    for x in valid_res: 
                        if abs(r-x) < min_dist:
                            min_dist = abs(r-x)
                            res = x

                    log.warn("Invalid resolution. Using closest valid resolution of %d dpi" % res)
                    log.error("Valid resolutions are %s dpi." % ', '.join([str(x) for x in valid_res]))

        elif o in ('-t', '--units'):
            a = a.strip().lower()

            if a in ('in', 'inch', 'inches'):
                units = 'in'

            elif a in ('mm', 'milimeter', 'milimeters', 'millimetre', 'millimetres'):
                units = 'mm'

            elif a in ('cm', 'centimeter', 'centimeters', 'centimetre', 'centimetres'):
                units = 'cm'

            elif a in ('px', 'pixel', 'pixels', 'pel', 'pels'):
                units = 'px'

            elif a in ('pt', 'point', 'points'):
                units = 'pt'

            else:
                log.error("Invalid units. Using default of mm.")
                units = 'mm'

        elif o == '--tlx':
            a = a.strip().lower()
            try:
                f = float(a)
            except ValueError:
                log.error("Invalid value for tlx.")
            else:
                tlx = f

        elif o == '--tly':
            a = a.strip().lower()
            try:
                f = float(a)
            except ValueError:
                log.error("Invalid value for tly.")
            else:
                tly = f

        elif o == '--brx':
            a = a.strip().lower()
            try:
                f = float(a)
            except ValueError:
                log.error("Invalid value for brx.")
            else:
                brx = f

        elif o == '--bry':
            a = a.strip().lower()
            try:
                f = float(a)
            except ValueError:
                log.error("Invalid value for bry.")
            else:
                bry = f

        elif o in ('-a', '--area'): # tlx, tly, brx, bry
            a = a.strip().lower()
            try:
                tlx, tly, brx, bry = a.split(',')[:4]
            except ValueError:
                log.error("Invalid scan area. Using defaults.")
            else:
                try:
                    tlx = float(tlx)
                except ValueError:
                    log.error("Invalid value for tlx. Using defaults.")
                    tlx = None

                try:
                    tly = float(tly)
                except ValueError:
                    log.error("Invalid value for tly. Using defaults.")
                    tly = None

                try:
                    brx = float(brx)
                except ValueError:
                    log.error("Invalid value for brx. Using defaults.")
                    brx = None

                try:
                    bry = float(bry)
                except ValueError:
                    log.error("Invalid value for bry. Using defaults.")
                    bry = None

        elif o == '--box': # tlx, tly, w, h
            a = a.strip().lower()
            try:
                tlx, tly, width, height = a.split(',')[:4]
            except ValueError:
                log.error("Invalid scan area. Using defaults.")
            else:
                try:
                    tlx = float(tlx)
                except ValueError:
                    log.error("Invalid value for tlx. Using defaults.")
                    tlx = None

                try:
                    tly = float(tly)
                except ValueError:
                    log.error("Invalid value for tly. Using defaults.")
                    tly = None

                if tlx is not None:
                    try:
                        brx = float(width) + tlx
                    except ValueError:
                        log.error("Invalid value for width. Using defaults.")
                        brx = None
                else:
                    log.error("Cannot calculate brx since tlx is invalid. Using defaults.")
                    brx = None

                if tly is not None:
                    try:
                        bry = float(height) + tly
                    except ValueError:
                        log.error("Invalid value for height. Using defaults.")
                        bry = None    
                else:
                    log.error("Cannot calculate bry since tly is invalid. Using defaults.")
                    bry = None

        elif o == '--size':
            size = a.strip().lower()
            if size in PAGE_SIZES:
                brx, bry, size_desc, page_units = PAGE_SIZES[size]
                tlx, tly = 0, 0
                page_size = size
            else:
                log.error("Invalid page size. Valid page sizes are: %s" % ', '.join(PAGE_SIZES.keys()))
                log.error("Using defaults.")

        elif o in ('-o', '--output', '-f', '--file'):
            output = os.path.abspath(os.path.normpath(os.path.expanduser(a.strip())))

            try:
                ext = os.path.splitext(output)[1]
            except IndexError:
                log.error("Invalid filename extension.")
                output = ''
                if 'file' in dest:
                    dest.remove('file')
            else:
                if ext.lower() not in ('.jpg', '.png'):
                    log.error("Only JPG (.jpg) and PNG (.png) output files are supported.")
                    output = ''
                    if 'file' in dest:
                        dest.remove('file')
                else:
                    if os.path.exists(output):
                        log.warn("Output file '%s' exists. File will be overwritten." % output)

                    if 'file' not in dest:
                        dest.append('file')

        elif o in ('-s', '--dest', '--destination'):
            a = a.strip().lower().split(',')
            for aa in a:
                aa = aa.strip()
                if aa in ('file', 'fax', 'viewer', 'editor', 'printer', 'print', 'email', 'pdf') \
                    and aa not in dest:
                    if aa == 'print': aa = 'printer'
                    dest.append(aa)

        elif o in ('-v', '--viewer'):
            a = a.strip()
            b = utils.which(a)
            if not b:
                log.error("Viewer application not found.")
            else:
                viewer = os.path.join(b, a)
                if 'viewer' not in dest:
                    dest.append('viewer')

        elif o in ('-e', '--editor'):
            a = a.strip()
            b = utils.which(a)
            if not b:
                log.error("Editor application not found.")
            else:
                editor = os.path.join(b, a)
                if 'editor' not in dest:
                    dest.append('editor')

        elif o == '--pdf':
            a = a.strip()
            b = utils.which(a)
            if not b:
                log.error("PDF viewer application not found.")
            else:
                pdf_viewer = os.path.join(b, a)
                if 'pdf' not in dest:
                    dest.append('pdf')

        elif o in ('-p', '--printer'):
            pp = a.strip()
            from prnt import cups
            printer_list = cups.getPrinters()
            found = False
            for p in printer_list:
                if p.name == pp:
                    found = True
                    printer = pp
                    break

            if found: 
                if 'printer' not in dest:
                    dest.append('printer')
            else:
                log.error("Unknown/invalid printer name: %s" % pp)

        elif o == '--fax':
            #print "fax"
            pp = a.strip()
            from prnt import cups
            printer_list = cups.getPrinters()
            found = False
            for p in printer_list:
                if p.name == pp:
                    found = True
                    fax = pp
                    break

            if found: 
                if 'fax' not in dest:
                    dest.append('fax')
            else:
                log.error("Unknown/invalid fax name: %s" % pp)
            
        elif o == '--email-to':
            email_to = a.split(',')
            if 'email' not in dest:
                dest.append('email')

        elif o == '--email-from':
            email_from = a
            if 'email' not in dest:
                dest.append('email')

        elif o == '--email-subject':
            email_subject = a
            if 'email' not in dest:
                dest.append('email')

        elif o in ('--email-note', '--email-msg'):
            email_note = a
            if 'email' not in dest:
                dest.append('email')

        elif o == '--resize':
            a = a.replace("%", "")
            try:
                resize = int(a)
            except ValueError:
                resize = 100
                log.error("Invalid resize value. Using default of 100%.")

        elif o in ('-b', '--brightness'):
            pass

        elif o in ('-c', '--contrast'):
            try:
                contrast = int(a.strip())
            except ValueError:
                log.error("Invalid contrast value. Using default of 100.")
                contrast = 100
                
        elif o == '--adf':
            adf = True
            output_type = 'pdf'


    utils.log_title(__title__, __version__)
    
    if os.getuid() == 0:
        log.error("hp-scan should not be run as root.")

    if 'printer' in dest and not printer:
        from prnt import cups
        printer = cups.getDefaultPrinter()

        if printer is not None:
            log.warn("Print destination enabled with no printer specified.")
            log.warn("Using CUPS default printer '%s'." % printer)
        else:
            log.error("Print destination enabled with no printer specified.")
            log.error("No CUPS default printer found. Disabling 'print' destination.")
            dest.remove("printer")
            
    if 'fax' in dest and 'file' not in dest:
        log.error("Fax destination not implemented. Adding 'file' destination. Use output file to fax.")
        dest.append('file')

    if not dest:
        log.warn("No destinations specified. Adding 'file' destination by default.")
        dest.append('file')

    if 'file' in dest and not output:
        log.warn("File destination enabled with no output file specified.")

        if adf:
            log.info("Setting output format to PDF for ADF mode.")
            output = utils.createSequencedFilename("hpscan", ".pdf")
            output_type = 'pdf'
        else:
            if scan_mode == 'gray':
                log.info("Setting output format to PNG for greyscale mode.")
                output = utils.createSequencedFilename("hpscan", ".png")
                output_type = 'png'
            else:
                log.info("Setting output format to JPEG for color/lineart mode.")
                output = utils.createSequencedFilename("hpscan", ".jpg")
                output_type = 'jpeg'
            
        log.warn("Defaulting to '%s'." % output)

    else:
        try:
            output_type = os.path.splitext(output)[1].lower()[1:]
            if output_type == 'jpg':
                output_type = 'jpeg'
        except IndexError:
            output_type = ''
            
    if output_type and output_type not in ('jpeg', 'png', 'pdf'):
        log.error("Invalid output file format. File formats must be 'jpeg', 'png', or 'pdf'.")
        sys.exit(1)

    #sys.exit(0)
    ##if scan_mode == 'gray' and output_type and output_type != 'png':
    ##    log.error("Grayscale scans must be saved in PNG file format. To save in other formats, set the 'editor' destination and save the image from the editor in the desired format.")
    ##    sys.exit(1)
        
    if adf and output_type and output_type != 'pdf':
        log.error("ADF scans must be saved in PDF file format.")
        sys.exit(1)

    if 'email' in dest and (not email_from or not email_to):
        log.error("Email specified, but email to and/or email from address(es) were not specified.")
        log.error("Disabling 'email' destination.")
        dest.remove("email")

    if page_size:
        units = 'mm'

    if units == 'in':
        if tlx is not None: tlx = tlx * 25.4
        if tly is not None: tly = tly * 25.4
        if brx is not None: brx = brx * 25.4
        if bry is not None: bry = bry * 25.4

    elif units == 'cm':
        if tlx is not None: tlx = tlx * 10.0
        if tly is not None: tly = tly * 10.0
        if brx is not None: brx = brx * 10.0
        if bry is not None: bry = bry * 10.0

    elif units == 'pt':
        if tlx is not None: tlx = tlx * 0.3528
        if tly is not None: tly = tly * 0.3528
        if brx is not None: brx = brx * 0.3528
        if bry is not None: bry = bry * 0.3528

    elif units == 'px':
        log.warn("Units set to pixels. Using resolution of %ddpi for area calculations." % res)
        if tlx is not None: tlx = tlx / res * 25.4
        if tly is not None: tly = tly / res * 25.4
        if brx is not None: brx = brx / res * 25.4
        if bry is not None: bry = bry / res * 25.4

    if tlx is not None and brx is not None and tlx >= brx:
        log.error("Invalid values for tlx (%d) and brx (%d) (tlx>=brx). Using defaults." % (tlx, brx))
        tlx = brx = None

    if tly is not None and bry is not None and tly >= bry:
        log.error("Invalid values for tly (%d) and bry (%d) (tly>=bry). Using defaults." % (tly, bry))
        tly = bry = None




    # Security: Do *not* create files that other users can muck around with
    os.umask (0037)

    if not prop.scan_build:
        log.error("Scanning disabled in build. Exiting")
        sys.exit(1)

    if mode == GUI_MODE:
        log.error("GUI mode is not implemented yet. Please use -n. Refer to 'hp-scan -h' for help.")
        sys.exit(1)

        if not prop.gui_build:
            log.warn("GUI mode disabled in build. Reverting to interactive mode.")
            mode = NON_INTERACTIVE_MODE

        elif not os.getenv('DISPLAY'):
            log.warn("No display found. Reverting to interactive mode.")
            mode = NON_INTERACTIVE_MODE

        elif not utils.checkPyQtImport():
            log.warn("PyQt init failed. Reverting to interactive mode.")
            mode = NON_INTERACTIVE_MODE

    if mode == GUI_MODE:
        app = None
        sendfax = None

        from qt import *

        # UI Forms
        from ui.scanform import ScanForm

        # create the main application object
        app = QApplication(sys.argv)

        if loc is None:
            loc = user_cfg.ui.get("loc", "system")
            if loc.lower() == 'system':
                loc = str(QTextCodec.locale())
                log.debug("Using system locale: %s" % loc)

        if loc.lower() != 'c':
            log.debug("Trying to load .qm file for %s locale." % loc)
            trans = QTranslator(None)
            
            try:
                l, e = loc.split('.')
            except ValueError:
                l = loc
                e = 'utf8'
            
            qm_file = 'hplip_%s.qm' % l
            log.debug("Name of .qm file: %s" % qm_file)
            loaded = trans.load(qm_file, prop.localization_dir)
            
            if loaded:
                app.installTranslator(trans)
            else:
                loc = 'c'
        
        if loc == 'c':
            log.debug("Using default 'C' locale")
        else:
            log.debug("Using locale: %s" % loc)
            QLocale.setDefault(QLocale(loc))
            prop.locale = loc
            try:
                locale.setlocale(locale.LC_ALL, locale.normalize(loc))
            except locale.Error:
                pass

        scanui = ScanForm(device_uri, printer_name, args) 

        app.setMainWidget(scanui)

        pid = os.getpid()
        log.debug('pid=%d' % pid)

        scanui.show()

        signal.signal(signal.SIGPIPE, signal.SIG_IGN)

        try:
            log.debug("Starting GUI loop...")
            app.exec_loop()
        except KeyboardInterrupt:
            pass
        #except:
        #    log.exception()

    else: # NON_INTERACTIVE_MODE
        import Queue
        from scan import sane
        import scanext
        import cStringIO
        
        try:
            import subprocess
        except ImportError:
            # Pre-2.4 Python
            from base import subproc as subprocess

        try:
            import Image
        except ImportError:
            log.error("'hp-scan -n' requires the Python Imaging Library (PIL). Please install it and try again or run 'hp-scan -u' instead.")
            sys.exit(1)
        
    ##    if output_type == 'pdf':
    ##        try:
    ##            from reportlab.pdfgen import canvas
    ##        except ImportError:
    ##            log.error("PDF output requires ReportLab.")
    ##            sys.exit(1)
                
        sane.init()
        devices = sane.getDevices()

        if not device_uri:
            if len(devices) == 0:
                log.error("No scanning devices found.")
                sys.exit(1)

            elif len(devices) == 1:
                device_uri = devices[0][0]

            else:
                log.info(log.bold("\nChoose device:\n"))

                max_deviceid_size = 0
                for d in devices:
                    max_deviceid_size = max(len(d[0]), max_deviceid_size)

                formatter = utils.TextFormatter(
                        (
                            {'width': 4},
                            {'width': max_deviceid_size, 'margin': 2},
                        )
                    )

                log.info(formatter.compose(("Num.", "Device URI")))
                log.info(formatter.compose(('-'*4, '-'*(max_deviceid_size))))

                i = 0
                for d in devices:
                    log.info(formatter.compose((str(i), d[0])))
                    i += 1

                ok, x = tui.enter_range("\nEnter number 0...%d for device (q=quit) ?" % (i-1), 0, (i-1))
                if not ok: sys.exit(0)
                device_uri = devices[x][0]


        log.info(log.bold("Using device %s" % device_uri))
        log.info("Opening connection to device...")

        try:
            device = sane.openDevice(device_uri)
        except scanext.error, e:
            log.error(e)
            sys.exit(1)

        #print device.options
        #sys.exit(0)
        
        tlx = device.getOptionObj('tl-x').limitAndSet(tlx)
        tly = device.getOptionObj('tl-y').limitAndSet(tly)
        brx = device.getOptionObj('br-x').limitAndSet(brx)
        bry = device.getOptionObj('br-y').limitAndSet(bry)

        scan_area = (brx - tlx) * (bry - tly) # mm^2
        scan_px = scan_area * res * res / 645.16 # res is in DPI

        if scan_mode == 'color':
            scan_size = scan_px * 3 # 3 bytes/px
        else:
            scan_size = scan_px # 1 byte/px

        if scan_size > 52428800: # 50MB
            if res > 600:
                log.warn("Using resolutions greater than 600 dpi will cause very large files to be created.")
            else:
                log.warn("The scan current parameters will cause very large files to be created.")

            log.warn("This can cause the scan to take a long time to complete and may cause your system to slow down.")
            log.warn("Approx. number of bytes to read from scanner: %s" % utils.format_bytes(scan_size, True))

        res = device.getOptionObj('resolution').limitAndSet(res)

        device.setOption('compression', scanner_compression)

        if brx - tlx <= 0.0 or bry - tly <= 0.0:
            log.error("Invalid scan area (width or height is negative).")
            sys.exit(1)

        log.info("")
        log.info("Resolution: %ddpi" % res)
        log.info("Mode: %s" % scan_mode)
        log.info("Compression: %s" % scanner_compression)
        log.info("Scan area (mm):")
        log.info("  Top left (x,y): (%fmm, %fmm)" % (tlx, tly))
        log.info("  Bottom right (x,y): (%fmm, %fmm)" % (brx, bry))
        log.info("  Width: %fmm" % (brx - tlx))
        log.info("  Height: %fmm" % (bry - tly))

        if page_size:
            units = page_units # for display purposes only
            log.info("Page size: %s" % size_desc)
            if units != 'mm':
                log.note("This scan area below in '%s' units may not be exact due to rounding errors." % units)

        if units == 'in':
            log.info("Scan area (in):")
            log.info("  Top left (x,y): (%fin, %fin)" % (tlx/25.4, tly/25.4))
            log.info("  Bottom right (x,y): (%fin, %fin)" % (brx/25.4, bry/25.4))
            log.info("  Width: %fin" % ((brx - tlx)/25.4))
            log.info("  Height: %fin" % ((bry - tly)/25.4))

        elif units == 'cm':
            log.info("Scan area (cm):")
            log.info("  Top left (x,y): (%fcm, %fcm)" % (tlx/10.0, tly/10.0))
            log.info("  Bottom right (x,y): (%fcm, %fcm)" % (brx/10.0, bry/10.0))
            log.info("  Width: %fcm" % ((brx - tlx)/10.0))
            log.info("  Height: %fcm" % ((bry - tly)/10.0))

        elif units == 'px':
            log.info("Scan area (px @ %ddpi):" % res)
            log.info("  Top left (x,y): (%fpx, %fpx)" % (tlx*res/25.4, tly*res/25.4))
            log.info("  Bottom right (x,y): (%fpx, %fpx)" % (brx*res/25.4, bry*res/25.4))
            log.info("  Width: %fpx" % ((brx - tlx)*res/25.4))
            log.info("  Height: %fpx" % ((bry - tly)*res/25.4))

        elif units == 'pt':
            log.info("Scan area (pt):")
            log.info("  Top left (x,y): (%fpt, %fpt)" % (tlx/0.3528, tly/0.3528))
            log.info("  Bottom right (x,y): (%fpt, %fpt)" % (brx/0.3528, bry/0.3528))
            log.info("  Width: %fpt" % ((brx - tlx)/0.3528))
            log.info("  Height: %fpt" % ((bry - tly)/0.3528))

        log.info("Destination(s): %s" % ', '.join(dest))

        if 'file' in dest:
            log.info("Output file: %s" % output)

        update_queue = Queue.Queue()
        event_queue = Queue.Queue()

        device.setOption("mode", scan_mode)
        device.setOption("resolution", res)
        
        if adf:
            try:
                device.setOption("source", "ADF")
                device.setOption("batch-scan", True)
            except scanext.error:
                log.error("Failed to set ADF mode. Does this device support ADF? Disabling ADF mode.")
                adf = False
        
        if not adf:
            try:
                device.setOption("source", "Auto")
                device.setOption("batch-scan", False)
            except scanext.error:
                log.debug("Error setting source or batch-scan option (this is probably OK).")

        log.info("\nWarming up...")
        
        no_docs = False
        page = 1
        adf_page_files = []
        #adf_pages = []
        
        cleanup_spinner()
        log.info("")

        try:
            while True:
                if adf:
                    log.info("\nPage %d: Scanning..." % page)
                else:
                    log.info("\nScanning...")
                    
                bytes_read = 0
     
                try:
                    try:
                        ok, expected_bytes, status = device.startScan("RGBA", update_queue, event_queue)
                    except scanext.error, e:
                        log.error(e)
                        sys.exit(1)
                    except KeyboardInterrupt:
                        log.error("Aborted.")
                        device.cancelScan()
                        sys.exit(1) 
                 
                    if adf and status == scanext.SANE_STATUS_NO_DOCS:
                        if page-1 == 0:
                            log.error("No document(s). Please load documents and try again.")
                            sys.exit(0)
                        else:    
                            log.info("Out of documents. Scanned %d pages total." % (page-1))
                            no_docs = True
                            break
                    
                    if adf:
                        log.info("Expecting to read %s from scanner (per page)." % utils.format_bytes(expected_bytes))
                    else:
                        log.info("Expecting to read %s from scanner." % utils.format_bytes(expected_bytes))
                    
                    device.waitForScanActive()
                    
                    pm = tui.ProgressMeter("Reading data:")
            
                    while device.isScanActive():
                        while update_queue.qsize():
                            try:
                                status, bytes_read = update_queue.get(0)
                                
                                #if log.get_level() >= log.LOG_LEVEL_INFO:
                                if not log.is_debug():
                                    pm.update(int(100*bytes_read/expected_bytes), 
                                        utils.format_bytes(bytes_read))
                                
    ##                            if status == scanext.SANE_STATUS_EOF:
    ##                                log.debug("EOF")
    ##                            elif status == scanext.SANE_STATUS_NO_DOCS:
    ##                                log.debug("NO DOCS")
    ##                                no_docs = True
                                #elif status != scanext.SANE_STATUS_GOOD:
                                if status != scanext.SANE_STATUS_GOOD:
                                    log.error("SANE error %d" % status)
                                    sys.exit(1)
            
                            except Queue.Empty:
                                break
            

                        time.sleep(0.5)
                        
                except KeyboardInterrupt:
                    log.error("Aborted.")
                    device.cancelScan()
                    sys.exit(1)
            
                # Make sure queue is cleared out...
                while update_queue.qsize():
                    status, bytes_read = update_queue.get(0)
                    
                    if not log.is_debug():
                        pm.update(int(100*bytes_read/expected_bytes), 
                            utils.format_bytes(bytes_read))
            
    ##                if status == scanext.SANE_STATUS_EOF:
    ##                    log.debug("EOF")
    ##                elif status == scanext.SANE_STATUS_NO_DOCS:
    ##                    log.debug("NO DOCS")
    ##                    no_docs = True

                    if status != scanext.SANE_STATUS_GOOD:
                        log.error("SANE error %d" % status)
                        sys.exit(1)
                    
                log.info("")
            
                if bytes_read:
                    log.info("Read %s from scanner." % utils.format_bytes(bytes_read))
            
                    buffer, format, format_name, pixels_per_line, \
                        lines, depth, bytes_per_line, pad_bytes, total_read = device.getScan()
            
                    if scan_mode in ('color', 'gray'):
                        try:
                            im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(), 
                                'raw', 'RGBA', 0, 1)
                        except ValueError:
                            log.error("Did not read enough data from scanner (I/O Error?)")
                            sys.exit(1)
                            
    ##                elif scan_mode == 'gray':
    ##                    im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(), 
    ##                        'raw', 'RGBA', 0, 1).convert('P')
            
                    elif scan_mode == 'lineart':
                        try:
                            im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(), 
                                'raw', 'RGBA', 0, 1).convert('L')
                        except ValueError:
                            log.error("Did not read enough data from scanner (I/O Error?)")
                            sys.exit(1)
                            
                    if adf:
                        temp_output = utils.createSequencedFilename("hpscan_pg%d_" % page, ".png")
                        adf_page_files.append(temp_output)
                        im.save(temp_output)  
                        log.debug("Saved page %d to file %s" % (page, temp_output))
                        #adf_pages.append(im)
                else:
                    log.error("No data read.")
                    sys.exit(1)

                if not adf or (adf and no_docs):
                    break
                    
                page += 1
            
        finally:
            log.info("Closing device.")
            device.cancelScan()
            #print "0"
            #device.freeScan()
            #sane.deInit()
                
        #if no_docs:
        #    sys.exit(0)
        
        #print "1"
        if adf:
            try:
                from reportlab.pdfgen import canvas
            except ImportError:
                log.error("PDF output requires ReportLab.")
                sys.exit(1)
            #print "2"
            #print canvas
            #print canvas.Canvas
            
            tlx_max = device.getOptionObj('tl-x').constraint[1]
            bry_max = device.getOptionObj('br-y').constraint[1]

            if not output:
                output = utils.createSequencedFilename("hpscan", ".pdf")
            
            c = canvas.Canvas(output, (tlx_max/0.3528, bry_max/0.3528))
            #print c
            
            #for p in adf_page_files:
            for p in adf_page_files:
                log.info("Processing page %s..." % p)
                
                image = Image.open(p)
                #print image
                
                try:
                    c.drawInlineImage(image, (tlx/0.3528), ((bry_max/0.3528)-(bry/0.3528)), 
                    
                    #c.drawInlineImage(image, 0, bry/0.3528,
                        width=None,height=None)
                except NameError:
                    log.error("A problem has occurred with PDF generation. This is a known bug in ReportLab. Please update your install of ReportLab to version 2.0 or greater.")
                    sys.exit(1)

                c.showPage()
                
            log.info("Saving to file %s" % output)
            c.save()
            log.info("Viewing PDF file in %s" % pdf_viewer)
            os.system("%s %s &" % (pdf_viewer, output))            
        
        
            sys.exit(0)
        
        if resize != 100:
            if resize < 1 or resize > 400:
                log.error("Resize parameter is incorrect. Resize must be 0% < resize < 400%.")
                log.error("Using resize value of 100%.")
            else:
                new_w = pixels_per_line * resize / 100
                new_h = lines * resize / 100
                log.info("Resizing from %dx%d to %dx%d..." % (pixels_per_line, lines, new_w, new_h))
                im = im.resize((new_w, new_h), Image.ANTIALIAS)

        file_saved = False
        if 'file' in dest:
            log.info("\nOutputting to destination 'file':")
            log.info("Saving to file %s" % output)

            try:
                im.save(output)
            except IOError, e:
                log.error("Error saving file: %s (I/O)" % e)
                try:
                    os.remove(output)
                except OSError:
                    pass
                sys.exit(1)
            except ValueError, e:
                log.error("Error saving file: %s (PIL)" % e)
                try:
                    os.remove(output)
                except OSError:
                    pass
                sys.exit(1)

            file_saved = True
            dest.remove("file")

        temp_saved = False
        if ('editor' in dest or 'viewer' in dest or 'email' in dest or 'printer' in dest) \
            and not file_saved:
            
            output_fd, output = utils.make_temp_file(suffix='.png')
            try:
                im.save(output)
            except IOError, e:
                log.error("Error saving temporary file: %s" % e)

                try:
                    os.remove(output)
                except OSError:
                    pass

                sys.exit(1)

            os.close(output_fd) 
            temp_saved = True      

        for d in dest:
            log.info("\nSending to destination '%s':" % d)

            if d == 'fax':
                log.error("fax: Not implemented yet.")

            elif d == 'pdf':
                try:
                    from reportlab.pdfgen import canvas
                except ImportError:
                    log.error("PDF output requires ReportLab.")
                    continue

                tlx_max = device.getOptionObj('tl-x').constraint[1]
                bry_max = device.getOptionObj('br-y').constraint[1]

                pdf_output = utils.createSequencedFilename("hpscan", ".pdf")
                c = canvas.Canvas(pdf_output, (tlx_max/0.3528, bry_max/0.3528))

                try:
                    c.drawInlineImage(im, (tlx/0.3528), ((bry_max/0.3528)-(bry/0.3528)), 
                        width=None,height=None)
                except NameError:
                    log.error("A problem has occurred with PDF generation. This is a known bug in ReportLab. Please update your install of ReportLab to version 2.0 or greater.")
                    continue

                c.showPage()
                log.info("Saving to file %s" % pdf_output)
                c.save()
                log.info("Viewing PDF file in %s" % pdf_viewer)
                os.system("%s %s &" % (pdf_viewer, pdf_output))

            elif d == 'printer':
                hp_print = utils.which("hp-print")
                if hp_print:
                    cmd = 'hp-print %s &' % output
                else:
                    cmd = "python ./print.py %s &" % output
                    
                os.system(cmd)

            elif d == 'email':
                try:
                    from email.mime.image import MIMEImage
                    from email.mime.multipart import MIMEMultipart
                    from email.mime.text import MIMEText
                except ImportError:
                    log.error("hp-scan email destination requires Python 2.2+.")
                    continue

                msg = MIMEMultipart()
                msg['Subject'] = email_subject
                msg['From'] = email_from
                msg['To'] = ','.join(email_to)
                msg.preamble = 'Scanned using hp-scan'

                if email_note:
                    txt = MIMEText(email_note)
                    msg.attach(txt)

                if file_saved:
                    txt = MIMEText("attached: %s: %dx%d %s PNG image." % 
                        (os.path.basename(output), pixels_per_line, lines, scan_mode))
                else:
                    txt = MIMEText("attached: %dx%d %s PNG image." % (pixels_per_line, lines, scan_mode))

                msg.attach(txt) 

                fp = open(output, 'r')
                img = MIMEImage(fp.read())
                fp.close()

                if file_saved:
                    img.add_header('Content-Disposition', 'attachment', filename=os.path.basename(output))

                msg.attach(img)

                sendmail = utils.which("sendmail")

                if sendmail:
                    sendmail = os.path.join(sendmail, 'sendmail')
                    cmd = [sendmail,'-t','-r',email_from]

                    log.debug(repr(cmd))
                    err = None
                    try:
                        sp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                        std_out, std_err = sp.communicate(msg.as_string())
                        if std_err != '':
                            err = std_err
                    except OSError, e:
                        err = str(e)
                    cleanup_spinner()

                    if err:
                        log.error(repr(err))

                else:
                    log.error("Mail send failed. 'sendmail' not found.")

            elif d == 'viewer':
                if viewer:
                    log.info("Viewing file in %s" % viewer)
                    os.system("%s %s &" % (viewer, output))
                else:
                    log.error("Viewer not found.")

            elif d == 'editor':
                if editor:
                    log.info("Editing file in %s" % editor)
                    os.system("%s %s &" % (editor, output))
                else:
                    log.error("Editor not found.")

        device.freeScan()
        sane.deInit()    
        
        
except KeyboardInterrupt:
    log.error("User exit")
    
log.info("")
log.info("Done.")

