# -*- coding: utf-8 -*-
"""
Ce module contient les classes de base utilisées dans le back-office HDA ainsi que dans l'application hdalab.
"""

import datetime

from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.db import models

from hdabo import utils
from hdabo.utils import Property, normalize


# User Class, due to migration to django 1.6.5
class User(AbstractUser):
    """
    Un utiliateur HDALab.
    """
    class Meta:
        db_table = 'auth_user'


class SortedModelManager(models.Manager):
    use_for_related_fields = True
    def get_queryset(self):
        qs = super(SortedModelManager, self).get_queryset()
        if getattr(self, 'through', None) is not None and getattr(self.through, 'Meta', None) is not None and getattr(self.through.Meta, 'ordering', None) is not None:
            qs = qs.order_by(*[self.through._meta.db_table + "." + f for f in self.through.Meta.ordering])
        return qs


class Organisation(models.Model):
    """
    Représente une entité émettrice de documents.
    """
    hda_id = models.CharField(max_length=512, unique=True, blank=False, null=False)
    name = models.CharField(max_length=512, unique=False, blank=False, null=False)
    location = models.CharField(max_length=512, unique=False, blank=True, null=True)
    website = models.CharField(max_length=2048, unique=False, blank=True, null=True)

class Author(models.Model):
    """
    Personne ayant importer une fiche.
    """

    hda_id = models.CharField(max_length=512, unique=True, blank=False, null=False)
    lastname = models.CharField(max_length=512, unique=False, blank=True, null=True)
    firstname = models.CharField(max_length=512, unique=False, blank=True, null=True)

class TimePeriod(models.Model):
    """
    Période de scolarité:
        - Primaire
        - Collège
        - Lycée
    """
    TIME_PERIOD_CHOICES = (
        (1, u'Primaire'),
        (2, u'Collège'),
        (3, u'Lycée'),
    )
    TIME_PERIOD_DICT = {
        u'Primaire': 1,
        u'Collège': 2,
        u'Lycée': 3,
    }
    label = models.CharField(max_length=512, unique=False, blank=False, null=False)
    school_period = models.IntegerField(choices=TIME_PERIOD_CHOICES)
    natural_key = models.CharField(max_length=512, unique=True, blank=False, null=False)

    objects = SortedModelManager()

    class Meta:
        unique_together = ("label", "school_period")

    def __unicode__(self):
        return unicode(self.label)

class Domain(models.Model):
    """
    Période de la scolarité ciblé par une fiche:
        - Global (toute les période scolaires)
        - Primaire
        - Collège
        - Lycée
    """
    DOMAIN_PERIOD_CHOICES = (
        (0, u'Global'),
        (1, u'Primaire'),
        (2, u'Collège'),
        (3, u'Lycée'),
    )
    DOMAIN_PERIOD_DICT = {
        u'Global': 0,
        u'Primaire': 1,
        u'Collège': 2,
        u'Lycée': 3,
    }
    label = models.CharField(max_length=512, unique=False, blank=False, null=False)
    school_period = models.IntegerField(choices=DOMAIN_PERIOD_CHOICES)
    natural_key = models.CharField(max_length=512, unique=True, blank=False, null=False)

    objects = SortedModelManager()

    class Meta:
        unique_together = ("label", "school_period")

    def __unicode__(self):
        return unicode(self.label)


class DocumentFormat(models.Model):
    """
    Format de la fiche (pdf, word, html,...)
    """
    label = models.CharField(max_length=512, unique=True, blank=False, null=False)

    def __unicode__(self):
        return unicode(self.label)

class TagCategory(models.Model):
    """
    Catégorie du tag. Explique pourqoui le tag a été posé sur une fiche.
    Exemples :
    - Localisation : le tage représente un lieu en relation avec la fiche.
    - Créateur : Le tag représente l'auteur de(s) oeuvre(s) décrite(s) dans la fiche.
    - Datation : Le tag ajoute une information de date sur l'oeuvre sujet de la fiche.
    - Ecole/Mouvement : Le tag décrit à quelle école ou mouvement artistique appartient l'oeuvre sujet de la fiche.
    - Discipline artistique : Le tag décrit à quelle discipline artistique appartient l'oeuvre sujet de la fiche.
    """
    label = models.CharField(max_length=512, unique=True, blank=False, null=False)
    natural_key = models.CharField(max_length=512, unique=False, blank=False, null=False, db_index=True)

    def __unicode__(self):
        return unicode(self.label)

    class Meta:
        verbose_name_plural = "TagCategories"

