import re, os
from math import atan, degrees
from PIL import Image, ImageChops, ImageFilter, ImageDraw, ImageFont

from django.conf import settings

from urlimaging.validator import *


TEN_MBS = 10 * 1024 * 1024 * 1024
RGB_ONLY_FORMATS = set(['.jpg', '.jpeg'])


def with_image(fn):
	""" Decorator that handles the open and closing of an image.  """
	def process(*args):
		img = fn(Image.open(args[0]), *args[1:])

		ext = os.path.splitext(args[0])[1].lower()
		if ext in RGB_ONLY_FORMATS and img.mode != 'RGB':
			img = img.convert('RGB')
		elif ext == '.png' and img.mode in ('P', 'I', 'RGB'):
			img = img.convert('RGBA').convert('P', palette=Image.ADAPTIVE)

		img.save(args[0], quality=95, **img.info)

	return process


@with_image
def resize(img, width, height):
	width, height = int(width), int(height)
	validate_width_height(width, height)
	return img.resize((int(width), int(height)), Image.ANTIALIAS)


@with_image
def scale(img, percent):
	percent = int(percent)
	validate_percent(percent)

	p = percent * 0.01

	w, h = img.size
	return img.resize((int(w * p), int(h * p)), Image.ANTIALIAS)


@with_image
def fit(img, width, height):
	img.thumbnail((int(width), int(height)), Image.ANTIALIAS)
	return img


@with_image
def zoom(img, width, height):
	w, h = img.size

	if w > h:
		w = int(width)
		h = int(float(h) * (float(width) / float(w)))
		img = img.resize((w, h), Image.ANTIALIAS)
		to_crop = int((h - height) / 2.0)

		return img.crop((0, to_crop, width, height + to_crop))
	else:
		w = int(float(w) * (float(height) / float(h)))
		h = int(height)
		img = img.resize((w, h), Image.ANTIALIAS) 
		to_crop = int((w - width) / 2.0)

		return img.crop((to_crop, 0, width + to_crop, height))


@with_image
def width(img, width):
	w, h = img.size
	return img.resize((int(width), int(float(h) * (float(width) / float(w)))), Image.ANTIALIAS)


@with_image
def height(img, height):
	w, h = img.size
	return img.resize((int(float(w) * (float(height) / float(h))), int(height)), Image.ANTIALIAS)


@with_image
def square(img, size):
	size = int(size)
	validate_width_height(size, size)

	width, height = img.size

	half_size = size / 2.0
	# already square?
	if width == height:
		img = img.resize((size, size), Image.ANTIALIAS)
		return img
	elif width > height:
		# resize proportionally to get height == size
		resize_amount = float(size) / float(height)
		width = int(width * resize_amount)
		img = img.resize((width, size), Image.ANTIALIAS)

		return img.crop((int(width / 2.0 - half_size), 0, int(width / 2.0 + half_size), size))
	else:
		# get width == size
		resize_amount = float(size) / float(width)
		height = int(height * resize_amount)
		img = img.resize((size, height), Image.ANTIALIAS)

		return img.crop((0, int(height / 2.0 - half_size), size, int(height / 2.0 + half_size)))


@with_image
def crop(img, x, y, width, height):
	x, y, width, height = int(x), int(y), int(width), int(height)
	validate_x_y(x, y)
	validate_width_height(width, height)

	return img.crop((x, y, x + width, y + height))


@with_image
def rotate(img, degrees):
	return img.rotate(-int(degrees))


@with_image
def thumbnail(img, size):
	THUMBNAIL_SIZES = { 
		'small': 64,
		'medium': 96,
		'large': 128,
	}

	if size.isdigit():
		s = int(size)
	elif size in THUMBNAIL_SIZES:
		s = THUMBNAIL_SIZES[size]
	else:
		s = THUMBNAIL_SIZES['medium']

	img.thumbnail((s, s), Image.ANTIALIAS)
	return img


@with_image
def black_and_white(img):
	return img.convert('L')


