<?php
/**
* @description: Skript, welches Feiertage ermittelt.
* @author: Jörg Reinholz, fastix WebDesign & Consult, Kassel - http://www.fastix.org/
* @version: 0.8.0 (BETA)
* @needed php >= 5.2.1 optimal: 64bit
**/


class feiertage
{
    private $iniFile = 'deutscheFeiertage.ini';
    private $jahr = false;
    private $advent4;
    private $feiertage = array();
    protected $cacheFile;
    protected $cacheFileName;
    protected $code;
    protected $arrShowOnlyTypes = false;
    protected $monthNames = array();
    protected $wdNames = array();

    protected $wdValues = array();

    public function __construct( $jahr = false ) {
        $this -> setJahr( $jahr );
        $this -> wdNames['0'] = $wdNames['7'] = 'Sonntag';
        $this -> wdNames[1] = 'Montag';
        $this -> wdNames[2] = 'Dienstag';
        $this -> wdNames[3] = 'Mittwoch';
        $this -> wdNames[4] = 'Donnerstag';
        $this -> wdNames[5] = 'Freitag';
        $this -> wdNames[6] = 'Sonnabend';

        $this -> monthNames[0] = 'Januar';
        $this -> monthNames[1] = 'Februar';
        $this -> monthNames[2] = 'März';
        $this -> monthNames[3] = 'April';
        $this -> monthNames[4] = 'Mai';
        $this -> monthNames[5] = 'Juni';
        $this -> monthNames[6] = 'Juli';
        $this -> monthNames[7] = 'August';
        $this -> monthNames[8] = 'September';
        $this -> monthNames[9] = 'Oktober';
        $this -> monthNames[10] = 'November';
        $this -> monthNames[11] = 'Dezember';
    }

    public function easterDate( $y ) {
        return ( mktime(0, 0, 0, 3, 21 + easter_days( $y ), $y ) );
    }

    public function wdnameToValue( $str ) {
        $wdValues['mo'] = 1;
        $wdValues['mon'] = 1;
        $wdValues['monday'] = 1;
        $wdValues['montag'] = 1;

        $wdValues['di'] = 2;
        $wdValues['tue'] = 2;
        $wdValues['tuesday'] = 2;
        $wdValues['mittwoch'] = 2;

        $wdValues['mi'] = 3;
        $wdValues['wed'] = 3;
        $wdValues['wednesday'] = 3;
        $wdValues['mittwoch'] = 3;

        $wdValues['do'] = 4;
        $wdValues['thu'] = 4;
        $wdValues['thursday'] = 4;
        $wdValues['donnerstag'] = 4;

        $wdValues['fr'] = 5;
        $wdValues['fre'] = 5;
        $wdValues['freeday'] = 5;
        $wdValues['freitag'] = 5;

        $wdValues['sa'] = 6;
        $wdValues['sat'] = 6;
        $wdValues['Saturday'] = 6;
        $wdValues['Samstag'] = 6;
        $wdValues['Sonnabend'] = 6;

        $wdValues['so'] = 7;
        $wdValues['son'] = 7;
        $wdValues['sonday'] = 7;
        $wdValues['sonntag'] = 7;

        $s = strtolower( trim( $str ) );
        if ( isset( $wdValues[$s] ) ) {
            return $wdValues[$s];
        } else {
            return $str;
        }
    }


