import os
from math import floor

from django.db import models
from django.urls import reverse
from django.dispatch import receiver
from django.db.models.signals import post_delete, pre_save
from django.utils.translation import ugettext_lazy as _

from lostplaces.models.abstract_models import Submittable, Taggable, Mapable, Expireable

from easy_thumbnails.fields import ThumbnailerImageField
from easy_thumbnails.files import get_thumbnailer

PLACE_LEVELS = (
    (1, 'Ruin'),
    (2, 'Vandalized'),
    (3, 'Natures Treasure'),
    (4, 'Lost in History'),
    (5, 'Time Capsule')
)

class Place(Submittable, Taggable, Mapable):
    """
    Place defines a lost place (location, name, description etc.).
    """

    location = models.CharField(
        max_length=50,
        verbose_name=_('Location'),
    )
    description = models.TextField(
        help_text=_('Description of the place: e.g. how to get there, where to be careful, the place\'s history...'),
        verbose_name=_('Description'),
    )

    hero = models.ForeignKey(
        'PlaceImage',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='place_heros'
    )

    level = models.IntegerField(
        default=5,
        choices=PLACE_LEVELS
    )

    def get_hero_image(self):
        if self.hero:
            return self.hero
        elif len(self.placeimages.all()) > 0:
            return self.placeimages.first()
        else:
            return None

    def get_absolute_url(self):
        return reverse('place_detail', kwargs={'pk': self.pk})
        
    def get_hero_index_in_queryset(self):
        '''
        Calculates the index of the hero image within
        the list / queryset of images. Necessary for
        the lightbox.
        '''
        for i in range(0, len(self.placeimages.all())):
            image = self.placeimages.all()[i]
            if image == self.hero:
                return i
        return None
    

    @classmethod
    # Get center position of LP-geocoordinates.
    def average_latlon(cls, place_list):
        amount = len(place_list)
        # Init fill values to prevent None
        # China Corner in Münster
        # Where I almost always eat lunch
        # (Does'nt help losing wheight, tho)
        longitude = 7.6295628132604385
        latitude = 51.961922091398904

        if amount > 0:
            for place in place_list:
                longitude += place.longitude
                latitude += place.latitude
            return {'latitude':latitude / amount, 'longitude': longitude / amount}

        return {'latitude': latitude, 'longitude': longitude}

    def calculate_place_level(self):
        self.remove_expired_votes()

        if self.placevotings.count() == 0:
            self.level = 5
            self.save()
            return

        level = 0

        for vote in self.placevotings.all():
            level += vote.vote

        self.level = floor(level / self.placevotings.count())
        self.save()
        
    def remove_expired_votes(self):
        for vote in self.placevotings.all():
            if vote.is_expired:
                vote.delete()

    def __str__(self):
        return self.name

def generate_place_image_filename(instance, filename):
    """
    Callback for generating filename for uploaded place images.
    Returns filename as: place_pk-placename{-number}.jpg
    """

    return 'places/' + str(instance.place.pk) + '-' + str(instance.place.name) + '.' + filename.split('.')[-1]

def generate_image_upload_path(instance, filename):
    return generate_place_image_filename(instance, filename)

class PlaceAsset(Submittable):
    """
    Assets to a place, i.e. images
    """

    class Meta:
        abstract = True

    place = models.ForeignKey(
        Place,
        on_delete=models.CASCADE,
        related_name='%(class)ss',
        null=True
    )

class DummyAsset(PlaceAsset):
    name = models.CharField(max_length=50)

class PlaceImage(PlaceAsset):
    """
    PlaceImage defines an image file object that points to a file in uploads/.
    Intermediate image sizes are generated as defined in THUMBNAIL_ALIASES.
    PlaceImage references a Place to which it belongs.
    """

    description = models.TextField(
        blank=True,
        verbose_name=_('Description'),
    )
    filename = ThumbnailerImageField(
        upload_to=generate_place_image_filename, 
        resize_source=dict(size=(2560, 2560), 
        sharpen=True),
        verbose_name=_('Images'),
        help_text=_('Optional: One or more images to upload')
    )
    place = models.ForeignKey(
        Place,
        on_delete=models.CASCADE,
        related_name='placeimages'
    )
    
    def __str__(self):
        """
        Returning the name of the corresponding place + id 
        of this image as textual representation of this instance
        """

        return 'Image ' + str(self.place.name)

# These two auto-delete files from filesystem when they are unneeded:

@receiver(post_delete, sender=PlaceImage)
def auto_delete_file_on_delete(sender, instance, **kwargs):
    """
    Deletes file (including thumbnails) from filesystem
    when corresponding `PlaceImage` object is deleted.
    """
    if instance.filename:
        # Get and delete all files and thumbnails from instance
        thumbmanager = get_thumbnailer(instance.filename)
        thumbmanager.delete(save=False)

@receiver(pre_save, sender=PlaceImage)
def auto_delete_file_on_change(sender, instance, **kwargs):
    """
    Deletes old file from filesystem
    when corresponding `PlaceImage` object is updated
    with new file.
    """
    if not instance.pk:
        return False

    try:
        old_file = PlaceImage.objects.get(pk=instance.pk).filename
    except PlaceImage.DoesNotExist:
        return False

    # No need to delete thumbnails, as they will be overwritten on regeneration.
    new_file = instance.filename
    if not old_file == new_file:
        old_file.delete(save=False)


class PlaceVoting(PlaceAsset, Expireable):
    vote = models.IntegerField(choices=PLACE_LEVELS)
    
    def get_human_readable_level(self):
        return PLACE_LEVELS[self.vote - 1][1]

    def get_all_choices(self):
        return reversed(PLACE_LEVELS)