import re
from six.moves import urllib
import os, os.path
from datetime import datetime

from django.conf import settings
import six
from django.utils.safestring import mark_safe

from .utils import curry

from django.core.files.images import ImageFile
from django.core.files.storage import get_storage_class
from django.contrib.staticfiles import finders

from sorl.thumbnail import get_thumbnail

from .conf import *


class FilePath(six.text_type):
    def __new__(cls, str, instance=None, field=None, settings={}):
        self = super(FilePath, cls).__new__(cls, str.strip())
        self._instance = instance
        self._field = field
        self._exists = None
        self._size = None
        self._accessed_time = None
        self._created_time = None
        self._modified_time = None
        self._thumbnails = {}
        self.settings = {
            'img_attrs': {},
            'thumbnail_size': None,
            'thumbnail_attrs': {},
        }
        self.settings.update(settings)
        return self

    def _html_attrs(self, **kwargs):
        attrs = {}
        attrs.update(kwargs)
        if 'css_class' in attrs:
            attrs['class'] = attrs['css_class']
            del attrs['css_class']
        return attrs

    @property
    def unescaped(self):
        return urllib.parse.unquote(self)

    @property
    def escaped(self):
        return urllib.parse.quote(self.unescaped)

    @property
    def url(self):
        if not self.startswith('/') and self.find('//') == -1:
            return os.path.join(MEDIA_URL, self.escaped)
        return self.escaped

    @property
    def local_path(self):
        if not self.startswith('/') and self.find('//') == -1:
            return os.path.join(MEDIA_ROOT, urllib.parse.unquote(self))
        return self

    def _get_local_path_or_file(self):
        # if file is in static instead of media directory, sorl raises
        # a suspicious operation error. So we open it safely without errors.

        if self.startswith('/'):
            if self.startswith('/static/'):
                path = self.replace('/static/', '')
            elif self.startswith(settings.STATIC_URL):
                path = self.replace(settings.STATIC_URL, '')
            else:
                return self.local_path
        else:
            return self.local_path

        path = finders.find(urllib.parse.unquote(path))
        image = ImageFile(open(path, 'r'))
        return image

    @property
    def filename(self):
        return urllib.parse.unquote(re.sub(r'^.+\/', '', self))

    @property
    def display_name(self):
        without_extension = re.sub(r'\.[\w\d]+$', '', self.filename)
        with_spaces = re.sub(r'_', ' ', without_extension)
        return with_spaces

    @property
    def ext(self):
        return re.sub(r'^.+\.', '', self.filename)

    def exists(self):
        if self._exists == None:
            self._exists = os.path.exists(self.local_path)
        return self._exists

    def get_size(self):
        if self._size == None:
            self._size = os.path.getsize(self.local_path)
        return self._size

    def get_accessed_time(self):
        if self._accessed_time == None:
            self._accessed_time = datetime.fromtimestamp(os.path.getatime(self.local_path))
        return self._accessed_time

    def get_created_time(self):
        if self._created_time == None:
            self._created_time = datetime.fromtimestamp(os.path.getctime(self.local_path))
        return self._created_time

    def get_modified_time(self):
        if self._modified_time == None:
            self._modified_time = datetime.fromtimestamp(os.path.getmtime(self.local_path))
        return self._modified_time