@with_image
def invert(img):
	return ImageChops.invert(img)


@with_image
def blur(img):
	return img.filter(ImageFilter.BLUR)

@with_image
def sharpen(img):
	return img.filter(ImageFilter.SHARPEN)


def convert(filename, format):
	img = Image.open(filename)
	if img.mode != 'RGB':
		img = img.convert('RGB')

	# for some reason this chokes on 'JPG'
	format = format.upper()
	if format == 'JPG':
		format = 'JPEG'

	img.save(filename + '.' + format, format, quality=95)


@with_image
def watermark(img, text):
	if img.mode != 'RGB':
		img = img.convert('RGB')

	watermark = Image.new('RGBA', (img.size[0], img.size[1]))
	draw = ImageDraw.ImageDraw(watermark, 'RGBA')

	size = 0
	while True:
		size += 1
		nextFont = ImageFont.truetype(settings.FONT_PATH, size)
		nextTextWidth, nextTextHeight = nextFont.getsize(text)
		if nextTextWidth + nextTextHeight / 3 > watermark.size[0]:
			break
		font = nextFont
		textWidth, textHeight = nextTextWidth, nextTextHeight
	
	draw.text(((watermark.size[0] - textWidth) / 2,
			(watermark.size[1] - textHeight) / 2), text, font=font, fill=(255,255,255))

	watermark = watermark.rotate(degrees(atan(float(img.size[1]) /
					img.size[0])), Image.BICUBIC)
	mask = watermark.convert('L').point(lambda x: min(x, 55))
	watermark.putalpha(mask)
	img.paste(watermark, None, watermark)

	return img


HTML_COLORS = { 
	'aqua': (0, 255, 255),
	'cyan': (0, 255, 255),
	'black': (0, 0, 0),
	'blue': (0, 0, 255),
	'fuchsia': (255, 0, 255),
	'magenta': (255, 0, 255),
	'gray': (128, 128, 128),
	'grey': (128, 128, 128),
	'green': (0, 128, 0),
	'lime': (0, 255, 0),
	'maroon': (128, 0, 0),
	'navy': (0, 0, 128),
	'olive': (128, 128, 0),
	'purple': (128, 0, 128),
	'red': (255, 0, 0),
	'silver': (192, 192, 192),
	'teal': (0, 128, 128),
	'white': (255, 255, 255),
	'yellow': (255, 255, 0),
}

def color_to_rgb(color):
	try:
		return HTML_COLORS[color.lower()]
	except KeyError:
		return None

def hex_to_decimal(h):
	return int(h, 16)

def hex_to_rgb(hex):
	""" Convert a hexadecimal triplet to an RGB tuple """

	if hex.startswith("#"):
		hex = hex[1:]

	try:
		if len(hex) == 3:
			hex_triplet = map(lambda x: "%s%s" % (x, x), tuple(hex))
			return tuple(map(hex_to_decimal, hex_triplet))
		elif len(hex) == 6:
			return tuple(map(hex_to_decimal, (hex[0:2], hex[2:4], hex[4:6])))
	except ValueError:
		pass

	return None


@with_image
def background(img, color):
	rgb = hex_to_rgb(color)
	if not rgb: rgb = color_to_rgb(color)
	if not rgb: return None

	img = img.convert('RGBA')
	overlayed = Image.new('RGBA', img.size)
	overlayed.paste(rgb)
	overlayed.paste(img, mask=img)

	return overlayed


