# Calendar.social
# Copyright (C) 2018 Gergely Polonkai
#
# 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 <https://www.gnu.org/licenses/>.

"""Gregorian calendar system for Calendar.social
"""

from datetime import datetime, timedelta
from functools import wraps

from flask_babelex import lazy_gettext as _

from . import CalendarSystem


def to_timestamp(func):
    """Decorator that converts the return value of a function from `datetime` to a UNIX timestamp
    """

    @wraps(func)
    def _decorator(*args, **kwargs):
        return func(*args, **kwargs).timestamp()

    return _decorator


class GregorianCalendar(CalendarSystem):
    """Gregorian calendar system for Calendar.social
    """

    __name__ = _('Gregorian')

    __has_months__ = True

    START_DAY = 0
    END_DAY = 7

    month_names = (
        _('January'),
        _('February'),
        _('March'),
        _('April'),
        _('May'),
        _('June'),
        _('July'),
        _('August'),
        _('September'),
        _('October'),
        _('November'),
        _('December'),
    )

    day_names = (
        _('Monday'),
        _('Tuesday'),
        _('Wednesday'),
        _('Thursday'),
        _('Friday'),
        _('Saturday'),
        _('Sunday'),
    )

    def __init__(self, timestamp):
        self.timestamp = datetime.utcfromtimestamp(timestamp)

    @property
    def month(self):
        return self.timestamp.strftime("%B, %Y")

    @property
    def days(self):
        day_list = []

        start_day = self.timestamp.replace(day=1)

        while start_day.weekday() > self.START_DAY:
            start_day -= timedelta(days=1)

        day_list.append(start_day)
        current_day = start_day

        while current_day.weekday() < self.END_DAY and current_day.month <= self.timestamp.month:
            current_day += timedelta(days=1)
            day_list.append(current_day)

        return day_list

    @property
    @to_timestamp
    def prev_year(self):
        """Returns the timestamp of the same date in the previous year
        """

        return self.timestamp.replace(year=self.timestamp.year - 1)

    @property
    def prev_year_year(self):
        """The number of the previous year
        """

        return self.timestamp.replace(year=self.timestamp.year - 1).year

    @property
    @to_timestamp
    def prev_month(self):
        """Returns the timestamp of the same day in the previous month
        """

        if self.timestamp.month == 1:
            return self.prev_year.replace(month=12)

        return self.timestamp.replace(month=self.timestamp.month - 1)

    @property
    def prev_month_name(self):
        """The name of the previous month
        """

        if self.timestamp.month == 1:
            timestamp = self.prev_year.replace(month=12)
        else:
            timestamp = self.timestamp.replace(month=self.timestamp.month - 1)

        return self.month_names[timestamp.month - 1]

    @property
    @to_timestamp
    def next_month(self):
        """Returns the timestamp of the same day in the next month
        """

        if self.timestamp.month == 12:
            return self.next_year.replace(month=1)

        return self.timestamp.replace(month=self.timestamp.month + 1)

    @property
    def next_month_name(self):
        """The name of the next month
        """

        if self.timestamp.month == 12:
            timestamp = self.prev_year.replace(month=1)
        else:
            timestamp = self.timestamp.replace(month=self.timestamp.month + 1)

        return self.month_names[timestamp.month - 1]

    @property
    @to_timestamp
    def next_year(self):
        """Returns the timestamp of the same date in the next year
        """

        return self.timestamp.replace(year=self.timestamp.year + 1)

    @property
    def next_year_year(self):
        """The number of the next year
        """

        return self.timestamp.replace(year=self.timestamp.year + 1).year

    @property
    def has_today(self):
        """``True`` if the set month holds today’s date
        """

        now = datetime.utcnow()
        month_start_timestamp = self.timestamp.replace(day=1,
                                                       hour=0,
                                                       minute=0,
                                                       second=0,
                                                       microsecond=0)

        if self.timestamp.month == 12:
            next_month = 1
        else:
            next_month = self.timestamp.month + 1

        month_end_timestamp = month_start_timestamp.replace(month=next_month)

        return now >= month_start_timestamp and now < month_end_timestamp

    @staticmethod
    def day_events(date, user=None):
        """Returns all events for a given day
        """

        from ..models import Event, Profile

        events = Event.query

        if user:
            events = events.join(Profile) \
                           .filter(Profile.user == user)

        start_timestamp = date.replace(hour=0, minute=0, second=0, microsecond=0)
        end_timestamp = start_timestamp + timedelta(days=1)

        events = events.filter(((Event.start_time >= start_timestamp) &
                                (Event.start_time < end_timestamp)) |
                               ((Event.end_time >= start_timestamp) &
                                (Event.end_time < end_timestamp))) \
                       .order_by('start_time', 'end_time')

        return events