#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2012-2016 Étienne Loks  <etienne.loks_AT_peacefrogsDOTnet>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# See the file COPYING for details.

from django.conf import settings
from django.contrib.gis.db import models
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.db.models.signals import post_delete, post_save
from django.utils.translation import ugettext_lazy as _, ugettext, pgettext

from ishtar_common.utils import cached_label_changed

from ishtar_common.models import GeneralType, BaseHistorizedItem, \
    HistoricalRecords, OwnPerms, ShortMenuItem, Source, GeneralRelationType,\
    GeneralRecordRelations, post_delete_record_relation, get_external_id, \
    ImageModel, post_save_cache
from archaeological_operations.models import Operation, Period, Parcel


class DatingType(GeneralType):
    class Meta:
        verbose_name = _(u"Dating type")
        verbose_name_plural = _(u"Dating types")
        ordering = ('label',)
post_save.connect(post_save_cache, sender=DatingType)
post_delete.connect(post_save_cache, sender=DatingType)


class DatingQuality(GeneralType):
    class Meta:
        verbose_name = _(u"Dating quality")
        verbose_name_plural = _(u"Dating qualities")
        ordering = ('label',)
post_save.connect(post_save_cache, sender=DatingQuality)
post_delete.connect(post_save_cache, sender=DatingQuality)


class Dating(models.Model):
    period = models.ForeignKey(Period, verbose_name=_(u"Period"))
    start_date = models.IntegerField(_(u"Start date"), blank=True, null=True)
    end_date = models.IntegerField(_(u"End date"), blank=True, null=True)
    dating_type = models.ForeignKey(DatingType, verbose_name=_(u"Dating type"),
                                    blank=True, null=True)
    quality = models.ForeignKey(DatingQuality, verbose_name=_(u"Quality"),
                                blank=True, null=True)
    precise_dating = models.TextField(_(u"Precise dating"), blank=True,
                                      null=True)

    class Meta:
        verbose_name = _(u"Dating")
        verbose_name_plural = _(u"Datings")

    def __unicode__(self):
        start_date = self.start_date and unicode(self.start_date) or u""
        end_date = self.end_date and unicode(self.end_date) or u""
        if not start_date and not end_date:
            return unicode(self.period)
        return u"%s (%s-%s)" % (self.period, start_date, end_date)


class Unit(GeneralType):
    order = models.IntegerField(_(u"Order"))
    parent = models.ForeignKey("Unit", verbose_name=_(u"Parent unit"),
                               blank=True, null=True)

    class Meta:
        verbose_name = _(u"Unit Type")
        verbose_name_plural = _(u"Unit Types")
        ordering = ('order',)

    def __unicode__(self):
        return self.label
post_save.connect(post_save_cache, sender=Unit)
post_delete.connect(post_save_cache, sender=Unit)


class ActivityType(GeneralType):
    order = models.IntegerField(_(u"Order"))

    class Meta:
        verbose_name = _(u"Activity Type")
        verbose_name_plural = _(u"Activity Types")
        ordering = ('order',)

    def __unicode__(self):
        return self.label
post_save.connect(post_save_cache, sender=ActivityType)
post_delete.connect(post_save_cache, sender=ActivityType)


class IdentificationType(GeneralType):
    order = models.IntegerField(_(u"Order"))

    class Meta:
        verbose_name = _(u"Identification Type")
        verbose_name_plural = _(u"Identification Types")
        ordering = ('order', 'label')

    def __unicode__(self):
        return self.label
post_save.connect(post_save_cache, sender=IdentificationType)
post_delete.connect(post_save_cache, sender=IdentificationType)


