#!/usr/bin/env python
"""
libkoki marker generation script
"""
import cairo
import kokimarker
import argparse
from collections import namedtuple

FILENAME_FMT = "{prefix}-{codes}-{paper_size}paper.pdf"

PAPER_SIZES = {'A4': (210, 297),
               'A3': (297, 420)}

PaperDim = namedtuple('PaperDim', ['x', 'y'])

class RenderError(Exception):
    pass

def get_args():
    """Get command line arguments"""
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--4up', dest='nup',
                        action='store_const', const=4, default=1,
                        help="places 4 markers on a single page if possible, "
                        "going across then down")
    parser.add_argument('--desc', default="",
                        help="a line of text to place on the marker")
    parser.add_argument('--paper-size', default='A4', choices=PAPER_SIZES.keys(),
                        help="the paper size to use")
    parser.add_argument('--no-dot', action='store_true', default=False,
                        help="don't show the top-left dot")
    parser.add_argument('--no-text', action='store_true', default=False,
                        help="don't show any text on the marker")
    parser.add_argument('--no-outline', action='store_true', default=False,
                        help="don't draw the outline")
    parser.add_argument('--width', type=int, default=100,
                        help="width (in mm) of marker to be produced")
    parser.add_argument('--prefix', default='marker',
                        help="the prefix to save the PDF under ($PREFIX-$CODE-$PAPERSIZE.pdf)")

    parser.add_argument('code', type=int, nargs='+',
                        help="""The code(s) to generate a marker for.
    If only a single code is provided, then this will occupy all the available
    places on the page. If more than one code is provided, then there must be
    enough spaces on the page to place all the markers.

    Practically, that means that only up to 4 markers can be specified at once,
    though the user must specify --4up to enable this.""")

    return parser.parse_args()


def mm_to_pt(x):
    """Convert millimeters to points"""
    return x * 2.8346278


def cairo_surface(filename, width, height):
    """Create a configured Cairo surface"""
    surface = cairo.PDFSurface(filename, width, height)
    return surface


def cairo_context(surface):
    """Configure a Cairo surface and produce a context"""
    context = cairo.Context(surface)
    context.scale(mm_to_pt(1), mm_to_pt(1))
    return context


_NUP_HANDLERS = {}
def nup_handler(n):
    """Decorator for nicely registering N-up handlers"""
    def wrap(f):
        """Register handler"""
        _NUP_HANDLERS[n] = f
    return wrap


def main():
    """Render marker(s)"""
    options = get_args()
    dims = PaperDim(*[mm_to_pt(x) for x in PAPER_SIZES[options.paper_size]])
    page_width, page_height = PAPER_SIZES[options.paper_size]
    filename = FILENAME_FMT.format(prefix=options.prefix,
                                   codes='-'.join("{:03d}".format(code) for code in options.code),
                                   paper_size=options.paper_size)

    # prepare cairo
    surface = cairo_surface(filename, *dims)
    context = cairo_context(surface)

    # function to manage rendering
    def _render(marker, offset_x, offset_y):
        """Inner function to render with the given options"""
        marker.render(context, options.width, offset_x, offset_y,
                      desc=options.desc, show_text=not options.no_text,
                      corner_dot=not options.no_dot,
                      outline=not options.no_outline)

    handler = _NUP_HANDLERS.get(options.nup, None)
    positions = list(handler(page_width, page_height, options.width))
    codes = list(options.code)

    if len(positions) < len(codes):
        # it might be nice if we supported multi-page output at some point..
        exit("Cannot fit {0} markers into {1} space(s)!".format(len(options.code), len(positions)))

    # Maintain existing behaviour for single-code arguments
    if len(codes) == 1 and len(positions) > 1:
        codes = len(positions) * codes

    # Render the markers
    try:
        for code, (x, y) in zip(codes, positions):
            marker = kokimarker.Marker(code)
            _render(marker, x, y)
    except RenderError as render_error:
        print render_error
        exit(1)

    # write output
    surface.finish()


@nup_handler(1)
def _1up(page_width, page_height, width):
    """1 marker in the middle"""

    if width > page_width or width > page_height:
        raise RenderError("Page not big enough")

    yield ((page_width - width) / 2,
           (page_height - width) / 2)


@nup_handler(4)
def _4up(page_width, page_height, width):
    """4 markers in a 2x2 grid"""
    padding = 0
    p_2 = padding / 2.0
    tl_offset_x = page_width / 2 - width - p_2
    tl_offset_y = page_height / 2 - width - p_2

    if width * 2 + padding > page_width or width * 2 + padding > page_height:
        raise RenderError("Page not big enough")

    yield (tl_offset_x, tl_offset_y)
    yield (tl_offset_x + p_2 + width, tl_offset_y)
    yield (tl_offset_x, tl_offset_y + p_2 + width)
    yield (tl_offset_x + p_2 + width, tl_offset_y + p_2 + width)


# render marker(s)
main()
