#!/usr/bin/python
# -*- encoding: utf-8 -*-
#canon_wrapper.py
"""
.. codeauthor:: Daniel STAN <dstan@crans.org>
.. codeauthor:: Antoine Durand-Gasselin <adg@crans.org>

Pour envoyer des jobs d'impression à l'imprimante canon via son interface web.

License: GPLv3
"""

import requests #pip install requests
# See:
# http://docs.python-requests.org/en/latest/user/quickstart/#post-a-multipart-encoded-file
import os, os.path, sys
import time
import argparse
import types
import tempfile, atexit
import subprocess
from syslog import syslog, openlog


BASE_URL = "http://imprimante.adm.crans.org"
URIs = {
    'root': '/',
    'ended_jobs_csv': '/pprint.csv?Flag=Csv_Data&LogType=0&Dummy=%(timestamp)s',
    'current_jobs': '/jpl.cgi?Flag=Init_Data&CorePGTAG=6&Dummy=%(timestamp)s',
    'print': '/ppdf.cgi?Type=PDF&Dummy=%(timestamp)s',
    'do_print': '/pprint.cgi',
}

STAPLE_POSITIONS={"TopLeft":"5", "TopRight":"6", "BottomLeft":"7",
                    "BottomRight":"8", "Top":"1", "Bottom":"2", "Left":"3",
                    "Right":"4", "None":"0", "Book":"9",}

OPTIONS = {
    "Url": "http://",
    "Mode": 100,
    "ManualNo": 0,
    "DocPasswd": "",
    "WebUserName": '',
    "WebPasswd": '',
    "PageMode": {'all': 0, 'range': 1, 'default': 0},
    "StartPage": 1,
    "EndPage": 1,
    "Copies": lambda args: args.copies,
    "MediaSize": lambda args: {
         'auto': 0, 'default': 0,
         "a4": 5, "a3": 6, "a5": 16, "b4": 12, "b5": 13,
         "ltr": 1, "lgl": 2, "11x17": 3, "exec": 4, "com-10": 7,
         "monarch": 8, "c5 env": 9, "b5 env": 10, "dl env": 11,
        }.get(args.page_size.lower()),
    "MediaType": 0, # TODO
    "ManualFeed": 0,
    "FitSize": lambda args: {'no': 0, 'yes': 1, 'default': 1}.get(args.expand),
                 #expand to mediasize
    "DuplexType": lambda args: 
        {'book': 0, 'one-sided': 0, 'short-edge': 1,
         'long-edge': 2}.get(args.duplex_type),
            # NB: book => one-sided (sinon, ça peut merder)
    "Sort": {'none': 0, 'finition': 1, 'default': 1},
        #finition: pour tout ce qui a une finition
        # TODO
    "PunchPos": 0,
    "StapleType": lambda args: STAPLE_POSITIONS['Book'] if args.duplex_type == 'book'
                         else STAPLE_POSITIONS[args.staple_position],
    "BookType": lambda args: 0 if args.duplex_type == 'book' else 2,
    "Annotation": 2,
    "ColorMode": lambda args: {'yes': 2, 'no': 1}.get(args.colors),
    "C_Render": 0, "C_RGB_Pro": 1, "C_CMYK_Pro": 4, "C_OUT_Pro": 1,
    "C_GRAY_Pro": 1, "C_Matching": 0,
    "SPOT_Color": 1,
    "C_Pure_B": 1, "C_B_OVPrn": 1, "C_Bright": 100, "C_Gray_Com": 1,
    "C_OVR_EFF": 1,
    "WidePrn": 0,
    "NupPrint": 0, "NupStart": 0,
    "Resolution": 1,
    "HalfToneTxt": 1, "HalfToneGrp": 1, "HalfToneImg": 1,
    "AstIntensity": 2, "AstText": 0, "AstGrap": 1, "AstImag": 1,
    "StoreBox": lambda args: int(args.test),
    "BoxNo": 17,
    "RGBDLName": "",
    "CMYKDLName": "",
    "OUTDLName": "",
    "BOXName": lambda args: args.jobname + '_%d' % int(time.time()),
    "Flag": "Exec_Data_Pdf",
    "Dummy": lambda _: int(time.time()),
    "Direct": 100,
}

