import functools
import os

from django.conf import settings
from django.core.checks import Warning, register
from django.db import models
from django.utils.translation import ugettext as _

from imagekit import ImageSpec
from imagekit.models.fields import ImageSpecField
from imagekit.processors import ResizeToFit, ResizeToFill
from PIL import Image, ImageEnhance

from .helper import get_file_path

# opacity ratio from 0 to 1
BASE_OPACITY = 0.7
# size ratio from 0 to 1, where 1 = image.size
BASE_WATERMARK_SIZE_RATIO = 0.3
# c, l, r, t, b - center, left, right, top, bottom
# allowed values: c, l, r, t, b, tl, bl, tr, br
BASE_WATERMARK_POSITION = 'bl'

_watermark_path = settings.WATERMARK_IMAGE_PATH


@register()
def example_check(*args, **kwargs):
    warnings = []
    if not _watermark_path or not os.path.exists(_watermark_path):
        warnings.append(
            Warning(
                "settings.WATERMARK_IMAGE_PATH isn't defined",
                hint="Please set correct image path otherwise \
watermark won't be overlay. Don't forget to set it for production too!",
                id='real_estate.models.image.W001',
            )
        )
    return warnings


class Opacity(object):
    """Add opacity to spec image with value of opacity"""

    def __init__(self, opacity):
        self.opacity = opacity

    def add_opacity(self, im):
        assert self.opacity >= 0 and self.opacity <= 1
        if im.mode != 'RGBA':
            im = im.convert('RGBA')
        else:
            im = im.copy()
        alpha = im.split()[3]
        alpha = ImageEnhance.Brightness(alpha).enhance(self.opacity)
        im.putalpha(alpha)
        return im

    def process(self, img):
        return self.add_opacity(img)


class Watermark(ImageSpec):
    opacity = BASE_OPACITY
    processors = [
        Opacity(opacity),
    ]
    format = 'PNG'


class EXIFRotationPreventor(object):
    """Prevents images with EXIF rotation from
    unexcpected rotation when process them

    About EXIF: https://en.wikipedia.org/wiki/Exif"""
    def process(self, im):
        exif_orientation_tag = 0x0112  # contains an integer, 1 through 8
        exif_transpose_sequences = [  # corresponding to the following
            [],
            [Image.FLIP_LEFT_RIGHT],
            [Image.ROTATE_180],
            [Image.FLIP_TOP_BOTTOM],
            [Image.FLIP_LEFT_RIGHT, Image.ROTATE_90],
            [Image.ROTATE_270],
            [Image.FLIP_TOP_BOTTOM, Image.ROTATE_90],
            [Image.ROTATE_90],
        ]

        try:
            seq = exif_transpose_sequences[
                im._getexif()[exif_orientation_tag] - 1]
        except Exception:
            return im
        else:
            return functools.reduce(lambda im, op: im.transpose(op), seq, im)


if _watermark_path and os.path.exists(_watermark_path):
    source_file = open(_watermark_path, 'rb')

    watermark_generator = Watermark(source=source_file)
    WATERMARK_SPEC = watermark_generator.generate()
    source_file.close()
else:
    WATERMARK_SPEC = None


def spec_factory(
    width=16, height=16, source='image', to_fit=True, *args, **kwargs
):
    """ Abstract factory for ImageSpecField with default thumbnail params"""
    if to_fit:
        main_processor = ResizeToFit(
            width,
            height,
            mat_color=(255, 255, 255, 0)
        )
    else:
        main_processor = ResizeToFill(
            width,
            height,
        )
    processors = [EXIFRotationPreventor()] \
        + kwargs.pop('pre_processors', []) \
        + [main_processor] \
        + kwargs.pop('extra_processors', [])
    return ImageSpecField(
        processors=processors,
        source=source,
        format=kwargs.pop('format', 'PNG'),
        options={
            'quality': kwargs.pop('options__quality', 50),
            'progressive': kwargs.pop('options__progressive', True),
        }
    )


thumbnail_16_16 = spec_factory()
thumbnail_32_32 = spec_factory(32, 32)
thumbnail_64_64 = spec_factory(64, 64)
thumbnail_120_120 = spec_factory(120, 120)
thumbnail_260_260 = spec_factory(260, 260)


