# -*- Mode: Python; coding: utf-8; indent-tabs-mode: s; tab-width: 4 -*-
import logging
import calendar
import datetime
from gi.repository import GLib, Gio, GObject
from gettext import gettext as _
import gahshomar.khayyam as khayyam
logger = logging.getLogger(__name__)
def strftime(frm, odate):
    if isinstance(odate, datetime.date):
        date = GLib.DateTime.new_local(odate.year, odate.month, odate.day, 0,
                                       0, 0)
        return date.format(_(frm))
    else:
        return odate.strftime(frm.replace('O', ''))
def add_years(date, years):
    while True:
        try:
            return date.replace(year=date.year + years)
        except ValueError:
            date -= datetime.timedelta(days=1)
def date_to_gregorian(date):
    if isinstance(date, khayyam.JalaliDate):
        return date.to_date()
    return date
def date_to_jalali(date):
    if isinstance(date, khayyam.JalaliDate):
        return date
    return khayyam.JalaliDate.from_date(date)
def add_one_month(t):
    """Return a `datetime.date` or `datetime.datetime` (as given) that is
    one month earlier.
    Note that the resultant day of the month might change if the following
    month has fewer days:
        >>> add_one_month(datetime.date(2010, 1, 31))
        datetime.date(2010, 2, 28)
    """
    import datetime
    one_day = datetime.timedelta(days=1)
    one_month_later = t + one_day
    while one_month_later.month == t.month:  # advance to start of next month
        one_month_later += one_day
    target_month = one_month_later.month
    while one_month_later.day < t.day:  # advance to appropriate day
        one_month_later += one_day
        if one_month_later.month != target_month:  # gone too far
            one_month_later -= one_day
            break
    return one_month_later
def subtract_one_month(t):
    """Return a `datetime.date` or `datetime.datetime` (as given) that is
    one month later.
    Note that the resultant day of the month might change if the following
    month has fewer days:
        >>> subtract_one_month(datetime.date(2010, 3, 31))
        datetime.date(2010, 2, 28)
    """
    import datetime
    one_day = datetime.timedelta(days=1)
    one_month_earlier = t - one_day
    while one_month_earlier.month == t.month or one_month_earlier.day > t.day:
        one_month_earlier -= one_day
    return one_month_earlier
def add_months(date, months):
    '''http://code.activestate.com/recipes/
    577274-subtract-or-add-a-month-to-a-datetimedate-or-datet/
    Note: months may be positive, or negative, but must be an integer.
    '''
    if months == 0:
        return date
    elif months > 0:
        for __ in range(months):
            date = add_one_month(date)
    else:
        for __ in range(abs(months)):
            date = subtract_one_month(date)
    return date
class Date(GObject.GObject):
    """The class for representing dates in Gahshomar
    """
    def __init__(self, date=None, **kwargs):
        self._date = date
        super().__init__(**kwargs)
    @GObject.Property
    def date(self):
        return self._date
    @date.setter
    def date(self, value):
        self._date = self._to_correct_date(value)
    @GObject.Property(type=str)
    def date_format(self):
        return self._date_format
    @date_format.setter
    def date_format(self, value):
        logger.debug("date_format changed to %s", value)
        self._date_format = str(value).replace("'", "")
    @property
    def first_day_month(self):
        first_day_of_month = self.date + \
            datetime.timedelta(days=1 - self.date.day)
        first_day_of_month = first_day_of_month.weekday()
        first_day_of_month = (first_day_of_month) % 7
        return first_day_of_month
    @property
    def grid_mat(self):
        # decide if it is going to be 6 rows or 5
        if self.first_day_month + self.days_in_month > 35:
            rows = 6
        else:
            rows = 5
        grid_mat = []  # 5 or 6 row, 7 column
        for __ in range(rows):
            row = []
            for __ in range(7):
                row.append([])
            grid_mat.append(row)
        delta = -(self.first_day_month + self.date.day) + 1
        for j in range(rows):
            for i in range(7):
                if self.rtl:
                    delta_time = datetime.timedelta(days=6 - i + j * 7 + delta)
                else:
                    delta_time = datetime.timedelta(days=i + j * 7 + delta)
                date = self.date + delta_time
                text = '{}'
                d = strftime(_('%d'), date)
                if d[0] == '0' or d[0] == '۰':
                    d = d[1:]
                grid_mat[j][i] = (date, text.format(d))
        return grid_mat
    @property
    def year(self):
        return self.date.year
    @property
    def month(self):
        return self.date.month
    @property
    def day(self):
        return self.date.day
    @property
    def full_date(self):
        return strftime(self.date_format, self.date)
    @property
    def day_str(self):
        day = strftime('%d', self.date)
        if day and day[0] == '۰':
            day = day[1:]
        return day
    def strftime(self, date_format):
        return strftime(date_format, self.date)
    def today(self):
        return self._to_correct_date(datetime.date.today())
    def add_months(self, n):
        return add_months(self.date, n)
    def add_years(self, n):
        return add_years(self.date, n)
    def replace(self, *args, **kwargs):
        return self.date.replace(*args, **kwargs)
    def on_date_changed(self, object, *args):
        self.date = object.date
    def on_update_to_today(self, *args):
        self.date = self.today()
        # return True so the timeout keeps continuing
        return True
