#
# Copyright 2019 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import datetime
from zoneinfo import ZoneInfo

from pandas.tseries.holiday import Easter, EasterMonday, Holiday
from pandas.tseries.offsets import Day

from .common_holidays import (
    all_saints_day,
    christmas,
    christmas_eve,
    european_labour_day,
    new_years_day,
    new_years_eve,
    whit_monday,
)
from .exchange_calendar import THURSDAY, TUESDAY, HolidayCalendar, ExchangeCalendar


ONE_DAY = datetime.timedelta(1)


def bridge_mon(dt: datetime.datetime) -> datetime.datetime | None:
    """Define Monday as holiday if Tuesday is a holiday.

    If a holiday falls on a Tuesday an extra holiday is observed on Monday
    to bridge the weekend and the official holiday.
    """
    return dt - ONE_DAY if dt.weekday() == TUESDAY else None


def bridge_fri(dt: datetime.datetime) -> datetime.datetime | None:
    """Define Friday as holiday if Thrusday is a holiday.

    If a holiday falls on a Thursday an extra holiday is observed on Friday
    to bridge the weekend and the official holiday.
    """
    return dt + ONE_DAY if dt.weekday() == THURSDAY else None


NewYearsDayExtraMon = new_years_day(observance=bridge_mon)
NewYearsDayExtraFri = new_years_day(observance=bridge_fri)

NationalHoliday1 = Holiday("National Day", month=3, day=15)
NationalHoliday1ExtraMon = Holiday(
    "National Day extra Monday", month=3, day=15, observance=bridge_mon
)
NationalHoliday1ExtraFri = Holiday(
    "National Day extra Friday", month=3, day=15, observance=bridge_fri
)

# Need custom start year so can't use pandas GoodFriday
GoodFriday = Holiday(
    "Good Friday", month=1, day=1, offset=[Easter(), Day(-2)], start_date="2012"
)

LabourDayExtraMon = european_labour_day(observance=bridge_mon)
LabourDayExtraFri = european_labour_day(observance=bridge_fri)

WhitMonday = whit_monday()

StStephensDay = Holiday("St. Stephen's Day", month=8, day=20)
StStephensDayExtraMon = Holiday(
    "St. Stephen's Day extra Monday", month=8, day=20, observance=bridge_mon
)
StStephensDayExtraFri = Holiday(
    "St. Stephen's Day extra Friday", month=8, day=20, observance=bridge_fri
)

NationalHoliday2 = Holiday("National Day", month=10, day=23)
NationalHoliday2ExtraMon = Holiday(
    "National Day extra Monday", month=10, day=23, observance=bridge_mon
)
NationalHoliday2ExtraFri = Holiday(
    "National Day extra Friday", month=10, day=23, observance=bridge_fri
)

AllSaintsDayExtraMon = all_saints_day(observance=bridge_mon)
AllSaintsDayExtraFri = all_saints_day(observance=bridge_fri)

# Christmas Eve does not follow the four day weekend rule
ChristmasEve = christmas_eve()

ChristmasDay = christmas()

# XBUD always has a holiday for the second day of Christmas (26th),
# but starting in 2013 if the 26th falls on a Thursday then the
# 27th (Friday) is also taken off
SecondDayOfChristmas = Holiday("Second Day of Christmas", month=12, day=26)
SecondDayOfChristmasExtraFri = Holiday(
    "Second Day of Christmas extra Friday",
    month=12,
    day=26,
    start_date="2013",
    observance=bridge_fri,
)
# SecondDayOfChristmas unnecessary to bridge_mon as if second day of christmas
# is a tuesday then monday will already be a holiday (Chirstmas Day)


# Starting in 2011, New Year's Eve is observed as a holiday every year.
# In some cases pre-2011, the 31st becomes a holiday due to the four day
# weekend rule (when Jan 1 falls on a Tuesday).
# Also, when NYE starts being observed as a holiday it does NOT follow
# the four day weekend rule (no 30ths are holidays)
NewYearsEve = new_years_eve(start_date="2011")


class XBUDExchangeCalendar(ExchangeCalendar):
    """
    Exchange calendar for the Budapest Stock Exchange (XBUD).

    Open Time: 9:00 AM, CET
    Close Time: 5:00 PM, CET

    Regularly-Observed Holidays:
    - New Year's Day
    - National Holiday (Mar 15)
    - Good Friday
    - Easter Monday
    - Labour Day (May 1)
    - Whit Monday (50 days after Easter Sunday)
    - St. Stephen's Day (Aug 20)
    - National Holiday (Oct 23)
    - All Saint's Day (Nov 1)
    - Christmas Eve
    - Christmas Day
    - Second Day of Christmas (Dec 26)
    - New Year's Eve

    Early Closes:
    - None
    """

    name = "XBUD"

    tz = ZoneInfo("Europe/Budapest")

    open_times = ((None, datetime.time(9)),)

    close_times = ((None, datetime.time(17, 00)),)

    @property
    def regular_holidays(self):
        return HolidayCalendar(
            [
                new_years_day(),
                NewYearsDayExtraMon,
                NewYearsDayExtraFri,
                NationalHoliday1,
                NationalHoliday1ExtraMon,
                NationalHoliday1ExtraFri,
                GoodFriday,
                EasterMonday,
                european_labour_day(),
                LabourDayExtraMon,
                LabourDayExtraFri,
                WhitMonday,
                StStephensDay,
                StStephensDayExtraMon,
                StStephensDayExtraFri,
                NationalHoliday2,
                NationalHoliday2ExtraMon,
                NationalHoliday2ExtraFri,
                all_saints_day(),
                AllSaintsDayExtraMon,
                AllSaintsDayExtraFri,
                ChristmasEve,
                ChristmasDay,
                SecondDayOfChristmas,
                SecondDayOfChristmasExtraFri,
                NewYearsEve,
            ]
        )