class Tag(models.Model):
    """
    Un tag. Soit c'est un simple mot, soit il est "sémantisé" et dans ce cas, il fait référence à une page Wikipedia.
    Attributs remarquables:
    - wikipedia_url : l'url wikipedia
    - wikipedia_pageid : l'identifiant de page wikipedia.
    - alternative_wikipedia_url : autre page wikipedia pour le même tag (par exemple "Molière" pour "Jean-Baptiste Poquelin")
    - url_status : état de liage du tag (pas de liage proposé, redirection, homonymie, correspondance, non sémantisé)
    - dbpedia_uri : URI DBPedia
    - popularity : Popularité du tag sur le portail HDA. Cela permet de mettre une priorité pour sémantisé le tag.
    """
    TAG_URL_STATUS_CHOICES = (
        (0, "null_result"),
        (1, "redirection"),
        (2, "homonyme"),
        (3, "match"),
        (4, "unsematized"),
    )

    TAG_URL_STATUS_DICT = {
        "null_result":0,
        "redirection":1,
        "homonyme":2,
        "match":3,
        "unsemantized":4,
    }

    label = models.CharField(max_length=1024, unique=False, blank=False, null=False, db_index=True)
    alternative_label = models.CharField(max_length=1024, unique=False, blank=True, null=True)
    normalized_label = models.CharField(max_length=1024, unique=False, blank=False, null=False, db_index=True, editable=False)
    created_at = models.DateTimeField(auto_now_add=True)
    original_label = models.CharField(max_length=1024, unique=False, blank=False, null=False, editable=False)
    alias = models.CharField(max_length=1024, unique=False, blank=True, null=True)
    category = models.ForeignKey(TagCategory, null=True, blank=True)
    wikipedia_url = models.URLField(max_length=2048, blank=True, null=True, db_index=True)
    wikipedia_pageid = models.BigIntegerField(unique=False, blank=True, null=True, db_index=True)
    alternative_wikipedia_url = models.URLField(max_length=2048, blank=True, null=True, db_index=True)
    alternative_wikipedia_pageid = models.BigIntegerField(unique=False, blank=True, null=True, db_index=True)
    url_status = models.IntegerField(choices=TAG_URL_STATUS_CHOICES, blank=True, null=True, default=None, db_index=True)
    dbpedia_uri = models.URLField(max_length=2048, blank=True, null=True, db_index=True)
    popularity = models.IntegerField(blank=False, null=False, default=0, db_index=True)
    #natural_key = models.CharField(max_length=7168, blank=True, null=True, db_index=True)
    #TODO: find a proper key. natural key is not really a key.
    natural_key = models.CharField(max_length=7168, blank=False, null=False, db_index=True)


    def __init__(self, *args, **kwargs):
        models.Model.__init__(self, *args, **kwargs)
        self.force_natural_key = False

    @Property
    def url_status_text(): #@NoSelf
        def fget(self):
            return self.TAG_URL_STATUS_CHOICES[self.url_status][1]

        return locals()

    def calculate_natural_key(self):
        parts = [
            utils.sanitize(self.label if hasattr(self,'label') else ''),
            utils.sanitize(self.normalized_label if hasattr(self,'normalized_label') else ''),
            utils.sanitize(self.original_label if hasattr(self,'original_label') else ''),
            self.wikipedia_url.split("/")[-1].rstrip('/') if hasattr(self,'wikipedia_url') and self.wikipedia_url else ""
        ]
        return ('_'.join(parts))[:7168]


    def save(self, *args, **kwargs):
        if self.label and not self.normalized_label:
            self._normalized_label = normalize(self.label)
        if not self.force_natural_key:
            self.natural_key = self.calculate_natural_key()
        super(Tag, self).save(*args, **kwargs)

    class Meta:
        unique_together = (('label', 'original_label', 'url_status'),)

class Location(models.Model):
    """
    Information de lieu pour une fiche, avec en particulier son numéro insee.
    """
    name = models.CharField(max_length=512, unique=False, blank=False, null=False)
    insee = models.CharField(max_length=5, unique=True, blank=False, null=False)

    def __unicode__(self):
        return unicode("%s : %s" % (self.name, self.insee))


def generate_m2m_setter(m2m_field_name):

    def set_m2m_field(self, lst):

        m2m_manager = getattr(self, m2m_field_name)
        m2m_manager.clear()

        through_klass = set_m2m_field.cache.get('through_klass', None)
        if through_klass is None:
            field = getattr(self.__class__, m2m_field_name)
            through_klass = field.through
            set_m2m_field.cache['through_klass'] = through_klass
            for f in through_klass._meta.fields:
                if isinstance(f, models.ForeignKey) and f.name != "datasheet":
                    set_m2m_field.cache['target_obj_field_name'] = f.name
                    break
        target_obj_field_name = set_m2m_field.cache['target_obj_field_name']

        for i, obj in enumerate(lst):
            kwargs = {
                'datasheet': self,
                'sort_value' : i,
                target_obj_field_name: obj
            }
            new_rel = through_klass(**kwargs)
            new_rel.save()
    set_m2m_field.cache = {}

    return set_m2m_field