def build_options(args):
    opt = {}
    for (k,v) in OPTIONS.iteritems():
        if isinstance(v, types.FunctionType):
            v = v(args)
        if isinstance(v, dict):
            v = v['default']
        if not isinstance(v, file):
            v = str(v)
        opt[k] = v
    return opt


def build_url(name):
    """ Build url from a page name (see URIs) """
    return (BASE_URL + URIs[name]) % {
        'timestamp': int(time.time()),
    }


def range_checker(a, b):
    """Type function for a given range"""
    def cast(x):
        try:
            v = int(x)
        except ValueError(err):
            raise argparse.ArgumentTypeError(str(err))
        if v not in xrange(a, b):
            raise argparse.ArgumentTypeError("should be between %d and %d" %  (a, b))
        return v
    return cast

def build_parser():
    """Make argparse object"""
    parser = argparse.ArgumentParser(
        description="HTTP printing driver for irc3580, with compatibility mode.",
        epilog="logs are sent to syslog (eg /var/log/canon_wrapper.log)",
        prog='canon_wrapper',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    parser.add_argument('--jobname', help="job name as seen by the printer", 
        default="unamed job",
    )
    parser.add_argument('--copies', default=1, 
        help="number of copies",
        type=range_checker(1,1000),
    )
    parser.add_argument('--colors', type=str, default='yes', const="yes", 
        nargs="?", 
        choices=["yes", "no"],
    )
    parser.add_argument('--expand', type=str, default='yes', const="yes", 
        nargs="?", 
        choices=["yes", "no"],
        help="expand to page size",
    )
    parser.add_argument('--duplex-type', type=str, default='one-sided',
        choices=["one-sided", "short-edge", "long-edge",
                 "book"],
    )
    parser.add_argument('--page-size', type=str, default='A4',
        choices=["A3", "A4"],
        help="page size",
    )
    parser.add_argument('--test', default=False, const=True,
        action='store_const',
        help="Don't really print, send to box folder",
    )
    parser.add_argument('--staple-position', default='None', 
        choices=STAPLE_POSITIONS.keys(),
        help="Where to staple",
    )
    parser.add_argument('filename')
    return parser

def do_print(args):
    """Effectively compatibilize then print the file"""
    opt=build_options(args)
    syslog('Options: %s' % repr(opt))
    #syslog('Starting ghostscript compat, piped mode')

    temppdf = tempfile.NamedTemporaryFile(suffix='.pdf')
    syslog('Starting ghostscript compat to %s' % temppdf.name)
    atexit.register(temppdf.close)
    proc = subprocess.Popen(['/usr/bin/gs','-dCompatibilityLevel=1.2',
        '-dBATCH','-dNOPAUSE','-sDEVICE=pdfwrite',
        '-sOutputFile=' + temppdf.name,
        args.filename])
    #    '-sOutputFile=%stdout%', '-q',
    #    args.filename], stdout=subprocess.PIPE)
    ret = proc.wait()
    syslog('ghostscript compat finished with ret=%d' % ret)
    if ret != 0:
        syslog('aborting.')
        return ret
    syslog('Getting cookie...')
    cook = requests.get(build_url('root')).cookies
    syslog('Sending pdf (%d bytes)...' % os.stat(temppdf.name).st_size)
    temppdf.seek(0)
    filename = args.jobname
    if not filename.endswith('.pdf'):
        filename += '.pdf'
    req = requests.post(build_url('do_print'), cookies=cook, data=opt,
        files={'File': (filename, temppdf)})
        #files={'File': (args.jobname + '.pdf', proc.stdout)})
    temppdf.close()
    if "ms_err.gif" in req.text:
        syslog("Printer side ERROR !")
        return 42
    syslog('Compatibilized pdf successfully sent. Continue tracking on the printer !')
    return 0

if __name__ == '__main__':
    parser = build_parser()
    args = parser.parse_args()
    if args:
        try:
            openlog('canon_wrapper[%s]' % args.jobname)
            sys.exit(do_print(args))
        except Exception as e:
            syslog('Caught exception %s' % repr(e))
            raise

else:
    parser = build_parser()
    __doc__ += '''\nHelp message : ::\n\n'''
    __doc__ += '\n'.join('   ' + l for l in parser.format_help().split('\n')) + "\n"
