import os, os.path
from io import FileIO, BufferedWriter
import re
import time

from django.conf import settings
from django.core.files.storage import default_storage
from django.utils.translation import ugettext_lazy as _
from django.template.defaultfilters import slugify

from .conf import *


def is_file_image(path):

    try:
        from PIL import Image
        im = Image.open(path)
        im.verify()
        return True
    except Exception as e:
        return False

def filename_from_path(path):
    return re.sub(r'^.+/', '', path)

def model_slug(model):
    return slugify(model._meta.verbose_name_plural)

def construct_temp_path(user):
    now = time.localtime()[0:5]
    dir_name = TEMP_DIR_FORMAT % now
    return os.path.join(TEMP_DIR, dir_name, str(user.pk))

def construct_permanent_path(instance):
    model_dir = model_slug(type(instance))
    return os.path.join(FILES_DIR, model_dir, str(instance.pk))

def in_directory(path, directory):
    # don't try to manipulate with ../../
    full_path = os.path.join(MEDIA_ROOT, path)
    return path.startswith(directory) and full_path == os.path.realpath(full_path)

def in_permanent_directory(path, instance):
    full_path = os.path.join(MEDIA_ROOT, path)
    return path.startswith(construct_permanent_path(instance)) and full_path == os.path.realpath(full_path)

def make_temp_directory(filename, user, short=False):
    if not short:
        public_dir = construct_temp_path(user)
    else:
        public_dir = f'{TEMP_DIR}'
    full_dir = os.path.join(settings.MEDIA_ROOT, public_dir)

    try:
        if not os.path.exists(full_dir):
            os.makedirs(full_dir)
    except EnvironmentError:
        # deepest dir already exists
        pass

    full_path = os.path.join(full_dir, filename)
    available_full_path = default_storage.get_available_name(full_path)
    return available_full_path

def make_permanent_directory(temp_path, instance):
    public_dir = construct_permanent_path(instance)
    filename = filename_from_path(temp_path)
    full_dir = os.path.join(MEDIA_ROOT, public_dir)

    if not os.path.exists(full_dir):
        os.makedirs(full_dir)

    full_path = os.path.join(full_dir, filename)
    available_full_path = default_storage.get_available_name(full_path)
    return available_full_path

def save_upload(uploaded, filename, raw_data, user):
    '''
    raw_data: if True, uploaded is an HttpRequest object with the file being
    the raw post data
    if False, uploaded has been submitted via the basic form
    submission and is a regular Django UploadedFile in request.FILES
    '''

    path = make_temp_directory(filename, user, short=True)
    public_path = path.replace(MEDIA_ROOT, '', 1)

    #try:
    with BufferedWriter(FileIO(path, "wb")) as dest:
        # if the "advanced" upload, read directly from the HTTP request
        # with the Django 1.3 functionality
        if raw_data:
            foo = uploaded.read(1024)
            while foo:
                dest.write(foo)
                foo = uploaded.read(1024)
        # if not raw, it was a form upload so read in the normal Django chunks fashion
        else:
            for c in uploaded.chunks():
                dest.write(c)
        # got through saving the upload, report success
        return public_path
    #except IOError:
        # could not open the file most likely
    #    pass
    return False

def try_to_recover_path(temp_path, instance):
    filename = filename_from_path(temp_path)
    permanent_directory = construct_permanent_path(instance)
    permanent_path = os.path.join(permanent_directory, filename)
    full_path = os.path.join(MEDIA_ROOT, permanent_path)
    if os.path.exists(full_path):
        return permanent_path, True
    else:
        return temp_path, False

def move_to_permanent_directory(temp_path, instance):
    if temp_path.startswith('/') or temp_path.find('//') != -1 \
            or in_permanent_directory(temp_path, instance):
        return temp_path, False

    full_path = make_permanent_directory(temp_path, instance)
    public_path = full_path.replace(MEDIA_ROOT, '', 1)
    full_temp_path = os.path.join(MEDIA_ROOT, temp_path)
    try:
        os.link(full_temp_path, full_path)
    except EnvironmentError:
        return try_to_recover_path(temp_path, instance)

    if in_directory(temp_path, TEMP_DIR):
        try:
            os.remove(full_temp_path)
        except EnvironmentError:
            return try_to_recover_path(temp_path, instance)

    return public_path, True

def manage_files_on_disk(sender, instance, **kwargs):
    # Receiver of Django post_save signal.
    # At this point we know that the model instance has been saved into the db.
    from .fields import ImagesField, ImageField, FilesField, FileField
    fields = [field for field in sender._meta.fields if type(field) in [ImagesField, ImageField, FilesField, FileField]]

    for field in fields:
        old_value_attr = OLD_VALUE_STR % field.name
        deleted_value_attr = DELETED_VALUE_STR % field.name
        moved_value_attr = MOVED_VALUE_STR % field.name
        if not hasattr(instance, old_value_attr):
            continue

        old_images = (getattr(instance, old_value_attr) or '').splitlines()
        current_images = (getattr(instance, field.name) or '').splitlines()
        deleted_images = (getattr(instance, deleted_value_attr) or '').splitlines()
        moved_images = (getattr(instance, moved_value_attr) or '').splitlines()
        new_images = []
        changed = False

        # Delete removed images from disk if they are in our FILES_DIR.
        # we implement redundant checks to be absolutely sure that
        # files must be deleted. For example, if a JS error leads to
        # incorrect file lists in the hidden inputs, we reconstruct the old value.
        #
        # O = old_images, C = current_images, D = deleted_images
        #
        # what do we do with files that appear in:
        #
        #   ---  (OK)    do nothing, we don't even know it's name :)
        #   --D  (OK)    if in temp dir or permanent dir of inst: delete from disk
        #   -C-  (OK)    if not in permanent dir of inst, create hard link if possible;
        #                if in temp dir, delete
        #   -CD  (ERROR) show warning message after save
        #   O--  (ERROR) put back in current, show warning message after save
        #   O-D  (OK)    if in temp dir or permanent dir of inst: delete from disk
        #   OC-  (OK)    if not in permanent dir of inst, create hard link if possible;
        #                if in temp dir, delete
        #   OCD  (ERROR) show warning message after save

        for img in current_images:
            # OC-, -C-, OCD & -CD
            new_path = img
            if in_directory(img, TEMP_DIR) or in_directory(img, FILES_DIR):
                new_path, path_changed = move_to_permanent_directory(img, instance)
                if path_changed:
                    changed = True
                new_images.append(new_path)

        for img in deleted_images:
            if img not in current_images:
                # --D & O-D
                if in_permanent_directory(img, instance) or in_directory(img, TEMP_DIR):
                    try:
                        os.remove(os.path.join(MEDIA_ROOT, img))
                    except EnvironmentError as e:
                        pass

        for img in old_images:
            if img not in current_images and img not in deleted_images and img not in moved_images:
                # O--
                changed = True
                new_images.append(img)

        delattr(instance, old_value_attr)
        delattr(instance, deleted_value_attr)
        delattr(instance, moved_value_attr)
        if changed:
            setattr(instance, field.name, '\n'.join(new_images))
            instance.save()
