# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, print_function, division,
                        absolute_import)

from collections import namedtuple
from math import floor

from PIL import Image, ImageDraw


# Rectangles are necessary
Rect = namedtuple('Rect', ['x', 'y', 'width', 'height'])

# As is padding
Padding = namedtuple('Padding', ['top', 'right', 'bottom', 'left'])

# And we'll add a useful default
NO_PADDING = Padding(0, 0, 0, 0)


def create_image(lines, size=None, padding=NO_PADDING, center=True,
                 line_color='#000', line_width=1, bg_color='#fff',
                 mode='RGB'):
    """Generate an image from lines generated by Signature Pad.

    :param lines: sequence of dictionaries as provided by Signature Pad
    :param size: desired size of the image; pass None to use the
                 original size
    :param padding: optional padding for the signature
    :param center: center the signature, defaults to True
    :param line_color: the color for the signature, defaults to black
    :param line_width: the width of the signature strokes, defaults to 1
    :param bg_color: background color, defaults to white
    :param mode: the image mode, defaults to RGB
    :returns: the PIL image

    Note that the padding works as in the default CSS box model: adding
    10 pixels of padding left and right, will enlarge the width of
    the image by 20 pixels.

    """
    # Calculate the bounding box
    box = bounding_box(lines)
    # Set up padding and size
    padding = padding if padding else NO_PADDING
    if size:
        size = pad(size, padding)
    else:
        size = pad((box.width, box.height), padding)
    # Initialize the image with a white background
    im = Image.new(mode, (box.width, box.height), bg_color)
    draw = ImageDraw.Draw(im)
    for line in lines:
        # Draw each line, taking the bounding box into account
        x = line['lx'] - box.x
        y = line['ly'] - box.y
        mx = line['mx'] - box.x
        my = line['my'] - box.y
        draw.line((x, y, mx, my), fill=line_color, width=line_width)
    # Calculate the desired width and height without padding
    width = size[0] - padding.left - padding.right
    height = size[1] - padding.top - padding.bottom
    desired_size = (width, height)
    current_size = (box.width, box.height)
    # Calculate the ratio to scale
    resize = proportional_resize(current_size, desired_size)
    # Scale the image
    im = im.resize(resize, Image.ANTIALIAS)
    # Now position it inside another image with the provided padding
    position = padding
    if center:
        left = (width - resize[0]) // 2
        left = left + padding.left
        top = (height - resize[1]) // 2
        top = top + padding.top
        position = Padding(top, padding.right, padding.bottom, left)
    scaled = Image.new(mode, size, bg_color)
    position = (position.left, position.top)
    scaled.paste(im, position)
    return scaled


def proportional_resize(current_size, desired_size):
    """Calculate correct size to maintain the proportions.

    :param current_size: two-tuple of width and height
    :param desired_size: two-tuple of desired width and height
    :returns: the correct size to maintain proportions

    For example, a propertional resize of (640,280) to (500,150) means
    a ratio of 0,5357142857 will be applied to (640, 280), resulting in
    a size of (500, 80).

    """
    width, height = current_size
    width, height = max(width, 1), max(height, 1)
    width_ratio = desired_size[0] / width
    height_ratio = desired_size[1] / height
    ratio = min(width_ratio, height_ratio)
    width, height = width * ratio, height * ratio
    return (int(floor(width)), int(floor(height)))


def bounding_box(lines):
    """Construct the bounding box for the lines.

    :param lines: lines as provided by Signature Pad
    :returns: Rect representing the bounding box

    """
    min_coords = [-1, -1]
    max_coords = [0, 0]
    first = True
    for line in lines:
        if first:
            first = False
            min_coords[0] = line['lx']
            min_coords[1] = line['ly']
        min_coords[0] = min(min_coords[0], line['lx'], line['mx'])
        min_coords[1] = min(min_coords[1], line['ly'], line['my'])
        max_coords[0] = max(max_coords[0], line['lx'], line['mx'])
        max_coords[1] = max(max_coords[1], line['ly'], line['my'])
    width = max_coords[0] - min_coords[0]
    height = max_coords[1] - min_coords[1]
    return Rect(min_coords[0], min_coords[1], width, height)


def pad(size, padding):
    """Apply padding to width and height.

    :param size: two-tuple of width and height
    :param padding: padding to apply to width and height
    :returns: two-tuple of width and height with padding applied

    """
    width = size[0] + padding.left + padding.right
    height = size[1] + padding.top + padding.bottom
    return (width, height)