    public function setJahr( $jahr = false ) {

        if ( false === $jahr) { $jahr = date( 'Y' ); }

        if ( $jahr !== $this -> jahr ) {
                $this -> jahr = $jahr;
        }
        $this -> cacheFileName = $this -> iniFile . '.php';
        $this -> cacheFile = __DIR__ . '/cache/' . $this -> jahr . '-' . $this -> iniFile . '.json';

        $iniFileDate = filemtime( $this -> iniFile );
        if ( is_file( $this -> cacheFile ) ) {
            $cacheFileDate = filemtime( $this -> cacheFile );
        }

        if ( ! isset( $cacheFileDate ) or $cacheFileDate < $iniFileDate ) {

            try {
                if ( ! mktime(1, 1, 1, 12, 31, $jahr ) ) {
                    throw new Exception('Das übergebene Jahr ' . $jahr . ' ist auf 32-Bit-Systemen ungültig. Gültig sind Jahre zwischen 1970 und 2037.');
                }
            } catch ( Exception $e ) {
                echo 'Fatal: ',  $e->getMessage(), ' Datei: ' , $e->getFile(), ' Zeile: ', $e->getLine(), "\n";
                exit;
            }


            if ( ! isset($ini) ) {
                $ini = parse_ini_file( $this -> iniFile, true );
            }

            $fromAdvent4 = $ini['fromAdvent4'];
            $dLastAdvent = 24 - date( 'w', mktime( 0, 0, 0, 12, 24, $jahr ) );
            $mLastAdvent = 12;

            for ( $i = 0; $i < count( $fromAdvent4['name'] ); $i++ ) {
                $offset = $fromAdvent4 ['offset'][$i];
                $dto = mktime ( 0, 0, 0, $mLastAdvent, ( $dLastAdvent + $offset ), $this -> jahr );
                list( $m, $d ) = explode( '-', date( 'n-j', $dto ) );
                $d = date( 'j', $dto );
                $notBefore = intval( $fromAdvent4['notBefore'][$i] );
                if ( $notBefore == 0 ) { $notBefore = -99999999; }
                $notAfter = intval( $fromAdvent4['notAfter'][$i] );
                if ( $notAfter == 0 ) { $notAfter = 99999999; }
                if ( $notBefore <= $jahr && $notAfter >= $jahr ) {
                    $name =   $fromAdvent4['name'][$i];
                    if ( isset( $fromAdvent4['codes'][$i] ) ) {
                        $codes = preg_replace('/[^A-Za-z0-9_]+/' , ',' ,$fromAdvent4['codes'][$i]);
                        $arCodes = explode( ',', $codes ) ;
                    }
                    $this -> addFeiertag (intval($m), intval($d), $name, $arCodes);
                }
            }

            $fromEaster = $ini['fromEaster'];
            $dtoEasterSonnday =  $this -> easterDate( $this -> jahr);
            $mEasterSonnday = date('n', $dtoEasterSonnday);
            $dEasterSonnday = date('j', $dtoEasterSonnday);

            for ( $i = 0; $i < count($fromEaster['name']); $i++ ) {
                $notBefore = intval( $fromEaster['notBefore'][$i] );
                if ( $notBefore == 0 ) { $notBefore = -99999999; }
                $notAfter = intval( $fromEaster['notAfter'][$i] );
                if ( $notAfter == 0 ) { $notAfter = 99999999; }
                if ( $notBefore <= $jahr && $notAfter >= $jahr ) {
                    $name =   $fromEaster['name'][$i];
                    $offset = $fromEaster['offset'][$i];
                    $dto = mktime ( 0, 0, 0, $mEasterSonnday, ($dEasterSonnday + $offset), $this -> jahr );
					list( $m, $d ) = explode( '-', date( 'n-j', $dto ) );
                    $arCodes = array();
                    if (  isset( $fromEaster['codes'][$i] ) ) {
                        $codes = preg_replace('/[^A-Za-z0-9_]+/' , ',' ,$fromEaster['codes'][$i]);
                        $arCodes = explode( ',', $codes ) ;
                    }
                    $this -> addFeiertag (intval($m), intval($d), $name, $arCodes);
                }
            }

            $fixDays = $ini['fix'];
            for ( $i = 0; $i < count($fixDays['name']); $i++ ) {
                $m    = intval( $fixDays['m'][$i] );
                $d    = intval( $fixDays['d'][$i] );
                $notBefore = intval( $fixDays['notBefore'][$i] );
                if ( $notBefore == 0 ) { $notBefore = -99999999; }
                $notAfter = intval( $fixDays['notAfter'][$i] );
                if ( $notAfter == 0 ) { $notAfter = 99999999; }
                if ( $notBefore <= $jahr && $notAfter >= $jahr ) {
                    $name = $fixDays['name'][$i];
                    $arCodes=array();
                    if ( isset( $fixDays['codes'][$i] ) ) {
                        $codes = preg_replace('/[^A-Za-z0-9_]+/' , ',' ,$fixDays['codes'][$i]);
                        $arCodes = explode( ',', $codes ) ;
                    }
                    $this -> addFeiertag (intval($m), intval($d), $name, $arCodes);
                }
            }

            $MO_WT_WO = $ini['MO_WT_WO'];
            for ( $i = 0; $i < count( $MO_WT_WO['name'] ); $i++ ) {
                $notBefore = intval( $MO_WT_WO['notBefore'][$i] );
                if ( $notBefore == 0 ) { $notBefore = -99999999; }
                $notAfter = intval( $MO_WT_WO['notAfter'][$i] );
                if ( $notAfter == 0 ) { $notAfter = 99999999; }
                if ( $notBefore <= $jahr && $notAfter >= $jahr ) {
                    $ar = explode( ',', $MO_WT_WO['crontab'][$i] );
                    if (3==count($ar)) {
                        list( $m, $wt, $wo ) = $ar;
                        $dayOffset=0;
                    } elseif ( 4 == count( $ar ) ) {
                        list($m, $wt, $wo, $dayOffset) = $ar;
                    }
                    unset ($ar);
                    if ( 'string' == gettype($wo) ) { $wo = strtoupper( trim($wo) ); }
                    if ( 'F' == $wo or 'FIRST' == $wo ) {
                        $wo = 1;
                    }
                    $wt = $this -> wdnameToValue($wt);
                    if ( 0 == intval($wt) ) { $wt = 7; }
                    $wt = 7 - $wt;
                    if ('L' == $wo or 'LAST' == $wo) {
                        $daysOfMonth  = date( 't', mktime(0,0,0, $m, 1, $jahr) );
                        $wdOffset = date( 'w', mktime(0,0,0, $m, $daysOfMonth, $jahr) );
                        $dto = mktime( 0,0,0, $m, $daysOfMonth - $wdOffset + $dayOffset, $jahr );
                        list( $m, $d ) = explode( '-', date('n-j', $dto ) );
                    } else {
                        $wdOffset = date('w', mktime(0,0,0, $m, $wt, $jahr));
                        $datum = mktime(0,0,0, $m, ( - $offset + ($wo * 7)),$jahr );
                        $dto   = mktime(0,0,0, $m, ( $wo * 7  - $wdOffset )  + $dayOffset, $jahr);
						list( $m, $d ) = explode( '-', date( 'n-j', $dto ) );
                    }
                    $name = $MO_WT_WO['name'][$i];
                    $arCodes=array();
                    if ( isset( $MO_WT_WO['codes'][$i] ) ) {
                        $codes = preg_replace('/[^A-Za-z0-9_]+/' , ',' ,$MO_WT_WO['codes'][$i]);
                        $arCodes = explode( ',', $codes ) ;
                    }
                    $this -> addFeiertag ( intval($m), intval($d), $name, $arCodes );
                }
            }
            $Anniversaries = $ini['Anniversaries'];
            $leer=array();
            for ( $i = 0; $i < count($Anniversaries['y'] ); $i++ ) {
				$n = $jahr - $Anniversaries['y'][$i];
				if (  $n == 0 ) {
					$name = str_replace( '%d. ', '', $Anniversaries['s'][$i] );
					$this -> addFeiertag ( intval($Anniversaries['m'][$i] ), intval( $Anniversaries['d'][$i] ), $name, $leer );
				} elseif (  $n > 0 ) {
					$name = sprintf( $Anniversaries['s'][$i], $n, $Anniversaries['y'][$i], $Anniversaries['m'][$i], $Anniversaries['d'][$i] );
					$this -> addFeiertag ( intval( $Anniversaries['m'][$i] ), intval( $Anniversaries['d'][$i] ), $name, $leer );
				}
            }


            $periodics = $ini['OtherPeriods'];
            $leer = array();
            for ( $i = 0; $i < count($periodics['name']); $i++ ) {
                $flag = true;
                $startDate   = mktime( 0,0,0, $periodics['FirstDay'][$i], $periodics['FirstMounth'][$i], $periodics['FirstYear'][$i] );
                if ( $periodics['FirstYear'][$i] == $jahr) {
                    $this -> addFeiertag( date('m', $startDate ), date( 'j', $startDate ), $periodics['name'][$i], $leer );
                }
                if ( $periodics['FirstYear'][$i] <= $jahr ) {
                    while ( $flag ) {
                        $startDate = mktime(0,0,0, date( 'm', $startDate ) + $periodics['PeriodMounths'][$i], date( 'j', $startDate ) + $periodics['PeriodDays'][$i], date( 'Y', $startDate ) );
                        if ( date('Y', $startDate) == $jahr) {
                            $this -> addFeiertag ( date('m', $startDate ), date( 'j', $startDate ), $periodics['name'][$i], $leer );
                        } elseif ( date('Y', $startDate ) > $jahr ) {
                            $flag = false;
                        }
                    }
                }
            }
            $this -> codes = $ini['codes'];
			$export=[];
            $export['feiertage']= $this -> feiertage;
            $export['codes']     = $this -> codes;
            file_put_contents( $this -> cacheFile , json_encode( $export ) );
            unset( $export );
            return true;
        } else {
            $this -> jahr = $jahr;
            $import = json_decode( file_get_contents( $this -> cacheFile ), true );
            $this -> feiertage = $import['feiertage'];
            $this -> codes     = $import['codes'];
            unset( $import );
            return true;
        }
    }

