# -*- coding: utf-8 -*-

from django.db import models
from django.conf import settings

import ox

from archive import extract

from . import managers
from .utils import add_cuts


def get_layers(item, interval=None, user=None):
    from annotation.models import Annotation

    if user and user.is_anonymous:
        user = None

    layers = {}
    private = []
    for l in settings.CONFIG['layers']:
        name = l['id']
        layers[name] = []
        if l.get('private'):
            private.append(name)

    qs = Annotation.objects.filter(item=item).exclude(value='')
    qs = qs.filter(layer__in=layers)
    qs = qs.order_by('start', 'end', 'sortvalue')

    if interval:
        start, end = interval
        qs = qs.filter(start__lt=end, end__gt=start)

    entity_cache = {}

    for a in qs.order_by('start').select_related('user'):
        if a.layer in private and a.user != user:
            continue

        layers[a.layer].append(a.json(user=user, entity_cache=entity_cache))

    return layers


class MetaClip(object):
    def update_calculated_values(self):
        start = self.start
        end = self.end
        if self.item.sort.duration:
            start = min(self.start, self.item.sort.duration)
            end = min(self.end, self.item.sort.duration)
        self.duration = end - start
        if int(end*25) - int(start*25) > 0:
            self.hue, self.saturation, self.lightness = extract.average_color(
                           self.item.timeline_prefix, self.start, self.end)
            self.volume = extract.average_volume(self.item.timeline_prefix, self.start, self.end)
        else:
            self.hue = self.saturation = self.lightness = 0
            self.volume = 0

    def update_findvalue(self):
        if not self.aspect_ratio and self.item:
            streams = self.item.streams()
            if streams:
                self.aspect_ratio = streams[0].aspect_ratio
        if self.item:
            self.user = self.item.user and self.item.user.id
            self.sort = self.item.sort
        if self.id:
            anns = self.annotations.order_by('layer', 'sortvalue')
            anns_by_layer = {}
            for ann in anns:
                anns_by_layer.setdefault(ann.layer, []).append(ann)

            sortvalue = ''.join((
                a.sortvalue
                for l in settings.CONFIG.get('clipLayers', [])
                for a in anns_by_layer.get(l, [])
                if a.sortvalue
            ))
            if sortvalue:
                self.sortvalue = sortvalue[:900]
            else:
                self.sortvalue = None

            self.findvalue = '\n'.join(list(filter(None, [a.findvalue for a in anns])))
            for l in [k['id'] for k in settings.CONFIG['layers']]:
                setattr(self, l, l in anns_by_layer and bool(len(anns_by_layer[l])))

    def save(self, *args, **kwargs):
        if self.duration != self.end - self.start:
            self.update_calculated_values()
        self.update_findvalue()
        models.Model.save(self, *args, **kwargs)

    clip_keys = ('id', 'in', 'out', 'position', 'created', 'modified',
                 'hue', 'saturation', 'lightness', 'volume', 'videoRatio')
    def json(self, keys=None, qs=None, user=None):
        j = {}
        for key in self.clip_keys:
            j[key] = getattr(self, {
                'id': 'public_id',
                'in': 'start',
                'out': 'end',
                'position': 'start',
                'videoRatio': 'aspect_ratio',
            }.get(key, key))
        if not j['videoRatio']:
            j['videoRatio'] = 4/3
        if keys:
            for key in list(j):
                if key not in keys:
                    del j[key]
            #needed here to make item find with clips work
            if 'annotations' in keys:
                #annotations = self.annotations.filter(layer__in=settings.CONFIG['clipLayers'])
                annotations = self.annotations.all()
                if qs:
                    for q in qs:
                        annotations = annotations.filter(q)
                entity_cache = {}
                j['annotations'] = [
                    a.json(keys=['value', 'id', 'layer'], entity_cache=entity_cache)
                    for a in annotations
                    if a.value
                ]
            if 'layers' in keys:
                j['layers'] = self.get_layers()
            if 'cuts' in keys:
                j['cuts'] = tuple([c for c in self.item.get('cuts', []) if c > self.start and c < self.end])
            for key in keys:
                if key not in self.clip_keys and key not in j:
                    if key == 'streams':
                        value = [s.file.oshash for s in self.item.streams()]
                    else:
                        value = self.item.get(key) or self.item.cache.get(key)
                    if not value and hasattr(self.item.sort, key):
                        value = getattr(self.item.sort, key)
                    if value is not None:
                        j[key] = value
        return j

    def edit_json(self, user=None):
        data = {
            'id': ox.toAZ(self.id),
        }
        data['item'] = self.item.public_id
        data['in'] = self.start
        data['out'] = self.end
        qs = self.annotations.all()
        if qs.count():
            data['annotation'] = qs[0].public_id
        data['parts'] = self.item.cache['parts']
        data['durations'] = self.item.cache['durations']
        for key in settings.CONFIG['itemTitleKeys'] + ['videoRatio']:
            value = self.item.cache.get(key)
            if value:
                data[key] = value
        data['duration'] = data['out'] - data['in']
        add_cuts(data, self.item, self.start, self.end)
        data['layers'] = self.get_layers(user)
        data['streams'] = [s.file.oshash for s in self.item.streams()]
        return data

    def get_layers(self, user=None):
        return get_layers(item=self.item, interval=(self.start, self.end), user=user)

    @classmethod
    def get_or_create(cls, item, start, end):
        start = float(start)
        end = float(end)
        start = float('%0.03f' % start)
        end = float('%0.03f' % end)
        qs = cls.objects.filter(item=item, start=start, end=end)
        if qs.count() == 0:
            clip, created = cls.objects.get_or_create(item=item, start=start, end=end)
            clip.save()
            created = True
        else:
            clip = qs[0]
            created = False
        return clip, created

    @property
    def public_id(self):
        return "%s/%0.03f-%0.03f" % (self.item.public_id, float(self.start), float(self.end))

    def __str__(self):
        return self.public_id

class Meta:
    unique_together = ("item", "start", "end")

attrs = {
    '__module__': 'clip.models',
    'Meta': Meta,
    'objects': managers.ClipManager(),
    'created': models.DateTimeField(auto_now_add=True),
    'modified': models.DateTimeField(auto_now=True),
    'aspect_ratio': models.FloatField(default=0),

    'item': models.ForeignKey('item.Item', related_name='clips', on_delete=models.CASCADE),
    'sort': models.ForeignKey('item.ItemSort', related_name='matching_clips', on_delete=models.CASCADE),
    'user': models.IntegerField(db_index=True, null=True),

    #seconds
    'start': models.FloatField(default=-1, db_index=True),
    'end': models.FloatField(default=-1),
    'duration': models.FloatField(default=0, db_index=True),


    #get from annotation
    'hue': models.FloatField(default=0, db_index=True),
    'saturation': models.FloatField(default=0, db_index=True),
    'lightness': models.FloatField(default=0, db_index=True),
    'volume': models.FloatField(default=0, null=True, db_index=True),

    'sortvalue': models.CharField(max_length=1000, null=True, db_index=True),
    'findvalue': models.TextField(null=True, db_index=settings.DB_GIN_TRGM),
}
for name in [k['id'] for k in settings.CONFIG['layers']]:
    attrs[name] = models.BooleanField(default=False, db_index=True)

Clip = type('Clip', (MetaClip, models.Model), attrs)

class ClipRandom(models.Model):
    id = models.BigIntegerField(primary_key=True)
    clip = models.OneToOneField(Clip, on_delete=models.CASCADE)
