"""
Provides tools (currently: only functions, not a real service) for image
processing.
"""
from __future__ import absolute_import, division, print_function

import hashlib
from cStringIO import StringIO

from future.utils import string_types
from PIL import Image

__all__ = ['resize', 'RESIZE_MODES', 'SCALE', 'FIT', 'CROP']

# resize modes

#: resize without retaining original proportions
SCALE = 'scale'

#: resize image and retain original proportions. Image width and height will be
#: at most specified width and height, respectively; At least width or height
#: will be equal to specified width and height, respectively.
FIT = 'fit'

#: crop image and resize so that it matches specified width and height.
CROP = 'crop'

RESIZE_MODES = frozenset({SCALE, FIT, CROP})


# TODO: cache to file
cache = {}


def open_image(img):
  if isinstance(img, string_types):
    img = StringIO(img)

  pos = img.tell()
  image = Image.open(img)
  img.seek(pos)
  return image


def get_format(img):
  image = open_image(img)
  return image.format


def get_size(img):
  image = open_image(img)
  return image.size


def get_save_format(fmt):
  if fmt in ('GIF', 'PNG'):
    return 'PNG'
  return 'JPEG'


def resize(orig, width, height, mode=FIT):
  """
  """
  if isinstance(orig, string_types):
    orig = StringIO(orig)

  digest = hashlib.md5(orig.read()).digest()
  cache_key = (digest, mode, width, height)
  if cache_key in cache:
    return cache[cache_key]

  orig.seek(0)
  image = open_image(orig)
  format = image.format
  x, y = image.size

  if (x, y) == (width, height):
    orig.seek(0)
    return orig.read()

  if mode is SCALE:
    image = image.resize((width, height), Image.LANCZOS)
    assert image.size == (width, height)
  elif mode is FIT:
    if (x >= width or y >= height):
      # resize only if images exceed desired dimensions
      image.thumbnail((width, height), Image.LANCZOS)
      x1, y1 = image.size
      assert x1 == width or y1 == height
  elif mode is CROP:
    image = _crop_and_resize(image, width, height)
    assert image.size == (width, height)

  output = StringIO()
  image.save(output, get_save_format(format))
  converted = output.getvalue()
  cache[cache_key] = converted
  return converted


def _crop_and_resize(image, width, height=0):
  if not height:
    height = width

  # Compute cropping coordinates
  x0, y0 = image.size

  w_ratio = 1.0 * x0 / width
  h_ratio = 1.0 * y0 / height

  if h_ratio > w_ratio:
    x1 = 0
    x2 = x0
    y1 = int((y0 - x0 * height / width) / 2)
    y2 = int((y0 + x0 * height / width) / 2)
  else:
    x1 = int((x0 - y0 * width / height) / 2)
    x2 = int((x0 + y0 * width / height) / 2)
    y1 = 0
    y2 = y0

  image = image.crop((x1, y1, x2, y2))
  # image.load()
  image = image.resize((width, height), Image.LANCZOS)
  return image
