# -*- coding: utf-8 -*-
from datetime import datetime
from math import ceil

from .util import SolarUtil, LunarUtil, HolidayUtil


class Solar:
    """
    阳历日期
    """

    # 2000年儒略日数(2000-1-1 12:00:00 UTC)
    J2000 = 2451545

    def __init__(self, year, month, day, hour, minute, second):
        if year == 1582 and month == 10:
            if 4 < day < 15:
                raise Exception("wrong solar year %d month %d day %d" % (year, month, day))
        if month < 1 or month > 12:
            raise Exception("wrong month %d" % month)
        if day < 1 or month > 31:
            raise Exception("wrong day %d" % day)
        if hour < 0 or hour > 23:
            raise Exception("wrong hour %d" % hour)
        if minute < 0 or minute > 59:
            raise Exception("wrong minute %d" % minute)
        if second < 0 or second > 59:
            raise Exception("wrong second %d" % second)
        self.__year = year
        self.__month = month
        self.__day = day
        self.__hour = hour
        self.__minute = minute
        self.__second = second

    @staticmethod
    def fromDate(date):
        return Solar(date.year, date.month, date.day, date.hour, date.minute, date.second)

    @staticmethod
    def fromJulianDay(julian_day):
        d = int(julian_day + 0.5)
        f = julian_day + 0.5 - d
        if d >= 2299161:
            c = int((d - 1867216.25) / 36524.25)
            d += 1 + c - int(c / 4)
        d += 1524
        year = int((d - 122.1) / 365.25)
        d -= int(365.25 * year)
        month = int(d / 30.601)
        d -= int(30.601 * month)
        day = d
        if month > 13:
            month -= 13
            year -= 4715
        else:
            month -= 1
            year -= 4716
        f *= 24
        hour = int(f)

        f -= hour
        f *= 60
        minute = int(f)

        f -= minute
        f *= 60
        second = int(round(f))
        if second > 59:
            second -= 60
            minute += 1
        if minute > 59:
            minute -= 60
            hour += 1
        if hour > 23:
            hour -= 24
            day += 1
        return Solar(year, month, day, hour, minute, second)

    @staticmethod
    def fromYmdHms(year, month, day, hour, minute, second):
        return Solar(year, month, day, hour, minute, second)

    @staticmethod
    def fromYmd(year, month, day):
        return Solar(year, month, day, 0, 0, 0)

    @staticmethod
    def fromBaZi(year_gan_zhi, month_gan_zhi, day_gan_zhi, time_gan_zhi, sect=2, base_year=1900):
        from . import Lunar
        sect = 1 if 1 == sect else 2
        solar_list = []
        # 月地支距寅月的偏移值
        m = LunarUtil.find(month_gan_zhi[1:], LunarUtil.ZHI, -1) - 2
        if m < 0:
            m += 12
        # 月天干要一致
        if ((LunarUtil.find(year_gan_zhi[:1], LunarUtil.GAN, -1) + 1) * 2 + m) % 10 != LunarUtil.find(month_gan_zhi[:1], LunarUtil.GAN, -1):
            return solar_list
        # 1年的立春是辛酉，序号57
        y = LunarUtil.getJiaZiIndex(year_gan_zhi) - 57
        if y < 0:
            y += 60
        y += 1
        # 节令偏移值
        m *= 2
        # 时辰地支转时刻，子时按零点算
        h = LunarUtil.find(time_gan_zhi[1:], LunarUtil.ZHI, -1) * 2
        hours = [h]
        if 0 == h and 2 == sect:
            hours.append(23)
        start_year = base_year - 1

        # 结束年
        end_year = datetime.now().year

        while y <= end_year:
            if y >= start_year:
                # 立春为寅月的开始
                jie_qi_table = Lunar.fromYmd(y, 1, 1).getJieQiTable()
                # 节令推移，年干支和月干支就都匹配上了
                solar_time = jie_qi_table[Lunar.JIE_QI_IN_USE[4 + m]]
                if solar_time.getYear() >= base_year:
                    # 日干支和节令干支的偏移值
                    d = LunarUtil.getJiaZiIndex(day_gan_zhi) - LunarUtil.getJiaZiIndex(solar_time.getLunar().getDayInGanZhiExact2())
                    if d < 0:
                        d += 60
                    if d > 0:
                        # 从节令推移天数
                        solar_time = solar_time.next(d)
                    for hour in hours:
                        mi = 0
                        s = 0
                        if d == 0 and hour == solar_time.getHour():
                            # 如果正好是节令当天，且小时和节令的小时数相等的极端情况，把分钟和秒钟带上
                            mi = solar_time.getMinute()
                            s = solar_time.getSecond()
                        # 验证一下
                        solar = Solar.fromYmdHms(solar_time.getYear(), solar_time.getMonth(), solar_time.getDay(), hour, mi, s)
                        if d == 30:
                            solar = solar.nextHour(-1)
                        lunar = solar.getLunar()
                        dgz = lunar.getDayInGanZhiExact2() if 2 == sect else lunar.getDayInGanZhiExact()
                        if lunar.getYearInGanZhiExact() == year_gan_zhi and lunar.getMonthInGanZhiExact() == month_gan_zhi and dgz == day_gan_zhi and lunar.getTimeInGanZhi() == time_gan_zhi:
                            solar_list.append(solar)
            y += 60
        return solar_list

    def isLeapYear(self):
        """
        是否闰年
        :return: True/False 闰年/非闰年
        """
        return SolarUtil.isLeapYear(self.__year)

    def getWeek(self):
        """
        获取星期，0代表周日，1代表周一
        :return: 0123456
        """
        return (int(self.getJulianDay() + 0.5) + 7000001) % 7

    def getWeekInChinese(self):
        """
        获取星期的中文
        :return: 日一二三四五六
        """
        return SolarUtil.WEEK[self.getWeek()]

    def getFestivals(self):
        """
        获取节日，有可能一天会有多个节日
        :return: 劳动节等
        """
        festivals = []
        key = "%d-%d" % (self.__month, self.__day)
        if key in SolarUtil.FESTIVAL:
            festivals.append(SolarUtil.FESTIVAL[key])
        week = self.getWeek()
        key = "%d-%d-%d" % (self.__month, int(ceil(self.__day / 7.0)), week)
        if key in SolarUtil.WEEK_FESTIVAL:
            festivals.append(SolarUtil.WEEK_FESTIVAL[key])
        if self.__day + 7 > SolarUtil.getDaysOfMonth(self.__year, self.__month):
            key = "%d-0-%d" % (self.__month, week)
            if key in SolarUtil.WEEK_FESTIVAL:
                festivals.append(SolarUtil.WEEK_FESTIVAL[key])
        return festivals

    def getOtherFestivals(self):
        """
        获取非正式的节日，有可能一天会有多个节日
        :return: 非正式的节日列表，如中元节
        """
        festivals = []
        key = "%d-%d" % (self.__month, self.__day)
        if key in SolarUtil.OTHER_FESTIVAL:
            for f in SolarUtil.OTHER_FESTIVAL[key]:
                festivals.append(f)
        return festivals

    def getXingZuo(self):
        """
        获取星座
        :return: 星座
        """
        index = 11
        y = self.__month * 100 + self.__day
        if 321 <= y <= 419:
            index = 0
        elif 420 <= y <= 520:
            index = 1
        elif 521 <= y <= 621:
            index = 2
        elif 622 <= y <= 722:
            index = 3
        elif 723 <= y <= 822:
            index = 4
        elif 823 <= y <= 922:
            index = 5
        elif 923 <= y <= 1023:
            index = 6
        elif 1024 <= y <= 1122:
            index = 7
        elif 1123 <= y <= 1221:
            index = 8
        elif y >= 1222 or y <= 119:
            index = 9
        elif y <= 218:
            index = 10
        return SolarUtil.XING_ZUO[index]

    def getJulianDay(self):
        """
        获取儒略日
        :return: 儒略日
        """
        y = self.__year
        m = self.__month
        d = self.__day + ((self.__second / 60.0 + self.__minute) / 60 + self.__hour) / 24
        n = 0
        g = False
        if y * 372 + m * 31 + int(d) >= 588829:
            g = True
        if m <= 2:
            m += 12
            y -= 1
        if g:
            n = int(y / 100)
            n = 2 - n + int(n / 4)
        return int(365.25 * (y + 4716)) + int(30.6001 * (m + 1)) + d + n - 1524.5

    def getLunar(self):
        """
        获取农历
        :return: 农历
        """
        from .Lunar import Lunar
        return Lunar.fromSolar(self)

    def nextDay(self, days):
        y = self.__year
        m = self.__month
        d = self.__day
        if 1582 == y and 10 == m:
            if d > 4:
                d -= 10
        if days > 0:
            d += days
            days_in_month = SolarUtil.getDaysOfMonth(y, m)
            while d > days_in_month:
                d -= days_in_month
                m += 1
                if m > 12:
                    m = 1
                    y += 1
                days_in_month = SolarUtil.getDaysOfMonth(y, m)
        elif days < 0:
            while d + days <= 0:
                m -= 1
                if m < 1:
                    m = 12
                    y -= 1
                d += SolarUtil.getDaysOfMonth(y, m)
            d += days
        if 1582 == y and 10 == m:
            if d > 4:
                d += 10
        return Solar.fromYmdHms(y, m, d, self.__hour, self.__minute, self.__second)

    def next(self, days, only_work_day=False):
        """
        获取往后推几天的阳历日期，如果要往前推，则天数用负数
        :param days: 天数
        :param only_work_day: 是否仅工作日
        :return: 阳历日期
        """
        if not only_work_day:
            return self.nextDay(days)
        solar = Solar.fromYmdHms(self.__year, self.__month, self.__day, self.__hour, self.__minute, self.__second)
        if days != 0:
            rest = abs(days)
            add = 1
            if days < 0:
                add = -1
            while rest > 0:
                solar = solar.next(add)
                work = True
                holiday = HolidayUtil.getHoliday(solar.getYear(), solar.getMonth(), solar.getDay())
                if holiday is None:
                    week = solar.getWeek()
                    if 0 == week or 6 == week:
                        work = False
                else:
                    work = holiday.isWork()
                if work:
                    rest -= 1
        return solar

    def getYear(self):
        return self.__year

    def getMonth(self):
        return self.__month

    def getDay(self):
        return self.__day

    def getHour(self):
        return self.__hour

    def getMinute(self):
        return self.__minute

    def getSecond(self):
        return self.__second

    def toYmd(self):
        return "%04d-%02d-%02d" % (self.__year, self.__month, self.__day)

    def toYmdHms(self):
        return "%s %02d:%02d:%02d" % (self.toYmd(), self.__hour, self.__minute, self.__second)

    def toFullString(self):
        s = self.toYmdHms()
        if self.isLeapYear():
            s += " 闰年"
        s += " 星期"
        s += self.getWeekInChinese()
        for f in self.getFestivals():
            s += " (" + f + ")"
        for f in self.getOtherFestivals():
            s += " (" + f + ")"
        s += " "
        s += self.getXingZuo()
        s += "座"
        return s

    def toString(self):
        return self.toYmd()

    def __str__(self):
        return self.toString()

    def subtract(self, solar):
        return SolarUtil.getDaysBetween(solar.getYear(), solar.getMonth(), solar.getDay(), self.__year, self.__month, self.__day)

    def subtractMinute(self, solar):
        days = self.subtract(solar)
        cm = self.__hour * 60 + self.__minute
        sm = solar.getHour() * 60 + solar.getMinute()
        m = cm - sm
        if m < 0:
            m += 1440
            days -= 1
        m += days * 1440
        return m

    def isAfter(self, solar):
        if self.__year > solar.getYear():
            return True
        if self.__year < solar.getYear():
            return False
        if self.__month > solar.getMonth():
            return True
        if self.__month < solar.getMonth():
            return False
        if self.__day > solar.getDay():
            return True
        if self.__day < solar.getDay():
            return False
        if self.__hour > solar.getHour():
            return True
        if self.__hour < solar.getHour():
            return False
        if self.__minute > solar.getMinute():
            return True
        if self.__minute < solar.getMinute():
            return False
        return self.__second > solar.getSecond()

    def isBefore(self, solar):
        if self.__year > solar.getYear():
            return False
        if self.__year < solar.getYear():
            return True
        if self.__month > solar.getMonth():
            return False
        if self.__month < solar.getMonth():
            return True
        if self.__day > solar.getDay():
            return False
        if self.__day < solar.getDay():
            return True
        if self.__hour > solar.getHour():
            return False
        if self.__hour < solar.getHour():
            return True
        if self.__minute > solar.getMinute():
            return False
        if self.__minute < solar.getMinute():
            return True
        return self.__second < solar.getSecond()

    def nextYear(self, years):
        y = self.__year + years
        m = self.__month
        d = self.__day
        if 1582 == y and 10 == m:
            if 4 < d < 15:
                d += 10
        elif 2 == m:
            if d > 28:
                if not SolarUtil.isLeapYear(y):
                    d = 28
        return Solar.fromYmdHms(y, m, d, self.__hour, self.__minute, self.__second)

    def nextMonth(self, months):
        from . import SolarMonth
        month = SolarMonth.fromYm(self.__year, self.__month).next(months)
        y = month.getYear()
        m = month.getMonth()
        d = self.__day
        if 1582 == y and 10 == m:
            if 4 < d < 15:
                d += 10
        else:
            days = SolarUtil.getDaysOfMonth(y, m)
            if d > days:
                d = days
        return Solar.fromYmdHms(y, m, d, self.__hour, self.__minute, self.__second)

    def nextHour(self, hours):
        h = self.__hour + hours
        n = 1
        if h < 0:
            n = -1
        hour = abs(h)
        days = int(hour / 24) * n
        hour = (hour % 24) * n
        if hour < 0:
            hour += 24
            days -= 1
        solar = self.next(days)
        return Solar.fromYmdHms(solar.getYear(), solar.getMonth(), solar.getDay(), hour, solar.getMinute(), solar.getSecond())
