#!/usr/bin/env python

import argparse
from tempfile import mkdtemp
import os, shutil, commands, itertools
import sys

def parseargs():
	# argparse was new in python 2.7
	parser = argparse.ArgumentParser(description='DIY Books from PDFs')

	parser.add_argument('--paper', type=paper, default='a4', help='a4 | letter (Default is a4)')
	parser.add_argument('--type', type=btype, default='auto', help='auto | chapbook | perfect (Default is auto)')
	parser.add_argument('--trim', type=trim, default='even-odd', help='even-odd | document | per-page (Default is even-odd)')
	parser.add_argument('--print', dest="printfile", action='store_true', default=False, help='send result to default printer')
	parser.add_argument('--printer', help='print result to specific printer (implies --print)')
	parser.add_argument('input', help='PDF to convert into a book')
	parser.add_argument('output', help='Where to store the new book', default=None, nargs='?')

	return parser.parse_args()

def getoutput(cmd):
	status, output = commands.getstatusoutput(cmd)
	if status:
		raise Exception("Error when executing: %s\n%s" % (cmd, output))
	return output

# validates book type selection
def btype(string):
	string = string.lower()
	if string not in ['auto', 'chapbook', 'perfect']:
		raise argparse.ArgumentTypeError("must be auto, chapbook, or perfect.")
	return string

# validates paper selection
def paper(string):
	string = string.lower()
	if string not in ['a4', 'letter']:
		raise argparse.ArgumentTypeError("must be a4 or letter.")
	return string

def trim(string):
	string = string.lower()
	if string not in ['even-odd', 'document', 'per-page']:
		raise argparse.ArgumentTypeError("must be even-odd, document, or per-page.")
	return string

def calc_bbox(boxes):
	# consume all boxes element by element and calculate min/max of each row
	return list(itertools.imap(min, *boxes))[:2] + list(itertools.imap(max, *boxes))[2:]

def isEmptyPage(bbox):
	return bbox == [0.0, 0.0, 0.0, 0.0]

def main():
	args = parseargs()
	if args.printer:
		args.printfile = True
	if not args.output and not args.printfile:
		print >>sys.stderr, "Must either specify output file or print directive"
		sys.exit(-1)

	tmpdir = mkdtemp()
	shutil.copyfile(args.input, os.path.join(tmpdir, 'input.pdf'))

	wd = os.getcwd()
	os.chdir(tmpdir)

	print 'Inspecting PDF'
	boundingboxes = getoutput('gs -sDEVICE=bbox -dSAFER -dPARANOIDSAFER -dNOPAUSE -dBATCH input.pdf 2<&1|grep %%HiResBoundingBox').splitlines()
	npages = len(boundingboxes)
	
	if args.type == 'auto':
		if npages <= 24:
			args.type = 'chapbook'
		else:
			args.type = 'perfect'
	types = {'chapbook':'chapbook', 'perfect':'perfect bound book'}
	print 'Creating a PDF for a %s on %s paper' % (types[args.type], args.paper)

	# drop %BoundingBox field and convert rest to float
	boxes = [map(float, bboxline.split()[1:]) for bboxline in boundingboxes]
	if(args.trim == 'even-odd'):
		# compound bounding box of all non-empty odd/even pages
		even_box = calc_bbox(filter(lambda bbox: not isEmptyPage(bbox), boxes[1::2])) 
		odd_box = calc_bbox(filter(lambda bbox: not isEmptyPage(bbox), boxes[0::2]))
	elif (args.trim == 'document'):
		# compound bounding box of all non-empty pages
		documentbox = calc_bbox(filter(lambda bbox: not isEmptyPage(bbox), boxes))
	# Nothing to do for per-page

	print 'Trimming Pages'
	# --bbox "--bbox "<left> <bottom> <right> <top>""
	if (args.trim == 'even-odd'):
		crop_str = '--bbox-even "%f %f %f %f" --bbox-odd "%f %f %f %f"' % tuple(even_box + odd_box)
	elif (args.trim == 'document'):
		crop_str = '--bbox "%f %f %f %f"' % tuple(documentbox)
	else: # per-page
		crop_str = ""
	getoutput('pdfcrop --restricted %s input.pdf cropped.pdf' % crop_str)
	
	print 'Imposing Pages'
	intermediate_tex = os.path.join(tmpdir, 'intermediate.tex')
	intermediate_pdf = os.path.join(tmpdir, 'intermediate.pdf')
	intermediate = open(intermediate_tex, 'w')

	papersizes = {'a4':'\setuppapersize[A5][A4,landscape]', 'letter':"\definepapersize[halfletter][width=5.5in,height=8.5in]\n\setuppapersize[halfletter][letter,landscape]"}
	intermediate.write("%s\n" % papersizes[args.paper])

	typesetups = {'chapbook':'\setuparranging[2UP,doublesided]', 'perfect':'\setuparranging[2*2,doublesided]'}
	intermediate.write("%s\n" % typesetups[args.type])

	intermediate.write('''
\setuplayout[backspace=12.7mm,rightmargin=5mm,width=fit,leftmargindistance=0mm,rightmargindistance=0mm,
	header=0mm,footer=\\baselineskip,topspace=5mm,height=middle]
\setuppagenumbering[location=marginedge,alternative=doublesided]
\starttext
''')

	for page in xrange(1, npages + 1):
		intermediate.write('\externalfigure[cropped.pdf][page=%d, factor=fit]\n' % page)

	intermediate.write('\n\stoptext\n')
	intermediate.close()

	getoutput('context --paranoid --batchmode --noconsole %s' % intermediate_tex)

	if args.printfile:
		print 'Sending document to printer'
		printeropt = '-d %r' % args.printer if args.printer else ''
		getoutput("pdf2ps %s - | lp %s -o sides=two-sided-long-edge -" % (intermediate_pdf, printeropt))
	
	os.chdir(wd)
	if args.output:
		shutil.move(intermediate_pdf, args.output)

	# cleanup
	shutil.rmtree(tmpdir)

	print 'Done'

if __name__ == '__main__':
	main()