<?PHP
/**
 * Splits nodac times.
 *
 * @author Joshua Ramon Enslin <joshua@museum-digital.de>
 */
declare(strict_types = 1);

/**
 * Class for splitting times.
 */
final class NodaTimeSplitter {

    const MONTH_NAMES_GERMAN = [
        "01" => ['Januar', 'Jan.'],
        "02" => ['Februar', 'Feb'],
        "03" => ['März', 'Mrz.'],
        "04" => ['April', 'Apr.'],
        "05" => ['Mai'],
        "06" => ['Juni', 'Jun.'],
        "07" => ['Juli', 'Jul.'],
        "08" => ['August', 'Aug.'],
        "09" => ['September', 'Sep.', 'Sept.'],
        "10" => ['Oktober', 'Okt.'],
        "11" => ['November', 'Nov.'],
        "12" => ['Dezember', 'Dez.'],
    ];

    const MONTH_NAMES_HUNGARIAN = [
        "01" => ['január', 'jan'],
        "02" => ['február', 'feb'],
        "03" => ['március', 'mar.'],
        "04" => ['április', 'apr.'],
        "05" => ['május', 'maj.'],
        "06" => ['június', 'jun.'],
        "07" => ['július', 'jul.'],
        "08" => ['augusztus', 'aug.'],
        "09" => ['szeptember', 'szp.'],
        "10" => ['október', 'okt.'],
        "11" => ['november', 'nov.'],
        "12" => ['december', 'dec.'],
    ];

    const STOP_STRINGS_GERMAN = [
        "-",
        ",",
        ";",
        ":",
        "/",
        "(", ")",
        "[", "]",
        ", ",
        " und ",
        "nach ",
        "um ",
        "ca.",
        "ab ",
        "seit ",
        "bis ",
        "vor ",
        "anfang ",
        "ende ",
    ];

    const STOP_STRINGS_HUNGARIAN = [
        "-",
        ",",
        ";",
        ":",
        "/",
        "(", ")",
        "[", "]",
        "ca.",
        ", ",
        "-ig",
        "és",
        "eleje",
        "között",
        "töl",
        "januárig",
        "februárig",
        "márciusig",
        "vége",
        "végén",
        "áprilisig",
        "májusig",
        "júniusig",
        "júliusig",
        "augusztusig",
        "szeptemberig",
        "októberig",
        "novemberig",
        "decemberig",
    ];

    /**
     * Cleans input strings by trimming obsolete stuff.
     *
     * @param string $input Input date name.
     *
     * @return string
     */
    private static function clean_input(string $input):string {

        while (strpos($input, " -") !== false) $input = str_replace(" -", "-", $input);
        while (strpos($input, "- ") !== false) $input = str_replace("- ", "-", $input);
        if (stripos($input, "között") !== false) $input = str_replace("között", " ", $input);

        return trim($input, ", [](){}");

    }

    /**
     * Checks if a string is really numeric, not numeric + space, dot.
     *
     * @param string $input Input string.
     *
     * @return boolean
     */
    private static function is_numeric(string $input):bool {

        if (is_numeric($input)
            and strpos($input, " ") === false
            and strpos($input, ".") === false
        ) {
            return true;
        }

        return false;

    }

    /**
     * Validates a time substr.
     *
     * @param string  $datum Date.
     * @param integer $start Start of substr.
     * @param integer $end   End of substr.
     *
     * @return string
     */
    private static function validateDateSubstr(string $datum, int $start, int $end = 10000):string {

        if ($start !== 0
            && !in_array(substr($datum, $start - 1, 1), ["-", " ", "."], true)
        ) {
            return "";
        }

        $output = substr($datum, $start, $end);
        if (self::is_numeric($output)) return $output;
        return "";

    }

    /**
     * Generates new time name based on moda.
     *
     * @param array<string> $moda Date strings.
     *
     * @return string
     */
    public static function timePartsToTimeName(array $moda):string {

        if ($moda[0] !== $moda[1]) {
            return "{$moda[0]}-{$moda[1]}";
        }
        else if (intval($moda[2]) !== 0 and intval($moda[3]) !== 0) {
            return "{$moda[3]}.{$moda[2]}.{$moda[0]}";
        }
        else if ($moda[0] === $moda[1] && trim($moda[2], " 0") === "" && trim($moda[3], " 0") === "") {
            return "{$moda[0]}";
        }

        return "";

    }

    /**
     * Generates counting year - the middle between start and end year.
     *
     * @param array<string> $moda Date strings.
     *
     * @return integer
     */
    public static function timePartsToCountingYear(array $moda):int {

        return (int)ceil(intval($moda[1]) - ((intval($moda[1]) - intval($moda[0])) / 2));

    }

