"""Image field with a delete checkbox"""

import cStringIO, os
from django import forms
from django.conf import settings
from django.db import models
from django.db.models import signals
from django.dispatch import dispatcher
from django.utils.functional import curry
from django.utils.translation import gettext
from PIL import Image as PILImage

FIT = 0
CROP = 1
DEFAULT_MODE = FIT
DEFAULT_WIDTH = 640
DEFAULT_HEIGHT = 480

class DeleteCheckbox(forms.CheckboxField):

    def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
        super(DeleteCheckbox, self).__init__(field_name, checked_by_default, validator_list)

    def render(self, data):
        checked_html = ''
        if data or (data is '' and self.checked_by_default):
            checked_html = ' checked="checked"'
        label = gettext('Delete')
        return ' %s <input type="checkbox" id="%s" class="v%s" name="%s"%s value="on" />' % \
            (label, self.get_id(), self.__class__.__name__,
            self.field_name, checked_html)

class FileField(models.FileField):

    def get_internal_type(self):
        return 'FileField'

    def get_manipulator_field_objs(self):
        return [forms.FileUploadField, forms.HiddenField, DeleteCheckbox]

    def get_manipulator_field_names(self, name_prefix):
        return [name_prefix + self.name + '_file', name_prefix + self.name, name_prefix + self.name + '_delete']

    def save_file(self, new_data, new_object, original_object, change, rel, save=True):
        field_names = self.get_manipulator_field_names('')
        # Delete only if DeleteCheckbox is present and checked
        if len(field_names) > 2 and new_data.get(field_names[2], False):
            file_name = getattr(new_object, 'get_%s_filename' % self.name)()
            # If file exists, delete it
            if not self.default or self.default not in file_name:
                if file_name and os.path.exists(file_name):
                    os.remove(file_name)
            setattr(new_object, self.name, None)
            new_object.save()
        else:
            super(FileField, self).save_file(new_data, new_object, original_object, change, rel)

class ImageField(FileField):
    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
        self.width_field, self.height_field = width_field, height_field
        FileField.__init__(self, verbose_name, name, **kwargs)

    def get_manipulator_field_objs(self):
        return [forms.ImageUploadField, forms.HiddenField, DeleteCheckbox]

    def contribute_to_class(self, cls, name):
        super(ImageField, self).contribute_to_class(cls, name)
        # Add get_BLAH_width and get_BLAH_height methods, but only if the
        # image field doesn't have width and height cache fields.
        if not self.width_field:
            setattr(cls, 'get_%s_width' % self.name, curry(cls._get_FIELD_width, field=self))
        if not self.height_field:
            setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self))

    def save_file(self, new_data, new_object, original_object, change, rel, save=True):
        FileField.save_file(self, new_data, new_object, original_object, change, rel)
        # If the image has height and/or width field(s) and they haven't
        # changed, set the width and/or height field(s) back to their original
        # values.
        if change and (self.width_field or self.height_field):
            if self.width_field:
                setattr(new_object, self.width_field, getattr(original_object, self.width_field))
            if self.height_field:
                setattr(new_object, self.height_field, getattr(original_object, self.height_field))
            new_object.save()