class Datasheet(models.Model):
    """
    Une fiche de ressource du portail Histoire des Arts.
    """
    hda_id = models.CharField(max_length=512, unique=True, blank=False, null=False)
    author = models.ForeignKey(Author, null=True, blank=True, serialize=False)
    organisation = models.ForeignKey(Organisation, serialize=False, null=True)
    title = models.CharField(max_length=2048, unique=False, blank=False, null=False, serialize=False)
    description = models.TextField(blank=True, null=True, serialize=False)
    url = models.URLField(max_length=2048, blank=True, null=True, serialize=False)
    domains = models.ManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Global']}, related_name="datasheets", through="Datasheet_domains", serialize=False)
    primary_periods = models.ManyToManyField(TimePeriod, limit_choices_to={'school_period':TimePeriod.TIME_PERIOD_DICT[u'Primaire']}, related_name="primary_periods_datasheets", through="Datasheet_primary_periods", serialize=False)
    college_periods = models.ManyToManyField(TimePeriod, limit_choices_to={'school_period':TimePeriod.TIME_PERIOD_DICT[u'Collège']}, related_name="college_periods_datasheets", through="Datasheet_college_periods", serialize=False)
    highschool_periods = models.ManyToManyField(TimePeriod, limit_choices_to={'school_period':TimePeriod.TIME_PERIOD_DICT[u'Lycée']}, related_name="highschool_periods_datasheets", through="Datasheet_highschool_periods", serialize=False)
    primary_themes = models.ManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Primaire']}, related_name="primary_themes_datasheets", through="Datasheet_primary_themes", serialize=False)
    college_themes = models.ManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Collège']}, related_name="college_themes_datasheets", through="Datasheet_college_themes", serialize=False)
    highschool_themes = models.ManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Lycée']}, related_name="highschool_themes_datasheets", through="Datasheet_highschool_themes", serialize=False)
    town = models.ForeignKey(Location, null=True, blank=True, serialize=False)
    format = models.ForeignKey(DocumentFormat, null=True, blank=True, serialize=False)
    original_creation_date = models.DateField(serialize=False)
    original_modification_date = models.DateField(serialize=False)
    modification_datetime = models.DateTimeField(auto_now=True, serialize=False)
    validation_date = models.DateTimeField(null=True, blank=True, serialize=False)
    validated = models.BooleanField(default=False, db_index=True)
    validator = models.ForeignKey(User, null=True, blank=True, serialize=False)
    manual_order = models.BooleanField(default=False, db_index=True, serialize=False)
    tags = models.ManyToManyField(Tag, through='TaggedSheet', serialize=False)

    def natural_key(self):
        return self.hda_id

    def validate(self, user):
        self.validation_date = datetime.datetime.now()
        self.validated = True
        self.validator = user
        self.save()

    def unvalidate(self):
        self.validation_date = datetime.datetime.min
        self.validated = False
        self.validator = None
        self.save()


    set_domains = generate_m2m_setter("domains")

    @Property
    def domains_list(): #@NoSelf
        def fget(self):
            return [d.label for d in self.domains.all()]

        return locals()

    @Property
    def domains_text(): #@NoSelf
        def fget(self):
            return "; ".join(self.domains_list)

        return locals()


    set_primary_periods = generate_m2m_setter("primary_periods")

    @Property
    def primary_periods_list(): #@NoSelf
        def fget(self):
            return [d.label for d in self.primary_periods.all()]

        return locals()


    @Property
    def primary_periods_text(): #@NoSelf
        def fget(self):
            return "; ".join(self.primary_periods_list)

        return locals()

    set_college_periods = generate_m2m_setter("college_periods")

    @Property
    def college_periods_list(): #@NoSelf
        def fget(self):
            return [d.label for d in self.college_periods.all()]

        return locals()

    @Property
    def college_periods_text(): #@NoSelf
        def fget(self):
            return "; ".join(self.college_periods_list)

        return locals()

    set_highschool_periods = generate_m2m_setter("highschool_periods")

    @Property
    def highschool_periods_list(): #@NoSelf
        def fget(self):
            return [d.label for d in self.highschool_periods.all()]

        return locals()

    @Property
    def highschool_periods_text(): #@NoSelf
        def fget(self):
            return "; ".join(self.highschool_periods_list)

        return locals()

    set_primary_themes = generate_m2m_setter("primary_themes")

    @Property
    def primary_themes_list(): #@NoSelf
        def fget(self):
            return [d.label for d in self.primary_themes.all()]

        return locals()


    @Property
    def primary_themes_text(): #@NoSelf
        def fget(self):
            return "; ".join(self.primary_themes_list)

        return locals()

    set_college_themes = generate_m2m_setter("college_themes")

    @Property
    def college_themes_list(): #@NoSelf
        def fget(self):
            return [d.label for d in self.college_themes.all()]

        return locals()

    @Property
    def college_themes_text(): #@NoSelf
        def fget(self):
            return "; ".join(self.college_themes_list)

        return locals()

    set_highschool_themes = generate_m2m_setter("highschool_themes")

    @Property
    def highschool_themes_list(): #@NoSelf
        def fget(self):
            return [d.label for d in self.highschool_themes.all()]

        return locals()

    @Property
    def highschool_themes_text(): #@NoSelf
        def fget(self):
            return "; ".join(self.highschool_themes_list)

        return locals()

    @Property
    def town_text(): #@NoSelf
        def fget(self):
            return self.town.name if self.town else ""

        return locals()

    @Property
    def tags_text(): #@NoSelf
        def fget(self):
            return "; ".join([t.label for t in self.tags.all()])

        return locals()

    @models.permalink
    def get_absolute_url(self):
        return ('display_datasheet', (), {
                    'ds_id': self.hda_id
                })