def get_position_by_shortcut(shortcut, base_size, overlay_size):
    """ returns tuple (x, y) to paste overlay_size into base_size image by shortcut
    for example BaseImage.paste(OverlayImage, (x, y))
    allowed shortcuts:
    c - center
    l - left
    r - right
    t - top
    b - bottom
    and combinations lt, tl, lb, bl, rt, tr, rb, br
    """
    w, h = base_size
    w_o, h_o = overlay_size

    positions = {}
    c = (int(w / 2) - int(w_o / 2), int(h / 2) - int(h_o / 2))
    positions['c'] = c
    positions['l'] = (0, c[1])
    positions['r'] = (w - w_o, c[1])
    positions['t'] = (c[0], 0)
    positions['b'] = (c[0], h - h_o)

    # For combinations of left/rigth and top/bottom
    for x in ('l', 'r'):
        for y in ('t', 'b'):
            positions[x + y] = (positions[x][0], positions[y][1])
            positions[y + x] = positions[x + y]

    position = positions.get(shortcut)
    if not position:
        raise Exception('Ambiguous shortcut for position - %s' % shortcut)
    return position


def add_watermark(image, wmk_image, size_ratio, pos):
    width, height = image.size
    w_width, w_height = wmk_image.size

    scale = min(
        width / (w_width),
        height / (w_height)
    )
    new_size = (
        int(w_width * scale * size_ratio),
        int(w_height * scale * size_ratio),
    )

    tranparent = Image.new('RGBA', (width, height), (255, 255, 255, 0))

    watermark = wmk_image.resize(new_size)

    tranparent = Image.new('RGBA', (width, height), (255, 255, 255, 0))
    tranparent.paste(image, (0, 0))

    new_pos = get_position_by_shortcut(pos, (width, height), new_size)
    tranparent.paste(watermark, new_pos, mask=watermark)
    return tranparent


class BasePropertyImage(models.Model):
    """Abstract model for all real estate pictures
    Helps to use django inlines with pictures.
    """
    image = models.ImageField(verbose_name=_('изображение'),
                              upload_to=get_file_path)
    thumbnail_admin = spec_factory(100, 100)

    def __str__(self):
        return _('изображение')

    class Meta:
        abstract = True
        verbose_name = _('изображение')
        verbose_name_plural = _('изображения')


class BaseDraggapbleImage(BasePropertyImage):
    """Abstract model for all real estate pictures with ordering
    Helps to use django inlines with pictures.
    """
    position = models.PositiveIntegerField(
        verbose_name=_('позиция'),
        default=0,
        blank=False,
        null=False,
    )

    class Meta(BasePropertyImage.Meta):
        abstract = True
        ordering = ('position',)

    def save(self, *args, **kwargs):
        model = self.__class__

        if self.position is None:
            # Append
            try:
                last = model.objects.order_by('-position')[0]
                self.position = last.position + 1
            except IndexError:
                # First row
                self.position = 0

        return super(BaseDraggapbleImage, self).save(*args, **kwargs)


class BaseWatermarkProcessor(object):
    watermark = WATERMARK_SPEC
    # size ratio from 0 to 1, where 1 = image.size
    size = BASE_WATERMARK_SIZE_RATIO
    # c, l, r, t, b - center, left, right, top, bottom
    # allowed values: c, l, r, t, b, lt, lb, rt, rb
    position = BASE_WATERMARK_POSITION

    def process(self, image):
        if not self.watermark:
            return image

        watermark_image = Image.open(WATERMARK_SPEC)
        return add_watermark(
            image,
            watermark_image,
            self.size,
            self.position,
        )

import functools
import os

from django.conf import settings
from django.core.checks import Warning, register
from django.db import models
from django.utils.translation import ugettext as _

from imagekit import ImageSpec
from imagekit.models.fields import ImageSpecField
from imagekit.processors import ResizeToFit, ResizeToFill
from PIL import Image, ImageEnhance

from .helper import get_file_path

# opacity ratio from 0 to 1
BASE_OPACITY = 0.7
# size ratio from 0 to 1, where 1 = image.size
BASE_WATERMARK_SIZE_RATIO = 0.3
# c, l, r, t, b - center, left, right, top, bottom
# allowed values: c, l, r, t, b, tl, bl, tr, br
BASE_WATERMARK_POSITION = 'bl'

