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

from __future__ import unicode_literals
from datetime import timedelta

from django.core.urlresolvers import reverse
from django.conf import settings
from django.db import models
from django.utils import timezone

from .fields import CommaSeparatedCharField
from .utils import add_one_year, ANGLES, merge_intervals


class ContribQuerySet(models.query.QuerySet):
    def expired(self):
        return self.filter(expiration_date__lte=timezone.now())

    def expired_in_days(self, ndays):
        return self.filter(
            expiration_date__lte=timezone.now() + timedelta(days=ndays))

    def expires_in_days(self, ndays):
        """ Returns only the data expiring in exactly that number of days

        Think about it as an anniversary. This function ignores
        minutes/seconds/hours and check only days.
        """
        return self.filter(
            expiration_date__date=timezone.now() + timedelta(days=ndays))


class Contrib(models.Model):
    CONTRIB_CONNECT = 'connect'
    CONTRIB_SHARE = 'share'

    id = models.AutoField(primary_key=True, blank=False, null=False)
    name = models.CharField(
        'Nom / Pseudo',
        max_length=30)
    contrib_type = models.CharField(
        'Type de contribution',
        max_length=10, choices=(
            (CONTRIB_CONNECT, 'Me raccorder à internet'),
            (CONTRIB_SHARE, 'Partager une partie de ma connexion')
        ), default=None)
    latitude = models.FloatField()
    longitude = models.FloatField()
    phone = models.CharField(
        'Téléphone',
        max_length=30, blank=True, default='')
    email = models.EmailField(blank=True)
    access_type = models.CharField(
        'Type de connexion',
        max_length=10, blank=True, choices=(
            ('vdsl', 'ADSL'),
            ('vdsl', 'VDSL'),
            ('fiber', 'Fibre optique'),
            ('cable', 'Coaxial (FTTLA)'),
        ))
    floor = models.PositiveIntegerField(
        'étage',
        blank=True, null=True)
    floor_total = models.PositiveIntegerField(
        "mombre d'étages",
        blank=True, null=True)
    orientations = CommaSeparatedCharField(
        blank=True, null=True, max_length=100)
    roof = models.BooleanField(
        'accès au toît',
        default=False)
    comment = models.TextField(
        'commentaire',
        blank=True, null=True)
    privacy_name = models.BooleanField(
        'nom/pseudo public',
        default=False)
    privacy_email = models.BooleanField(
        'email public',
        default=False)
    privacy_coordinates = models.BooleanField(
        'coordonnées GPS publiques',
        default=True)
    privacy_place_details = models.BooleanField(
        'étage/orientations publiques',
        default=True)
    privacy_comment = models.BooleanField(
        'commentaire public',
        default=False)
    date = models.DateTimeField(
        "date d'enregistrement",
        auto_now_add=True)
    expiration_date = models.DateTimeField(
        "date d'expiration",
        null=True, blank=True)

    STATUS_TOSTUDY = 'TOSTUDY'
    STATUS_TOCONNECT = 'TOCONNECT'
    STATUS_CONNECTED = 'CONNECTED'
    STATUS_WONTCONNECT = 'WONTCONNECT'

    CONNECTABILITY = (
        (STATUS_TOSTUDY, 'à étudier'),
        (STATUS_TOCONNECT, 'à connecter'),
        (STATUS_CONNECTED, 'connecté'),
        (STATUS_WONTCONNECT, 'pas connectable'),
    )

    status = models.CharField(
        blank=True,
        null=True,
        max_length=250,
        choices=CONNECTABILITY,
        default=STATUS_TOSTUDY)

    class Meta:
        managed = True
        db_table = 'contribs'
        verbose_name = 'contribution'

    PRIVACY_MAP = {
        'name': 'privacy_name',
        'comment': 'privacy_comment',
        'floor': 'privacy_place_details',
        'floor_total': 'privacy_place_details',
        'orientations': 'privacy_place_details',
        'roof': 'privacy_place_details',
        'angles': 'privacy_place_details',
    }

    PUBLIC_FIELDS = set(PRIVACY_MAP.keys())

    objects = ContribQuerySet.as_manager()

    def __str__(self):
        return '#{} {}'.format(self.pk, self.name)

    @property
    def angles(self):
        """Return a list of (start, stop) angles from cardinal orientations
        """
        # Cleanup
        if self.orientations is None:
            return []
        orientations = [o for o in self.orientations if o in ANGLES.keys()]
        # Hack to make leaflet-semicircle happy (drawing a full circle only
        # works with (0, 360))
        if len(orientations) == 8:
            return [[0, 360]]
        angles = [ANGLES[orientation] for orientation in orientations]
        angles.sort(key=lambda i: i[0])  # sort by x
        return merge_intervals(angles)

    def get_postponed_expiration_date(self, from_date):
        """ Computes the new expiration date

        :param from_date: reference datetime frow where we add our extra delay.
        """
        return add_one_year(from_date)

    def clean(self):
        # usefull only for data imported from bottle version
        if not self.date:
            self.date = timezone.now()
        if not self.expiration_date:
            self.expiration_date = self.get_postponed_expiration_date(
                self.date)

    def save(self, *args, **kwargs):
        if not self.pk:  # New instance
            self.date = timezone.now()
            self.expiration_date = self.get_postponed_expiration_date(
                self.date)
        super().save(*args, **kwargs)

    def is_public(self):
        return self.privacy_coordinates

    def is_expired(self):
        return self.expiration_date <= timezone.now()

    def _may_be_public(self, field):
        return field in self.PUBLIC_FIELDS

    def _is_public(self, field):
        return getattr(self, self.PRIVACY_MAP[field])

    def get_public_field(self, field):
        """ Gets safely an attribute in its public form (if any)

        :param field: The field name
        :return: the field value, or None, if the field is private
        """
        if self._may_be_public(field) and self._is_public(field):
            v = getattr(self, field)
            if hasattr(v, '__call__'):
                return v()
            else:
                return v

        else:
            return None

    def get_absolute_url(self, request=None, base_url=None):
        """ Get absolute url

        You can mention either `request` or `base_url` to get a full URL
        (starting with "http://" or "https://")

        :type request: request
        :param request: if mentioned, will be used to provide a full URL
        :param base_url: if mentioned, will be used to provide a full URL
        """
        url = '{}#{}'.format(
            reverse('display_map'), self.pk)
        if request:
            return request.build_absolute_uri(url)
        elif base_url:
            return '{}{}'.format(base_url, url)
        else:
            return url

    def make_management_url(self, token):
        return '{}{}?token={}'.format(
            settings.SITE_URL.strip('/'),
            reverse('manage_contrib', kwargs={'pk': self.pk}),
            token)