<?php

declare(strict_types=1);

if (!function_exists('easter_date')) {
    /**
     * replacement for missing `easter_date` function
     * (!): Gregorian dates are fine, but only works
     *      for Julian dates between 1900-2100
     *
     * @param int $year
     *
     * @return false|int
     * @see https://stackoverflow.com/a/3589696
     */
    function easter_date(int $year): bool|int
    {
        return mktime(
            0,
            0,
            0,
            (int)floor(($b =
                 ($a = (19 * (($y = $year) % 19) + 15) % 30) +
                 (2 * ($y % 4) + 4 * $y % 7 - $a + 34) % 7 + 114
             ) / 31),
            ($b % 31) + 14,
            $y
        );
    }
}

class FeastInfo
{
    /**
     * determine feast-day info for given date (timestamp)
     *
     * @param $tDay int|null timestamp of day to check for feast-day info 
     * @return array(
     *  'text' => '' Text
     *  'feast' => boolean true -> feast days
     *  'arState' => '' list of regions/federal states where this feast day applies, empty = all
     * )
     *
     * list of regions/federal states
     *  BW = Baden-Württemberg
     *  BY = Bayern
     *  BE = Berlin
     *  BB = Brandenburg
     *  HB = Bremen
     *  HH = Hamburg
     *  HE = Hessen
     *  MV = Mecklenburg-Vorpommern
     *  NI = Niedersachsen
     *  NW = Nordrhein-Westfalen
     *  RP = Rheinland-Pfalz
     *  SL = Saarland
     *  SN = Sachsen
     *  ST = Sachsen-Anhalt
     *  SH = Schleswig-Holstein
     *  TH = Thüringen
     */
    public static function getFeastInfo(int|null $tDay)
    {

        $md = date('md', $tDay);
        $tEaster = easter_date((int) date('Y', $tDay));

        $feast = '';
        $arState = [];

        // feast days per region
        if ($md === '0101') {
            $feast = trans('Neujahr');
        } elseif ($md === '0501') {
            $feast = trans('Erster Mai');
        } elseif ($md === '0106') {
            $feast = trans('Heiligen Drei Könige');
            $arState = ['BW', 'BY', 'ST'];
        } elseif ($md === date('md', strtotime('-2 day', $tEaster))) {
            $feast = trans('Karfreitag');
        } elseif ($md === date('md', $tEaster)) {
            $feast = trans('Ostersonntag');
        } elseif ($md === date('md', strtotime('+1 day', $tEaster))) {
            $feast = trans('Ostermontag');
            $arState = ['SN'];
        } elseif ($md === date('md', strtotime('+39 day', $tEaster))) {
            $feast = trans('Christi Himmelfahrt');
        } elseif ($md === date('md', strtotime('+49 day', $tEaster))) {
            $feast = trans('Pfingstsonntag');
        } elseif ($md === date('md', strtotime('+50 day', $tEaster))) {
            $feast = trans('Pfingstmontag');
        } elseif ($md === date('md', strtotime('+60 day', $tEaster))) {
            $feast = trans('Fronleichnam');
            $arState = ['BW', 'BY', 'HE', 'NW', 'RP', 'SL', 'SN', 'TH'];
        } elseif ($md === '0815') {
            $feast = trans('Maria Himmelfahrt');
            $arState = ['SL', 'BY'];
        } elseif ($md === '1003') {
            $feast = trans('Tag der deutschen Einheit');
        } elseif ($md === '1031') {
            $feast = trans('Reformationstag');
            $arState = ['BB', 'MV', 'SN', 'ST', 'TH'];
        } elseif ($md === '1101') {
            $feast = trans('Allerheiligen');
            $arState = ['BW', 'BY', 'NW', 'RP', 'SL'];
        } elseif ($md === date('md', strtotime('last wednesday', mktime(0, 0, 0, 11, 23, (int) date('Y', $tDay))))) {
            $feast = trans('Buß- und Bettag');
            $arState = ['SN'];
        } elseif ($md === '1224') {
            $feast = trans('Heiliger Abend');
        } elseif ($md === '1225') {
            $feast = trans('1. Weihnachtsfeiertag');
        } elseif ($md === '1226') {
            $feast = trans('2. Weihnachtsfeiertag');
        } elseif ($md === '1231') {
            $feast = trans('Silvester (Bankfeiertag)');
        } elseif ($md === '0803') {
            $feast = trans('Internationaler Frauentag');
            $arState = ['BE'];
        }

        $text = '';
        if (date('N', $tDay) === '6') {
            $text = 'Samstag';
        } elseif (date('N', $tDay) === '7') {
            $text = 'Sonntag';
        }

        if (sizeof($arState) > 0) {
            $arStateMapping = [
                'BW' => 'Baden-Württemberg',
                'BB' => 'Brandenburg',
                'BY' => 'Bayern',
                'BE' => 'Berlin',
                'HB' => 'Bremen',
                'HH' => 'Hamburg',
                'HE' => 'Hessen' ,
                'MV' => 'Mecklenburg-Vorpommern',
                'NI' => 'Niedersachsen',
                'NW' => 'Nordrhein-Westfalen',
                'RP' => 'Rheinland-Pfalz',
                'SL' => 'Saarland',
                'SN' => 'Sachsen',
                'ST' => 'Sachsen-Anhalt',
                'SH' => 'Schleswig-Holstein',
                'TH' => 'Thüringen'
            ];

            foreach ($arState as $k => $v) {
                $arState[$k] = $arStateMapping[$v];
            }
        }

        if ($feast === '' && $text === '') {
            $text = 'Arbeitstag';
        } elseif ($feast !== '' && $text !== '') {
            $text .= ' (' . $feast . rtrim(', ' . implode(', ', $arState), ' ,') . ')';
        } elseif ($feast !== '') {
            $text = $feast . rtrim(', ' . implode(', ', $arState), ' ,');
        }

        return [
            'text' => $text,
            'feast' => ($feast !== ''),
            'arState' => $arState
        ];
    }

    /**
     * determine next non feast-day from given date
     *
     * @param int|null $tDay
     *
     * @return int|null
     */
    public static function findNextWorkDay(int|null $tDay)
    {
        $fDay = self::getFeastInfo($tDay);
        if (($fDay['feast'] === true) || self::isWeekend($tDay)) {
            return self::findNextWorkDay($tDay + (24 * 60 * 60));
        }
        return $tDay;
    }

    /**
     * determine last feast-day up to given date
     *
     * @param int|null $tDay
     *
     * @return int|null
     */
    public static function findLastWorkDay(int|null $tDay)
    {
        $fDay = self::getFeastInfo($tDay);
        if (($fDay['feast'] === true) || self::isWeekend($tDay)) {
            return self::findLastWorkDay($tDay - (24 * 60 * 60));
        }
        return $tDay;
    }

    /**
     * determine last feast-day up to given date
     *
     * @param int|null $tDay
     *
     * @return int|null
     */
    public static function findLastFeastDay(int|null $tDay)
    {
        $fDay = self::getFeastInfo($tDay);
        if ($fDay['feast'] !== true) {
            return self::findLastFeastDay($tDay - (24 * 60 * 60));
        }
        return $tDay;
    }

    /**
     * determine given day is a weekend day
     *
     * @param int|null $tDay
     *
     * @return bool
     */
    public static function isWeekend(int|null $tDay)
    {
        return in_array((int) date('N', $tDay), [6, 7], true);
    }
}