class ContextRecord(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem):
    SHOW_URL = 'show-contextrecord'
    SLUG = 'contextrecord'
    TABLE_COLS = ['parcel.town', 'operation.year',
                  'operation.operation_code',
                  'label', 'unit']
    if settings.COUNTRY == 'fr':
        TABLE_COLS.insert(1, 'operation.code_patriarche')
    TABLE_COLS_FOR_OPE = ['label', 'parcel', 'unit',
                          'datings.period', 'description']
    TABLE_COLS_FOR_OPE_LBL = {'section__parcel_number': _("Parcel")}
    CONTEXTUAL_TABLE_COLS = {
        'full': {
            'related_context_records': 'detailled_related_context_records'
        }
    }
    IMAGE_PREFIX = 'context_records/'
    external_id = models.TextField(_(u"External ID"), blank=True, null=True)
    auto_external_id = models.BooleanField(
        _(u"External ID is set automatically"), default=False)
    parcel = models.ForeignKey(Parcel, verbose_name=_(u"Parcel"),
                               related_name='context_record')
    operation = models.ForeignKey(Operation, verbose_name=_(u"Operation"),
                                  related_name='context_record')
    label = models.CharField(_(u"ID"), max_length=200)
    description = models.TextField(_(u"Description"), blank=True, null=True)
    comment = models.TextField(_(u"General comment"), blank=True, null=True)
    opening_date = models.DateField(_(u"Date d'ouverture"),
                                    blank=True, null=True)
    closing_date = models.DateField(_(u"End date"), blank=True, null=True)
    length = models.FloatField(_(u"Length (m)"), blank=True, null=True)
    width = models.FloatField(_(u"Width (m)"), blank=True, null=True)
    thickness = models.FloatField(_(u"Thickness (m)"), blank=True,
                                  null=True)
    depth = models.FloatField(_(u"Depth (m)"), blank=True, null=True)
    location = models.CharField(
        _(u"Location"), blank=True, null=True, max_length=200,
        help_text=_(u"A short description of the location of the context "
                    u"record"))
    datings = models.ManyToManyField(Dating)
    datings_comment = models.TextField(_(u"Comment on datings"), blank=True,
                                       null=True)
    unit = models.ForeignKey(Unit, verbose_name=_(u"Unit"), related_name='+',
                             blank=True, null=True)
    has_furniture = models.NullBooleanField(_(u"Has furniture?"), blank=True,
                                            null=True)
    filling = models.TextField(_(u"Filling"), blank=True, null=True)
    interpretation = models.TextField(_(u"Interpretation"), blank=True,
                                      null=True)
    taq = models.IntegerField(
        _(u"TAQ"), blank=True, null=True,
        help_text=_(u"\"Terminus Ante Quem\" the context record can't have "
                    u"been created after this date"))
    taq_estimated = models.IntegerField(
        _(u"Estimated TAQ"), blank=True, null=True,
        help_text=_(u"Estimation of a \"Terminus Ante Quem\""))
    tpq = models.IntegerField(
        _(u"TPQ"), blank=True, null=True,
        help_text=_(u"\"Terminus Post Quem\" the context record can't have "
                    u"been created before this date"))
    tpq_estimated = models.IntegerField(
        _(u"Estimated TPQ"), blank=True, null=True,
        help_text=_(u"Estimation of a \"Terminus Post Quem\""))
    identification = models.ForeignKey(
        IdentificationType, blank=True, null=True,
        verbose_name=_(u"Identification"),)
    activity = models.ForeignKey(ActivityType, blank=True, null=True,
                                 verbose_name=_(u"Activity"),)
    related_context_records = models.ManyToManyField(
        'ContextRecord', through='RecordRelations', blank=True, null=True)
    point = models.PointField(_(u"Point"), blank=True, null=True, dim=3)
    polygon = models.PolygonField(_(u"Polygon"), blank=True, null=True)
    cached_label = models.TextField(_(u"Cached name"), null=True, blank=True)
    history = HistoricalRecords()

    class Meta:
        verbose_name = _(u"Context Record")
        verbose_name_plural = _(u"Context Record")
        permissions = (
            ("view_contextrecord", ugettext(u"Can view all Context Records")),
            ("view_own_contextrecord",
             ugettext(u"Can view own Context Record")),
            ("add_own_contextrecord",
             ugettext(u"Can add own Context Record")),
            ("change_own_contextrecord",
             ugettext(u"Can change own Context Record")),
            ("delete_own_contextrecord",
             ugettext(u"Can delete own Context Record")),
        )
        ordering = ('cached_label',)

    @property
    def name(self):
        return self.label or ""

    @property
    def short_class_name(self):
        return pgettext("short", u"Context record")

    def __unicode__(self):
        return self.short_label

    @property
    def short_label(self):
        return settings.JOINT.join([unicode(item) for item in [
            self.operation.get_reference(), self.parcel, self.label] if item])

    @property
    def show_url(self):
        return reverse('show-contextrecord', args=[self.pk, ''])

    @classmethod
    def get_query_owns(cls, user):
        return Q(operation__scientist=user.ishtaruser.person) |\
            Q(operation__in_charge=user.ishtaruser.person) |\
            Q(history_creator=user)

    @classmethod
    def get_owns(cls, user, menu_filtr=None, limit=None):
        replace_query = {}
        if menu_filtr:
            replace_query = {'operation': menu_filtr}
        owns = super(ContextRecord, cls).get_owns(
            user, replace_query=replace_query,
            limit=limit)
        return sorted(owns, key=lambda x: x.cached_label)

    def full_label(self):
        return unicode(self)

    def _real_label(self):
        if not self.operation.code_patriarche:
            return
        return settings.JOINT.join((unicode(self.operation.code_patriarche),
                                    self.label))

    def _temp_label(self):
        if self.operation.code_patriarche:
            return
        return settings.JOINT.join([unicode(lbl) for lbl in [
            self.operation.year, self.operation.operation_code, self.label]
            if lbl])

    def _generate_cached_label(self):
        return self.full_label()

    @property
    def reference(self):
        if not self.operation:
            return "00"
        return self.full_label()

    def get_department(self):
        if not self.operation:
            return "00"
        return self.operation.get_department()

    def get_town_label(self):
        if not self.operation:
            return "00"
        return self.operation.get_town_label()

    @classmethod
    def get_periods(cls, slice='year', fltr={}):
        q = cls.objects
        if fltr:
            q = q.filter(**fltr)
        if slice == 'year':
            years = set()
            for res in list(q.values('operation__start_date')):
                if res['operation__start_date']:
                    yr = res['operation__start_date'].year
                    years.add(yr)
            return list(years)
        return []

    @classmethod
    def get_by_year(cls, year, fltr={}):
        q = cls.objects
        if fltr:
            q = q.filter(**fltr)
        return q.filter(operation__start_date__year=year)

    @classmethod
    def get_operations(cls):
        return [dct['operation__pk']
                for dct in cls.objects.values('operation__pk').distinct()]

    @classmethod
    def get_by_operation(cls, operation_id):
        return cls.objects.filter(operation__pk=operation_id)

    @classmethod
    def get_total_number(cls, fltr={}):
        q = cls.objects
        if fltr:
            q = q.filter(**fltr)
        return q.count()

    def detailled_related_context_records(self):
        crs = []
        for cr in self.right_relations.all():
            crs.append(u"{} ({})".format(cr.right_record,
                                         cr.relation_type.get_tiny_label()))
        return u" ; ".join(crs)

    def find_docs_q(self):
        from archaeological_finds.models import FindSource
        return FindSource.objects.filter(find__base_finds__context_record=self)

    def save(self, *args, **kwargs):
        returned = super(ContextRecord, self).save(*args, **kwargs)
        updated = False
        if not self.external_id or self.auto_external_id:
            external_id = get_external_id('context_record_external_id', self)
            if external_id != self.external_id:
                updated = True
                self.auto_external_id = True
                self.external_id = external_id
        if updated:
            self.save()
        return returned