    public function getJahr() {
        return $this -> jahr;
    }

    public function getDescriptionPerMD ( $m, $d ) {
        $aRet = array();
        $m    = intval( $m );
        $d    = intval( $d );
        try {
            if ( ! checkdate( $m , $d , $this -> jahr ) ) {
                throw new Exception( 'Das übergebene Datum ' . $d . '.' . $m . '.' . $this -> jahr . ' ist ungültig.' );
                return false;

            }
        } catch ( Exception $e ) {
            echo 'Notiz: ',  $e->getMessage(), ' Datei: ' , $e->getFile(), ' Zeile: ', $e->getLine(), "\n";
        }
        $arReturns = array();
        if ( isset( $this -> feiertage[$m][$d] ) ) {
            if ( false == $this -> arrShowOnlyTypes ) {
                foreach( $this -> feiertage[$m][$d] as $found) {
                    $aRet = array();
                    $aRet['name'] = $found['name'];
                    $aRet['codes'] = $found['codes'];
                    $arReturns[] = $aRet;
                }
            } else {
                foreach( $this -> feiertage[$m][$d] as $found) {
                #print_r($found);
                    foreach ( $this -> arrShowOnlyTypes as $mustHave ) {
                        #echo ' $mustHave: ', $mustHave, "\n";
                        if ( $found['codes'] && in_array( $mustHave, $found['codes'] ) ) {
                            $aRet = array();
                            $aRet['name']  = $found['name'];
                            $aRet['codes'] = $found['codes'];
                            $arReturns[] = $aRet;
                            break;
                        }
                    }
                }
            }
        }
        if ( count ( $arReturns ) ) {
            return $arReturns;
        }
    }