class GregorianDate(Date):
    def __init__(self, date=None, *args, **kwargs):
        date = date_to_gregorian(date or datetime.date.today())
        super().__init__(date, *args, **kwargs)
        self.first_week_day_offset = 0
        self.rtl = False
        settings = Gio.Settings.new('org.gahshomar.Gahshomar')
        settings.bind('gregorian-date-format', self, 'date_format',
                      Gio.SettingsBindFlags.DEFAULT)
    @property
    def days_in_month(self):
        return calendar.monthrange(self.date.year, self.date.month)[1]
    @property
    def week_days(self):
        return [(_('Mon'), _('Monday')), (_('Tue'), _('Tuesday')),
                (_('Wed'), _('Wednesday')), (_('Thu'),
                                             _('Thursday')), (_('Fri'),
                                                              _('Friday')),
                (_('Sat'), _('Saturday')), (_('Sun'), _('Sunday'))]
    @property
    def months(self):
        return list(calendar.month_name[1:])
    def _to_correct_date(self, date):
        return date_to_gregorian(date)
class PersianDate(Date):
    def __init__(self, date=None, *args, **kwargs):
        date = date_to_jalali(date or datetime.date.today())
        super().__init__(date, *args, **kwargs)
        self.first_week_day_offset = 2
        self.rtl = True
        settings = Gio.Settings.new('org.gahshomar.Gahshomar')
        settings.bind('afghan-month', self, 'afghan_month',
                      Gio.SettingsBindFlags.DEFAULT)
    def is_kabiseh(year: int) -> bool:
        #Check if a Jalali (Persian) year is a leap year (kabiseh).
        kabiseh_remainders = {1, 5, 9, 13, 17, 22, 26, 30}
        return (year % 33) in kabiseh_remainders
    @GObject.Property(type=bool, default=False)
    def afghan_month(self):
        logger.debug("afghan_month accessed {}".format(self._afghan_month))
        return self._afghan_month
    @afghan_month.setter
    def afghan_month(self, value):
        self._afghan_month = value
        logger.debug("afghan_month set {}".format(self._afghan_month))
        self._set_date_format()
    def _set_date_format(self, *args):
        if self.afghan_month:
            date_format = 'afghan-date-format'
        else:
            date_format = 'persian-date-format'
        settings = Gio.Settings.new('org.gahshomar.Gahshomar')
        settings.bind(date_format, self, 'date_format',
                      Gio.SettingsBindFlags.DEFAULT)
    @property
    def week_days(self):
        return [('ش', 'شنبه'), ('۱ش', 'یکشنبه'), ('۲ش', 'دوشنبه'),
                ('۳ش', 'سهشنبه'), ('۴ش', 'چهارشنبه'), ('۵ش', 'پنجشنبه'),
                ('آ', 'آدینه')]
    @property
    def days_in_month(self):
        if self.date.month == 12:  # Esfand
            return 30 if is_kabiseh(self.date.year) else 29
        return self.date.days_in_month
    @property
    def months(self):
        if self.afghan_month:
            return list(khayyam.AFGHAN_MONTH_NAMES.values())
        else:
            return list(khayyam.PERSIAN_MONTH_NAMES.values())
    def _to_correct_date(self, date):
        return date_to_jalali(date)
GREGORIAN_DATE = GregorianDate()
"""This object represents the current selected Gregorian date in Gahshomar's
interface. You can connect to its signal to see if selected date has changed:
``GREGORIAN_DATE.connect("notify::date", on_date_changed)``
"""
PERSIAN_DATE = PersianDate()
"""This object represents the current selected Persian date in Gahshomar's
interface. You can connect to its signal to see if selected date has changed:
``PERSIAN_DATE.connect("notify::date", on_date_changed)``
"""
# connect the current Persian date to the current Gregorian date
GREGORIAN_DATE.connect("notify::date", PERSIAN_DATE.on_date_changed)
TODAY = GregorianDate()
"""This object represents today in Gahshomar's interface.
You can connect to its signal to see if today has changed:
``TODAY.connect("notify::date", on_today_changed)``
"""
# update today every 10 seconds
GLib.timeout_add_seconds(
    priority=GLib.PRIORITY_DEFAULT,
    interval=10,
    function=TODAY.on_update_to_today)
TODAY_PERSIAN = PersianDate()
TODAY.connect("notify::date", TODAY_PERSIAN.on_date_changed)