post_save.connect(cached_label_changed, sender=ContextRecord)


class RelationType(GeneralRelationType):
    inverse_relation = models.ForeignKey(
        'RelationType', verbose_name=_(u"Inverse relation"), blank=True,
        null=True)

    class Meta:
        verbose_name = _(u"Relation type")
        verbose_name_plural = _(u"Relation types")
        ordering = ('order', 'label')


class RecordRelations(GeneralRecordRelations, models.Model):
    MAIN_ATTR = 'left_record'
    left_record = models.ForeignKey(ContextRecord,
                                    related_name='right_relations')
    right_record = models.ForeignKey(ContextRecord,
                                     related_name='left_relations')
    relation_type = models.ForeignKey(RelationType)
    TABLE_COLS = [
        "left_record__label", "left_record__unit", "left_record__parcel",
        "left_record__datings__period", "left_record__description",
        "relation_type",
        "right_record__label", "right_record__unit", "right_record__parcel",
        "right_record__datings__period", "right_record__description"]
    TABLE_COLS_LBL = {
        "left_record__label": _(u"ID (left)"),
        "left_record__unit": _(u"Unit (left)"),
        "left_record__parcel": _(u"Parcel (left)"),
        "left_record__description": _(u"Description (left)"),
        "left_record__datings__period": _(u"Periods (left)"),
        "relation_type": _(u"Relation type"),
        "right_record__label": _(u"Unit (right)"),
        "right_record__unit": _(u"Parcel (right)"),
        "right_record__parcel": _(u"Parcel (right)"),
        "right_record__description": _(u"Description (right)"),
        "right_record__datings__period": _(u"Periods (right)")
    }

    class Meta:
        verbose_name = _(u"Record relation")
        verbose_name_plural = _(u"Record relations")

post_delete.connect(post_delete_record_relation, sender=RecordRelations)


class ContextRecordSource(Source):
    SHOW_URL = 'show-contextrecordsource'
    MODIFY_URL = 'record_source_modify'
    TABLE_COLS = ['context_record__operation', 'context_record'] + \
        Source.TABLE_COLS

    class Meta:
        verbose_name = _(u"Context record documentation")
        verbose_name_plural = _(u"Context record documentations")
    context_record = models.ForeignKey(
        ContextRecord, verbose_name=_(u"Context record"),
        related_name="source")

    @property
    def owner(self):
        return self.context_record