_watermark_path = settings.WATERMARK_IMAGE_PATH


@register()
def example_check(*args, **kwargs):
    warnings = []
    if not _watermark_path or not os.path.exists(_watermark_path):
        warnings.append(
            Warning(
                "settings.WATERMARK_IMAGE_PATH isn't defined",
                hint="Please set correct image path otherwise \
watermark won't be overlay. Don't forget to set it for production too!",
                id='real_estate.models.image.W001',
            )
        )
    return warnings


class Opacity(object):
    """Add opacity to spec image with value of opacity"""

    def __init__(self, opacity):
        self.opacity = opacity

    def add_opacity(self, im):
        assert self.opacity >= 0 and self.opacity <= 1
        if im.mode != 'RGBA':
            im = im.convert('RGBA')
        else:
            im = im.copy()
        alpha = im.split()[3]
        alpha = ImageEnhance.Brightness(alpha).enhance(self.opacity)
        im.putalpha(alpha)
        return im

    def process(self, img):
        return self.add_opacity(img)


class Watermark(ImageSpec):
    opacity = BASE_OPACITY
    processors = [
        Opacity(opacity),
    ]
    format = 'PNG'


class EXIFRotationPreventor(object):
    """Prevents images with EXIF rotation from
    unexcpected rotation when process them

    About EXIF: https://en.wikipedia.org/wiki/Exif"""
    def process(self, im):
        exif_orientation_tag = 0x0112  # contains an integer, 1 through 8
        exif_transpose_sequences = [  # corresponding to the following
            [],
            [Image.FLIP_LEFT_RIGHT],
            [Image.ROTATE_180],
            [Image.FLIP_TOP_BOTTOM],
            [Image.FLIP_LEFT_RIGHT, Image.ROTATE_90],
            [Image.ROTATE_270],
            [Image.FLIP_TOP_BOTTOM, Image.ROTATE_90],
            [Image.ROTATE_90],
        ]

        try:
            seq = exif_transpose_sequences[
                im._getexif()[exif_orientation_tag] - 1]
        except Exception:
            return im
        else:
            return functools.reduce(lambda im, op: im.transpose(op), seq, im)


if _watermark_path and os.path.exists(_watermark_path):
    source_file = open(_watermark_path, 'rb')

    watermark_generator = Watermark(source=source_file)
    WATERMARK_SPEC = watermark_generator.generate()
    source_file.close()
else:
    WATERMARK_SPEC = None


def spec_factory(
    width=16, height=16, source='image', to_fit=True, *args, **kwargs
):
    """ Abstract factory for ImageSpecField with default thumbnail params"""
    if to_fit:
        main_processor = ResizeToFit(
            width,
            height,
            mat_color=(255, 255, 255, 0)
        )
    else:
        main_processor = ResizeToFill(
            width,
            height,
        )
    processors = [EXIFRotationPreventor()] \
        + kwargs.pop('pre_processors', []) \
        + [main_processor] \
        + kwargs.pop('extra_processors', [])
    return ImageSpecField(
        processors=processors,
        source=source,
        format=kwargs.pop('format', 'PNG'),
        options={
            'quality': kwargs.pop('options__quality', 50),
            'progressive': kwargs.pop('options__progressive', True),
        }
    )


thumbnail_16_16 = spec_factory()
thumbnail_32_32 = spec_factory(32, 32)
thumbnail_64_64 = spec_factory(64, 64)
thumbnail_120_120 = spec_factory(120, 120)
thumbnail_260_260 = spec_factory(260, 260)