class PhotoField(ImageField):

    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT, mode=DEFAULT_MODE, quality=None, parent_pk=None, **kwargs):
        self.width_field, self.height_field = width_field, height_field
        super(PhotoField, self).__init__(verbose_name, name, width_field, height_field, **kwargs)
        self.width, self.height, self.mode, self.quality = width, height, mode, quality
        self.parent_pk = parent_pk or "UNDEFINED"
        self.original_file_name = ''

    def get_internal_type(self):
        return 'ImageField'

    def _update_parent_pk(self, instance=None):
        self.parent_pk = instance._get_pk_val()

    def contribute_to_class(self, cls, name):
        super(PhotoField, self).contribute_to_class(cls, name)
        # Add get_FIELD_content_type method
        setattr(cls, 'get_%s_content_type' % self.name, lambda instance: "image/jpeg")
        #hook to access the photofiled instance
        setattr(cls, '%s_instance'  % self.name, self)
        dispatcher.connect(self._update_parent_pk, signals.post_save, sender=cls)

    def get_content_type(self):
        return "image/jpeg"

    def save_file(self, new_data, new_object, original_object, change, rel, save=True):
        field_names = self.get_manipulator_field_names('')
        upload_field_name = field_names[0]
        filename = '%s-%s.jpg' % (self.parent_pk, self.get_attname())
        # If there is no DeleteCheckbox or the DeleteCheckbox was not checked
        if len(field_names) < 3 or not new_data.get(field_names[2], False):
            if new_data.get(upload_field_name, False):
                if rel:
                    self.original_file_name = new_data[upload_field_name][0]["filename"]
                    new_data[upload_field_name][0]["filename"] = filename 
                    new_data[upload_field_name][0]["content"] = PhotoField.resize(new_data[upload_field_name][0]["content"], self.width, self.height, self.mode, self.quality)
                else:
                    self.original_file_name = new_data[upload_field_name]["filename"]
                    new_data[upload_field_name]["filename"] = filename
                    new_data[upload_field_name]["content"] = PhotoField.resize(new_data[upload_field_name]["content"], self.width, self.height, self.mode, self.quality)
                # If file exists, delete it
                filepath = os.path.join(settings.MEDIA_ROOT, self.upload_to, filename)
                if os.path.exists(filepath):
                    os.remove(filepath)
        super(PhotoField, self).save_file(new_data, new_object, original_object, change, rel)

    def resize(cls, data, width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT, mode=DEFAULT_MODE, quality=None):
        """Resize an image given as the string data to fit the bounding box
           (width, height). Mode may either be FIT, in which case the image is 
           shrunk to fit completely within the bounding box (the new image may 
           be smaller than the bounding box), or CROP, in which case the image 
           is cropped to the aspect ratio of the bounding box, and then resized 
           to fit (the new image will be the same size as the bounding box)."""
        data_file = cStringIO.StringIO(data)
        pil_obj = PILImage.open(data_file)
        if pil_obj.mode != 'RGB':
            pil_obj = pil_obj.convert('RGB')

        if mode == FIT:
            # Reduce the image's size by using the PIL's thumbnail method, which
            # preserves the aspect ratio of the original image, but does not
            # guarantee that the image will be (scale_width x scale_height) in
            # size. (It will extend to at least one of those dimensions, however.)
            pil_obj.thumbnail((int(width), int(height)), PILImage.ANTIALIAS)

        elif mode == CROP:
            x, y = pil_obj.size

            # If the main image is larger in both dimensions than the desired
            # thumbnail (this is the normal situation,) crop the image to the
            # aspect ratio of the thumbnail, centred on the main image, and
            # reduce the cropped image to thumbnail-size.
            if x >= width and y >= height:
                aspect = float(width) / float(height)
                crop_width = min(int(aspect*y), x)
                crop_height = min(int(float(x)/aspect), y)
                crop_point_x = (x-crop_width)/2
                crop_point_y = (y-crop_height)/2
                pil_obj = pil_obj.crop((crop_point_x, crop_point_y, crop_point_x+crop_width, crop_point_y+crop_height))
                pil_obj.thumbnail((width, height), PILImage.ANTIALIAS)

            # Otherwise, if either the width or height of the main image is
            # less than the thumbnail size, crop the image to the largest
            # rectangle which fits inside of the thumbnail size. (This will
            # be the image's complete extent in at least one dimension.) In
            # this case, we do not reduce the size of the cropped image.
            else:
                crop_width = min(width, x)
                crop_height = min(height, y)
                crop_point_x = (x-crop_width)/2
                crop_point_y = (y-crop_height)/2
                pil_obj = pil_obj.crop((crop_point_x, crop_point_y, crop_point_x+crop_width, crop_point_y+crop_height))

        out_file = cStringIO.StringIO()
        if quality:
            pil_obj.save(out_file, 'JPEG', quality=quality)
        else:
            pil_obj.save(out_file, 'JPEG')
        out_file.reset()
        return out_file.read()

    resize=classmethod(resize)