#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of convertdate.
# http://github.com/fitnr/convertdate

# Licensed under the MIT license:
# http://opensource.org/licenses/MIT
# Copyright (c) 2016, fitnr <fitnr@fakeisthenewreal>
from calendar import isleap
from . import gregorian
from .utils import floor
from .data import positivist as data

'''
    Convert between Gregorian/Julian Day and Comte's Positivist calendar.
    The Positivist calendar has 13 months and one or two festival days.
    Festival days are given as the fourteenth month.
    The Gregorian date 1789-01-01 is Positivist 0001-01-01.
'''

# Positivist calendar has 13 28-day months and one festival day

EPOCH = 2374479.5

YEAR_EPOCH = 1789

DAYS_IN_YEAR = 365

MONTHS = (
    'Moses', 'Homer', 'Aristotle', 'Archimedes',
    'Caesar', 'Saint Paul', 'Charlemagne', 'Dante',
    'Gutenberg', 'Shakespeare', 'Descartes', 'Frederic',
    'Bichat', ''
)


def legal_date(year, month, day):
    '''Checks if a given date is a legal positivist date'''
    try:
        assert year >= 1
        assert 0 < month <= 14
        assert 0 < day <= 28
        if month == 14:
            if isleap(year + YEAR_EPOCH - 1):
                assert day <= 2
            else:
                assert day == 1

    except AssertionError:
        raise ValueError("Invalid Positivist date: ({}, {}, {})".format(year, month, day))

    return True


def to_jd(year, month, day):
    '''Convert a Positivist date to Julian day count.'''
    legal_date(year, month, day)
    gyear = year + YEAR_EPOCH - 1

    return (
        gregorian.EPOCH - 1 + (365 * (gyear - 1)) +
        floor((gyear - 1) / 4) + (-floor((gyear - 1) / 100)) +
        floor((gyear - 1) / 400) + (month - 1) * 28 + day
    )


def from_jd(jd):
    '''Convert a Julian day count to Positivist date.'''
    try:
        assert jd >= EPOCH
    except AssertionError:
        raise ValueError('Invalid Julian day')

    depoch = floor(jd - 0.5) + 0.5 - gregorian.EPOCH

    quadricent = floor(depoch / gregorian.INTERCALATION_CYCLE_DAYS)
    dqc = depoch % gregorian.INTERCALATION_CYCLE_DAYS

    cent = floor(dqc / gregorian.LEAP_SUPPRESSION_DAYS)
    dcent = dqc % gregorian.LEAP_SUPPRESSION_DAYS

    quad = floor(dcent / gregorian.LEAP_CYCLE_DAYS)
    dquad = dcent % gregorian.LEAP_CYCLE_DAYS

    yindex = floor(dquad / gregorian.YEAR_DAYS)
    year = (
        quadricent * gregorian.INTERCALATION_CYCLE_YEARS +
        cent * gregorian.LEAP_SUPPRESSION_YEARS +
        quad * gregorian.LEAP_CYCLE_YEARS + yindex
    )

    if yindex == 4:
        yearday = 365
        year = year - 1

    else:
        yearday = (
            depoch -
            quadricent * gregorian.INTERCALATION_CYCLE_DAYS -
            cent * gregorian.LEAP_SUPPRESSION_DAYS -
            quad * gregorian.LEAP_CYCLE_DAYS -
            yindex * gregorian.YEAR_DAYS
        )

    month = floor(yearday / 28)

    return (year - YEAR_EPOCH + 2, month + 1, int(yearday - (month * 28)) + 1)


def from_gregorian(year, month, day):
    return from_jd(gregorian.to_jd(year, month, day))


def to_gregorian(year, month, day):
    return gregorian.from_jd(to_jd(year, month, day))


def dayname(year, month, day):
    '''
    Give the name of the month and day for a given date.

    Returns:
        tuple month_name, day_name
    '''
    legal_date(year, month, day)

    yearday = (month - 1) * 28 + day

    if isleap(year + YEAR_EPOCH - 1):
        dname = data.day_names_leap[yearday - 1]
    else:
        dname = data.day_names[yearday - 1]

    return MONTHS[month - 1], dname


def weekday(day):
    '''
        Gives the weekday (0=Monday) of a positivist month and day.
        Note that the festival month does not have a day.
    '''
    return (day % 7) - 1


def festival(month, day):
    '''
    Gives the festival day for a month and day.
    Returns None if inapplicable.
    '''
    return data.festivals.get((month, day))