class ImagePath(FilePath):
    def img_tag(self, **kwargs):
        attrs = {}
        attrs.update(self.settings['img_attrs'])
        attrs.update(kwargs)
        attrs = self._html_attrs(**attrs)
        attrs_str = ''.join([
            u'%s="%s" ' % (key, value)
            for key, value in attrs.items()
        ])
        return mark_safe(u'<img src="%s" %s/>' % (self.url, attrs_str))

    def _thumbnail_file_format(self):
        if self.ext.lower() in ['gif', 'png']:
            return 'PNG'
        return 'JPEG'

    def thumbnail(self, size=None, **kwargs):
        size = size or self.settings['thumbnail_size']
        if not size:
            raise Exception('No thumbnail size supplied')

        attrs = {
            'format': self._thumbnail_file_format(),
            'upscale': False,
        }
        attrs.update(self.settings['thumbnail_attrs'])
        attrs.update(kwargs)

        all_attrs = { 'size': size }
        all_attrs.update(attrs)
        key = hash(frozenset(all_attrs))

        if not key in self._thumbnails:
            #self._thumbnails[key] = get_thumbnail(self._get_local_path_or_file(), size, **attrs)
            self._thumbnails[key] = get_thumbnail(self.local_path, size, **attrs)

        return self._thumbnails[key]

    def thumbnail_tag(self, size, opts={}, **kwargs):
        try:
            thumbnail = self.thumbnail(size, **opts)
        except EnvironmentError as e:
            if settings.THUMBNAIL_DEBUG:
                raise e
            return ''

        src = ImagePath(thumbnail.url, self._instance, self._field)
        attrs = { 'width': thumbnail.width, 'height': thumbnail.height }
        attrs.update(self.settings['img_attrs'])
        attrs.update(kwargs)
        return src.img_tag(**attrs)

    def __getattr__(self, attr):
        thumbnail_mxn = re.match(r'^thumbnail_(tag_)?(\d*x?\d+)$', attr)
        if thumbnail_mxn:
            tag = thumbnail_mxn.group(1) == 'tag_'
            size = thumbnail_mxn.group(2)
            if tag:
                return curry(self.thumbnail_tag, size)
            else:
                return curry(self.thumbnail, size)

        raise AttributeError


class FilePaths(six.text_type):
    item_class = FilePath

    def __new__(cls, str, instance=None, field=None, settings={}):
        self = super(FilePaths, cls).__new__(cls, str)
        self._instance = instance
        self._field = field
        self._all = None
        self._length = None
        self._current = 0
        self.settings = {
            'img_attrs': {},
            'thumbnail_size': None,
            'thumbnail_attrs': {},
        }
        self.settings.update(settings)
        return self

    def all(self):
        if self._all == None:
            self._all = []
            for f in self.splitlines():
                self._all.append(self._field.attr_class.item_class(f, self._instance, self._field, self.settings))

            self._length = len(self._all)

        return self._all

    def count(self):
        self.all()
        return self._length

    def first(self):
        return self.all() and self.all()[0] or None

    def last(self):
        return self.all() and self.all()[-1] or None

    def next(self):
        f = self.all()[self._current]
        self._current += 1
        return f

    def next_n(self, n):
        files = self.all()[self._current:self._current+n]
        self._current += n
        return files

    def next_all(self):
        files = self.all()[self._current:]
        self._current = self._length - 1
        return files

    def has_next(self):
        self.all()
        return max(0, self._length - self._current - 1)

    def reset(self):
        self._current = 0

    def __getattr__(self, attr):
        next_n = re.match(r'^next_(\d+)$', attr)
        if next_n:
            n = int(next_n.group(1))
            return curry(self.next_n, n)

        raise AttributeError


class ImagePaths(FilePaths):
    item_class = ImagePath

    def as_gallery(self):
        raise NotImplementedError

    def as_carousel(self):
        raise NotImplementedError


class FilesDescriptor(object):
    """
    Used django.db.models.fields.files.FileDescriptor as an example.
    This descriptor returns an unicode object, with special methods
    for formatting like filename(), absolute(), relative() and img_tag().
    """
    def __init__(self, field):
        self.field = field

    def __get__(self, instance=None, owner=None):
        if instance is None:
            return self

        files = instance.__dict__[self.field.name]
        if isinstance(files, six.string_types) and not isinstance(files, (FilePath, FilePaths)):
            attr = self.field.attr_class(files, instance, self.field)
            instance.__dict__[self.field.name] = attr

        return instance.__dict__[self.field.name]

    def __set__(self, instance, value):
        instance.__dict__[self.field.name] = value