    /**
     * Generates HTML for linking disassembly of times for a single day.
     *
     * @param integer       $znum     Time ID.
     * @param array<string> $moda     Date strings.
     * @param MDTlLoader    $tlLoader Translation loader.
     *
     * @return string
     */
    public static function generateDisassemblyForDay(int $znum, array $moda, MDTlLoader $tlLoader):string {

        $zaehlzeit_jahr = self::timePartsToCountingYear($moda);

        // Wenn Datum in Form von tt.mm.jjjj, dann biete zerlegen an
        $output = '<hr>';
        $output .= '<table>';
        $output .= '<tr><td width="250px">' . $tlLoader->tl("tempi", "tempi", "time_tool") . '</td>';
        $output .= '<td><a href="tempi_md/zeit_cha.php?znum=' . $znum . '&kontrolle=todo';
        if (($newTimeName = self::timePartsToTimeName($moda)) !== "") {
            $output .= "&zeit_name_neu={$newTimeName}";
        }
        $output .= '&zeit_beginn_neu=' . $moda[0] . '&zeit_ende_neu=' . $moda[1] . '&zeit_zaehlzeit_vorzeichen_neu=%2B&zeit_zaehlzeit_jahr_neu=' . $zaehlzeit_jahr . '&zeit_zaehlzeit_monat_neu=' . $moda[2] . '&zeit_zaehlzeit_tag_neu=' . $moda[3] . '&zeit_status_neu=%2B" class="icons iconsBell buttonLike" id="splitTimeLink">+';
        if (!empty(trim($moda[3], " 0")) and !empty(trim($moda[2], " 0"))) $output .= $moda[3] . '.' . $moda[2] . '.' . $moda[0];
        else if ($moda[0] !== $moda[1]) $output .= $moda[0] . "-" . $moda[1];
        else if (!empty(trim($moda[2], " 0"))) $output .= "{$moda[2]}.{$moda[0]}";
        else $output .= $moda[0];
        $output .= ' - ' . $tlLoader->tl("tempi", "tempi", "time_disassemble") . '</a></td>';
        $output .= '</tr>';
        $output .= '</table>';

        return $output;

    }

    /**
     * Checks if any string of a list occurs in the haystack input string.
     *
     * @param string        $haystack Haystack.
     * @param array<string> $needles  Needles.
     *
     * @return boolean
     */
    private static function stri_occurs(string $haystack, array $needles):bool {

        foreach ($needles as $needle) {
            if (stripos($haystack, $needle) !== false) return true;
        }

        return false;

    }

    /**
     * Translate German month to two digits number.
     *
     * @param string $datum Date.
     *
     * @return array<string>
     */
    public static function is_valid_date(string $datum):array {

        $datum = self::clean_input($datum);

        if (self::stri_occurs($datum, self::STOP_STRINGS_GERMAN)) {
            return [];
        }

        if (strlen($datum) <= 9) $use_day = false;
        else $use_day = true;

        foreach (self::MONTH_NAMES_GERMAN as $monthVal => $monthValidNames) {
            if (self::stri_occurs($datum, $monthValidNames)) {
                if (!empty($monat)) return [];
                $monat = (string)$monthVal;
            }
        }

        if (empty($monat) and self::is_numeric((string)substr($datum, 3, 2))) $monat = substr($datum, 3, 2);

        if (self::is_numeric((string)substr($datum, 0, 2))) $day = substr($datum, 0, 2);
        else if (substr($datum, 1, 1) === "." and self::is_numeric((string)substr($datum, 0, 1))) $day = "0" . substr($datum, 0, 1);

        if (self::is_numeric((string)substr($datum, -4))) $year = substr($datum, -4);

        if (!empty($year) and !empty($monat) and !empty($day) and $use_day) {
            return [$year, $year, $monat, $day];
        }
        else if (!empty($year) and !empty($monat)) {
            return [$year, $year, $monat, "00"];
        }
        return [];

    }