    public function setArrShowOnlyTypes ( $var ) {
        $this -> arrShowOnlyTypes = array();
        if (  'array' != gettype($var) ) {
            $var = preg_replace( '/[^A-Za-z0-9_]+/' , ',' , $var );
            $var = explode( ',', $var );
        }
        $arr2 = array();
        foreach ($var as $s ) {
            if ( '*' == $s ) {
                $this -> arrShowOnlyTypes = false;
                return true;
            } else {
                if ('' != $s) {
                    $arr2[] = strtoupper( trim( $s ) );
                }
            }
        }
        if ( count( $arr2 ) ) {
            $this -> arrShowOnlyTypes = $arr2;
        } else {
            $this -> arrShowOnlyTypes = false;
        }
    }

    public function getArrShowOnlyTypes () {
        return $this -> arrShowOnlyTypes;
    }

    private function addFeiertag ( $m, $d, $name, $arCodes ) {
		if ( 
			29 == $d &&
			2  == $m &&
			0 == date( 'L', mktime( 0, 0, 0, 2, 1, $this->jahr ) ) 
		)   {
				$d=1; $m=3;
		}

        $arCodes2 = array();
        foreach ($arCodes as $s) {
            $s = trim($s);
            if ($s) {
                $arCodes2[]=$s;
            }
        }
        $ar['codes']= $arCodes2;
        $ar['name'] = $name;
        $this -> feiertage[ intval( $m )][intval( $d )][] = $ar;
        return true;
    }

    public function getDescriptionPerDTO ( $dto ) {
		list( $m, $d, $y ) = explode( '-',  date( 'n-j-Y', $dto ) );
        return $this -> getDescriptionPerMD ( $m, $d, $y );
    }

    public function isFeiertagPerDTO ( $dto, $mustHave ) {
		list( $d, $m, $y ) = explode( '-', date('j-n-Y', $dto) );
        try {
            if ( ! checkdate( $m , $d , $this -> jahr ) ) {
                throw new Exception( 'Das übergebene Datum ' . $d . '.' . $m . '.' . $this -> jahr . ' ist ungültig.' );
                return false;
            }
        } catch ( Exception $e ) {
            echo 'Notiz: ',  $e->getMessage(), ' Datei: ' , $e->getFile(), ' Zeile: ', $e->getLine(), "\n";
        }

        return $this -> isFeiertagPerMD( $mustHave, $m, $d, $y );
    }