class TaggedSheet(models.Model):
    """
    Objet liant un tag à une fiche.
    Attributs important:
    - order : ordre du tag dans la liste de tag de la fiche. Plus un tag est en tête de fiche, plus il est important pour la fiche.
    - index_note : coéficient d'importance du tag calculé automatiquement lors de l'import et permettant de déterminer un ordre initial pour les tags.
    - wikipedia_revision_id : Numéro de révision de la page wikipedia au moment de la pose du tag sur la fiche.
    """
    datasheet = models.ForeignKey(Datasheet)
    tag = models.ForeignKey(Tag)
    created_at = models.DateTimeField(auto_now_add=True)
    original_order = models.IntegerField(null=False, blank=False, default=0)
    order = models.IntegerField(null=False, blank=False, default=0, db_index=True)
    index_note = models.FloatField(null=False, blank=False, default=0.0, db_index=True)
    wikipedia_revision_id = models.BigIntegerField(unique=False, blank=True, null=True)

    @Property
    def wikipedia_verion_permalink(): #@NoSelf
        def fget(self):
            return settings.WIKIPEDIA_VERSION_PERMALINK_TEMPLATE % (unicode(self.wikipedia_revision_id))

        return locals()


class SortedDatasheetLink(models.Model):
    datasheet = models.ForeignKey(Datasheet, db_index=True, null=False, blank=False)
    sort_value = models.IntegerField(null=False, blank=False)

    class Meta:
        abstract = True
        ordering = ['sort_value']


class Datasheet_domains(SortedDatasheetLink):
    domain = models.ForeignKey(Domain, db_index=True, null=False, blank=False)

class Datasheet_highschool_periods(SortedDatasheetLink):
    timeperiod = models.ForeignKey(TimePeriod, db_index=True, null=False, blank=False)

class Datasheet_highschool_themes(SortedDatasheetLink):
    domain = models.ForeignKey(Domain, db_index=True, null=False, blank=False)

class Datasheet_college_periods(SortedDatasheetLink):
    timeperiod = models.ForeignKey(TimePeriod, db_index=True, null=False, blank=False)

class Datasheet_college_themes(SortedDatasheetLink):
    domain = models.ForeignKey(Domain, db_index=True, null=False, blank=False)

class Datasheet_primary_periods(SortedDatasheetLink):
    timeperiod = models.ForeignKey(TimePeriod, db_index=True, null=False, blank=False)

class Datasheet_primary_themes(SortedDatasheetLink):
    domain = models.ForeignKey(Domain, db_index=True, null=False, blank=False)


# Evolution pour Hda 2 : folders of datasheets
class Folder(models.Model):
    """
    Décrit un dossier thématique comportant une liste de fiches.
    """
    url = models.URLField(max_length=2048, unique=True, blank=False, null=False)
    title = models.CharField(max_length=2048, blank=True, null=True)
    description = models.TextField(blank=True, null=True)
    datasheets = models.ManyToManyField(Datasheet)