COMMANDS = [{ 
		'regex': re.compile(r'^resize/(\d+)x(\d+)/', re.I),
		'fn': resize, 
		'title': 'Resize',
		'anchor_name': 'resize',
		'description': 'Resize an image.',
		'format': '<tt>/resize/WIDTHxHEIGHT/IMAGEURL</tt>',
		'arguments': [ 
			('WIDTHxHEIGHT', 'The width and height of the desired image. Expressed as a width and height specification of the form WIDTHxHEIGHT where WIDTH and HEIGHT are both positive numbers.') 
		],
	}, {
		'regex': re.compile(r'^scale/(\d+)/', re.I),
		'fn': scale, 
		'title': 'Scale',
		'anchor_name': 'scale',
		'description': 'Scale an image proportionally by a given percentage',
		'format': '<tt>/scale/PERCENT/IMAGEURL</tt>',
		'arguments': [ 
			('PERCENT', 'A value between 0 and 1000 by which to scale the image.  If greater than 100, the image will be increased in size by that percentage amount.') 
		],
	}, {
		'regex': re.compile(r'^width/(\d+)/', re.I),
		'fn': width, 
		'title': 'Width',
		'anchor_name': 'width',
		'description': 'Scale an image proportionaly to the given width',
		'format': '<tt>/width/SIZE/IMAGEURL</tt>',
		'arguments': [ ('SIZE', 'A positive number specifying the target width of the image.') ],
	}, {
		'regex': re.compile(r'^height/(\d+)/', re.I),
		'fn': height, 
		'title': 'Height',
		'anchor_name': 'height',
		'description': 'Scale an image proportionaly to the given height',
		'format': '<tt>/height/SIZE/IMAGEURL</tt>',
		'arguments': [ 
			('SIZE', 'A positive number specifying the target height of the image.') 
		],
	}, {
		'regex': re.compile(r'^fit/(\d+)x(\d+)/', re.I),
		'fn': fit, 
		'title': 'Fit',
		'anchor_name': 'fit',
		'description': 'Fit an image proportionally to be within a given size',
		'format': '<tt>/fit/WIDTHxHEIGHT/IMAGEURL</tt>',
		'arguments': [ 
			('WIDTHxHEIGHT', 'The box that the image must be resized to fit into. Expressed as a width and height specification of the form WIDTHxHEIGHT where WIDTH and HEIGHT are both positive numbers.') 
		],
	}, {
		'regex': re.compile(r'^zoom/(\d+)x(\d+)/', re.I),
		'fn': zoom, 
		'title': 'Zoom',
		'anchor_name': 'zoom',
		'description': 'Similar to fit, but will zoom in on the image to get one side matching as best as possible.  If the image is wider than tall, it will do the equivalent of /height/HEIGHT/ otherwise it will do the equivalent of /width/WIDTH/',
		'format': '<tt>/zoom/WIDTHxHEIGHT/IMAGEURL</tt>',
		'arguments': [ 
			('WIDTHxHEIGHT', 'The box that the image must be zoomed to fit into. Expressed as a width and height specification of the form WIDTHxHEIGHT where WIDTH and HEIGHT are both positive numbers.') 
		],
	}, {
		'regex': re.compile(r'^thumb(?:nail)?/(small|medium|large|\d+)/', re.I),
		'fn': thumbnail, 
		'title': 'Thumbnail',
		'anchor_name': 'thumbnail',
		'description': 'Create a thumbnail of an image',
		'format': """<tt>/thumbnail/SIZE/IMAGEURL</tt>
				or a shorter version:
			     <tt>/thumb/SIZE/IMAGEURL</tt>""",
		'arguments': [ ('SIZE', 'A number of pixels or a value of either "small" (64 pixels), "medium" (96 pixels) or "large" (128).') ],
	}, {
		'regex': re.compile(r'^square/(\d+)/', re.I),
		'fn': square, 
		'title': 'Square',
		'anchor_name': 'square',
		'description': 'Create a squared version of an image.  This will resize and crop an image to create a squared version with the height and width given.',
		'format': '<tt>/square/SIZE/IMAGEURL</tt>',
		'arguments': [ 
			('SIZE', 'The height and width of the desired square'), 
		],
	}, {
		'regex': re.compile(r'^rotate/(-?\d+)/', re.I),
		'fn': rotate, 
		'title': 'Rotate',
		'anchor_name': 'rotate',
		'description': 'Rotate an image',
		'format': '<tt>/rotate/DEGREES/IMAGEURL</tt>',
		'arguments': [ 
			('DEGREES', 'A value specifying the number of degrees that the image is to be rotated in a clockwise direction.  A negative number will rotate the image in a counter-clockwise direction.') 
		],
	}, {
		'regex': re.compile(r'^crop/(\d+),(\d+),(\d+)x(\d+)/', re.I),
		'fn': crop, 
		'title': 'Crop',
		'anchor_name': 'crop',
		'description': 'Select a rectangular region of an image and remove everything outside of that region.',
		'format': '<tt>/crop/CORNER-X,CORNER-Y,WIDTHxHEIGHT/IMAGEURL</tt>',
		'arguments': [ 
			('CORNER-X', 'The x coordinate of the top left corner of the rectangle to be cropped. Assumes that the top left corner is (0, 0).'),
			('CORNER-Y', 'The y coordinate of the top left corner of the rectangle to be cropped.'),
			('WIDTH', 'The width of the rectangle to be cropped.'),
			('HEIGHT', 'The height of the rectangle to be cropped.'),
		],
	}, {
		'regex': re.compile(r'^(?:watermark|wm)/([^/]+)/', re.I),
		'fn': watermark, 
		'title': 'Watermark',
		'anchor_name': 'watermark',
		'description': 'Overlay some text on the image',
		'format': '<tt>/wm/TEXT/IMAGEURL</tt> or <tt>/watermark/TEXT/IMAGEURL</tt>',
		'arguments': [ 
			('TEXT', 'The text of the watermark.'),
		],
		'note': 'It is recommended that after generating the watermarked image, you remove the original image.  Otherwise it will continue to be publicly available.'
	}, {
		'regex': re.compile(r'^(?:blackwhite|bw)/', re.I),
		'fn': black_and_white, 
		'title': 'Black & White',
		'anchor_name': 'blackwhite',
		'description': 'Convert the image to black and white',
		'format': '<tt>/bw/IMAGEURL</tt> or <tt>/blackwhite/IMAGEURL</tt>',
		'arguments': [ ],
	}, {
		'regex': re.compile(r'^invert/', re.I),
		'fn': invert, 
		'title': 'Invert',
		'anchor_name': 'invert',
		'description': 'Invert the color profile of the image',
		'format': '<tt>/invert/IMAGEURL</tt>',
		'arguments': [ ],
	}, {
		'regex': re.compile(r'^blur/', re.I),
		'fn': blur, 
		'title': 'Blur',
		'anchor_name': 'blur',
		'description': 'Apply a blurring filter to the image',
		'format': '<tt>/blur/IMAGEURL</tt>',
		'arguments': [ ],
	}, {
		'regex': re.compile(r'^sharpen/', re.I),
		'fn': sharpen, 
		'title': 'Sharpen',
		'anchor_name': 'sharpen',
		'description': 'Apply a sharpening filter to the image',
		'format': '<tt>/sharpen/IMAGEURL</tt>',
		'arguments': [ ],
	}, {
		'regex': re.compile(r'^convert/\.?(bmp|gif|im|jpe?g|msp|pcx|pdf|png|ppm|tiff|xbm)/', re.I),
		'fn': convert, 
		'title': 'Convert',
		'anchor_name': 'convert',
		'description': 'Convert the image to a different format',
		'format': '<tt>/convert/FORMAT/IMAGEURL</tt>',
		'arguments': [ 
			('FORMAT', 'The desired image format of the created image.  Allowable values are: bmp, gif, im, jpeg/jpg, msp, pcx, pdf, png, ppm, tiff, xbm')
		],
	}, {
		'regex': re.compile(r'^background/(\w+)/', re.I),
		'fn': background, 
		'title': 'Background',
		'anchor_name': 'background',
		'description': 'Set a custom background color.  The image must support transparency.',
		'format': '<tt>http://urlimg.com/background/FORMAT/IMAGEURL</tt>',
		'arguments': [ 
			('FORMAT', 'Either a color (red, green, blue, etc..) or a hexadecimal triplet (#FF00FF).')
		],
	}, 
]