    public function isFeiertagPerMD($m, $d, $y=false ) {

        $m = intval($m);
        $d = intval($d);

        if ( $y !== false ) {
            $y = intval($y);
            if ( $y != $this -> jahr )  {
                $this -> setJahr( intval( $y ) );
            }
        }

        if ( isset( $this -> feiertage[$m][$d] ) ) {
            if ( false == $this -> arrShowOnlyTypes ) {
                foreach ( $this -> feiertage[$m][$d] as $found ) {
                    return $found['name'];
                }
            } else {
                foreach ( $this -> feiertage[$m][$d] as $found ) {
                    foreach ( $this -> arrShowOnlyTypes as $mustHave ) {
                        if ( $found['codes'] && in_array( $mustHave, $found['codes'] )  ) {
                            return  $found['name'];
                        }
                    }
                }
            }
        }
        return false;
    }

    public function getWdName ($wd) {
        if ( isset($this -> wdNames[$wd]) ) {
            return $this -> wdNames[$wd];
        } else {
            error_log(__FILE__ . 'function: getWdName('.$wd.') - ungültiger Parameter');
        }
    }

    public function getMonthName ($d, $start=1) {
        $ds = $d - $start;
        if ( isset($this -> monthNames[$ds]) ) {
            return $this -> monthNames[$ds];
        } else {
            error_log(__FILE__ . 'function: getMonthName('.$d.','.$start.') - ungültiger Parameter' );
        }
    }

    public function countKalendarItems () {
        $c=0;
        foreach ($this -> feiertage as $day) {
            foreach ($day as $ereignisse) {
                foreach($ereignisse as $ereignis) {
                    if (! count( $ereignis['codes'] ) ) {
                        $c++;
                    } else {
                        $c = $c + count($ereignis['codes']);
                    }
                }
            }
        }
        return $c;
    }

}

// constants definition for hhvm

$arr = [
		'CAL_EASTER_DEFAULT',
		'CAL_EASTER_ROMAN',
		'CAL_EASTER_ALWAYS_GREGORIAN',
		'CAL_EASTER_ALWAYS_JULIAN'
];

for ( $i=0; $i < count( $arr ); $i++ ) {
	if( ! defined( $arr[$i] ) ) {	
		define( $arr[$i], $i );
	}
}


if ( ! function_exists( 'easter_days' ) ) {

    /**
    *    code by rabbit-aaron, https://github.com/rabbit-aaron
    *    based on code by Simon Kershaw, <webmaster@ely.anglican.org>
    *    License: https://github.com/rabbit-aaron/hhvm-php-easter/blob/master/LICENSE
    **/

    function easter_days( $year = NULL, $method = CAL_EASTER_DEFAULT )  {

        /**
            gibt den offset (in Tagen) zum 21.3. zurück.
        **/

        if( $year == NULL ) {
            $year = intval( date( 'Y' ) );
        }
        $golden = ( $year % 19 ) + 1;
        if ( ( $year <= 1582 && $method != CAL_EASTER_ALWAYS_GREGORIAN ) ||
            ( $year >= 1583 && $year <= 1752 && $method != CAL_EASTER_ROMAN && $method != CAL_EASTER_ALWAYS_GREGORIAN ) ||
            $method == CAL_EASTER_ALWAYS_JULIAN ) {

            $dom = ( $year + (int)($year/4) + 5) % 7;
            if ( $dom < 0 ) {
                $dom += 7;
            }
            $pfm = ( 3 - ( 11 * $golden ) - 7 ) % 30;
            if ($pfm < 0) {
                $pfm += 30;
            }
        } else {
            $dom = ( $year + (int)( $year / 4 ) - (int)( $year / 100 ) + (int)( $year / 400 ) ) % 7;
            if ( $dom < 0 ) {
                $dom += 7;
            }
            $solar = (int)( ( $year - 1600 ) / 100 ) - (int)( ( $year - 1600 ) / 400) ;
            $lunar = (int)( ( ( (int)( ($year - 1400 ) / 100 ) ) * 8 ) / 25 );
            $pfm = ( 3 - ( 11 * $golden ) + $solar - $lunar ) % 30;
            if ( $pfm < 0 ) {
                $pfm += 30;
            }
        }
        if ( ( $pfm == 29 ) || ( $pfm == 28 && $golden > 11 ) ) {
            $pfm--;
        }
        $tmp = ( 4 - $pfm - $dom ) % 7;
        if ( $tmp < 0 ) {
            $tmp += 7;
        }
        $easter = $pfm + $tmp + 1;               /* Easter as the number of days after 21st March */
        return $easter;
    }
}