    /**
     * Translate Hungarian month to two digits number.
     *
     * @param string $datum Date.
     *
     * @return array<string>
     */
    public static function is_valid_date_hungarian(string $datum):array {

        $datum = self::clean_input($datum);

        if (self::stri_occurs($datum, self::STOP_STRINGS_HUNGARIAN)) {
            return [];
        }

        if (strlen($datum) <= 9) return [];

        foreach (self::MONTH_NAMES_HUNGARIAN as $monthVal => $monthValidNames) {
            if (self::stri_occurs($datum, $monthValidNames)) {
                if (!empty($monat)) return [];
                $monat = (string)$monthVal;
            }
        }

        if (empty($monat) and self::is_numeric((string)substr($datum, 5, 2))) $monat = substr($datum, 5, 2);
        else if (empty($monat) and self::is_numeric((string)substr($datum, 6, 2))) $monat = substr($datum, 6, 2);

        $day = self::validateDateSubstr($datum, -2);
        if (empty($day)) $day = self::validateDateSubstr($datum, -3, 2);
        if (empty($day)) $day = self::validateDateSubstr($datum, -4, 2);
        if (empty($day)) $day = self::validateDateSubstr($datum, -5, 2);
        if (empty($day)) $day = self::validateDateSubstr($datum, -6, 2);

        if (substr($datum, -2, 1) === " " and self::is_numeric((string)substr($datum, -1, 1))) {
            $day = "0" . substr($datum, -1, 1);
        }
        else if ((empty($day)) and substr($datum, -3, 1) === " " and self::is_numeric((string)substr($datum, -2, 1))) {
            $day = "0" . substr($datum, -2, 1);
        }

        if (self::is_numeric((string)substr($datum, 0, 4))) $year = substr($datum, 0, 4);

        if (!empty($year) and !empty($monat) and !empty($day)) {
            return [$year, $year, $monat, $day];
        }
        else if (!empty($year) and !empty($monat)) {
            return [$year, $year, $monat, "00"];
        }
        return [];

    }

    /**
     * Translate German month to two digits number.
     *
     * @param string $datum Date.
     *
     * @return array<string>
     */
    public static function is_valid_date_by_php(string $datum):array {

        $datum = self::clean_input($datum);

        if (!($timeInt = strtotime($datum))) {
            return [];
        }

        return [date("Y", $timeInt), date("m", $timeInt), date("d", $timeInt)];

    }

    /**
     * Checks if an input date is a timespan.
     *
     * @param string $datum Input date.
     *
     * @return array<string>
     */
    public static function is_timespan(string $datum):array {

        $datum = self::clean_input($datum);

        if (preg_match("/^[0-9][0-9][0-9][0-9](-|\/)[0-9][0-9][0-9][0-9]$/", $datum)) {

            $start = substr($datum, 0, 4);
            $end = substr($datum, -4);
            return [$start, $end, "00", "00"];

        }
        if (preg_match("/^[0-9][0-9]\.[0-9]\.[0-9][0-9][0-9][0-9]$/", $datum)) { // German T.MM.JJJJ
            $start = substr($datum, 5, 4);
            $month = "0" . substr($datum, 3, 1);
            $day   = substr($datum, 0, 2);
            return [$start, $start, $month, $day];
        }
        if (preg_match("/^[0-9]\.[0-9][0-9]\.[0-9][0-9][0-9][0-9]$/", $datum)) { // German T.MM.JJJJ
            $start = substr($datum, 5, 4);
            $month = substr($datum, 2, 2);
            $day   = "0" . substr($datum, 0, 1);
            return [$start, $start, $month, $day];
        }
        if (preg_match("/^[0-9]\.[0-9]\.[0-9][0-9][0-9][0-9]$/", $datum)) { // German T.M.JJJJ
            $start = substr($datum, 4, 4);
            $month = "0" . substr($datum, 2, 1);
            $day   = "0" . substr($datum, 0, 1);
            return [$start, $start, $month, $day];
        }
        if (preg_match("/^[0-9][0-9]\.[0-9][0-9][0-9][0-9]$/", $datum)) { // German Y-m
            $start = substr($datum, 3, 4);
            $month = substr($datum, 0, 2);
            return [$start, $start, $month, "00"];
        }
        if (preg_match("/^[0-9]\.[0-9][0-9][0-9][0-9]$/", $datum)) { // German Y-m
            $start = substr($datum, 2, 4);
            $month = "0" . substr($datum, 0, 1);
            return [$start, $start, $month, "00"];
        }
        if (preg_match("/^[0-9][0-9][0-9][0-9]\.[0-9][0-9]$/", $datum)) { // Hungarian Y-m
            $start = substr($datum, 0, 4);
            $month = substr($datum, 5, 2);
            return [$start, $start, $month, "00"];
        }
        if (preg_match("/^[0-9][0-9][0-9][0-9]$/", $datum)) {

            $start = substr($datum, 0, 4);
            return [$start, $start, "00", "00"];

        }
        return [];

    }

    /**
     * Wrapper to check if any splitting command works.
     *
     * @param string $datum Input date.
     *
     * @return array<string>
     */
    public static function attempt_splitting(string $datum):array {

        $moda = NodaTimeSplitter::is_timespan($datum);
        if (!$moda) $moda = NodaTimeSplitter::is_valid_date($datum);
        if (!$moda) $moda = NodaTimeSplitter::is_valid_date_hungarian($datum);

        return $moda;

    }

}