def get_position_by_shortcut(shortcut, base_size, overlay_size):
    """ returns tuple (x, y) to paste overlay_size into base_size image by shortcut
    for example BaseImage.paste(OverlayImage, (x, y))
    allowed shortcuts:
    c - center
    l - left
    r - right
    t - top
    b - bottom
    and combinations lt, tl, lb, bl, rt, tr, rb, br
    """
    w, h = base_size
    w_o, h_o = overlay_size

    positions = {}
    c = (int(w / 2) - int(w_o / 2), int(h / 2) - int(h_o / 2))
    positions['c'] = c
    positions['l'] = (0, c[1])
    positions['r'] = (w - w_o, c[1])
    positions['t'] = (c[0], 0)
    positions['b'] = (c[0], h - h_o)

    # For combinations of left/rigth and top/bottom
    for x in ('l', 'r'):
        for y in ('t', 'b'):
            positions[x + y] = (positions[x][0], positions[y][1])
            positions[y + x] = positions[x + y]

    position = positions.get(shortcut)
    if not position:
        raise Exception('Ambiguous shortcut for position - %s' % shortcut)
    return position


def add_watermark(image, wmk_image, size_ratio, pos):
    width, height = image.size
    w_width, w_height = wmk_image.size

    scale = min(
        width / (w_width),
        height / (w_height)
    )
    new_size = (
        int(w_width * scale * size_ratio),
        int(w_height * scale * size_ratio),
    )

    tranparent = Image.new('RGBA', (width, height), (255, 255, 255, 0))

    watermark = wmk_image.resize(new_size)

    tranparent = Image.new('RGBA', (width, height), (255, 255, 255, 0))
    tranparent.paste(image, (0, 0))

    new_pos = get_position_by_shortcut(pos, (width, height), new_size)
    tranparent.paste(watermark, new_pos, mask=watermark)
    return tranparent


class BasePropertyImage(models.Model):
    """Abstract model for all real estate pictures
    Helps to use django inlines with pictures.
    """
    image = models.ImageField(verbose_name=_('изображение'),
                              upload_to=get_file_path)
    thumbnail_admin = spec_factory(100, 100)

    def __str__(self):
        return _('изображение')

    class Meta:
        abstract = True
        verbose_name = _('изображение')
        verbose_name_plural = _('изображения')


class BaseDraggapbleImage(BasePropertyImage):
    """Abstract model for all real estate pictures with ordering
    Helps to use django inlines with pictures.
    """
    position = models.PositiveIntegerField(
        verbose_name=_('позиция'),
        default=0,
        blank=False,
        null=False,
    )

    class Meta(BasePropertyImage.Meta):
        abstract = True
        ordering = ('position',)

    def save(self, *args, **kwargs):
        model = self.__class__

        if self.position is None:
            # Append
            try:
                last = model.objects.order_by('-position')[0]
                self.position = last.position + 1
            except IndexError:
                # First row
                self.position = 0

        return super(BaseDraggapbleImage, self).save(*args, **kwargs)


class BaseWatermarkProcessor(object):
    watermark = WATERMARK_SPEC
    # size ratio from 0 to 1, where 1 = image.size
    size = BASE_WATERMARK_SIZE_RATIO
    # c, l, r, t, b - center, left, right, top, bottom
    # allowed values: c, l, r, t, b, lt, lb, rt, rb
    position = BASE_WATERMARK_POSITION

    def process(self, image):
        if not self.watermark:
            return image

        watermark_image = Image.open(WATERMARK_SPEC)
        return add_watermark(
            image,
            watermark_image,
            self.size,
            self.position,
        )

# opacity ratio from 0 to 1 # size ratio from 0 to 1, where 1 = image.size # c, l, r, t, b - center, left, right, top, bottom # allowed values: c, l, r, t, b, tl, bl, tr, br Add opacity to spec image with value of opacity Prevents images with EXIF rotation from     unexcpected rotation when process them      About EXIF: https://en.wikipedia.org/wiki/Exif # contains an integer, 1 through 8 # corresponding to the following Abstract factory for ImageSpecField with default thumbnail params returns tuple (x, y) to paste overlay_size into base_size image by shortcut     for example BaseImage.paste(OverlayImage, (x, y))     allowed shortcuts:     c - center     l - left     r - right     t - top     b - bottom     and combinations lt, tl, lb, bl, rt, tr, rb, br # For combinations of left/rigth and top/bottom Abstract model for all real estate pictures     Helps to use django inlines with pictures. Abstract model for all real estate pictures with ordering     Helps to use django inlines with pictures. # Append # First row # size ratio from 0 to 1, where 1 = image.size # c, l, r, t, b - center, left, right, top, bottom # allowed values: c, l, r, t, b, lt, lb, rt, rb