<?php

/**
 * Datei: getEvents.php
 * Autor: bernd@nr18.space
 * Letze Änderung: 08.07.2020
 * Kurzbeschreibung:
 * Enthält Funktionen zum Parsen und Ausgeben einer iCalendar Kalenderdatei.
 * Die Bibliothek zum Parsen des iCalendarfiles stammt von ZContent.
 * Der Quelltext liegt auf https://github.com/zcontent/icalendar. Da sie
 * auf calendar.org verlinkt war, dachte ich eigentlich, daß sie gut wäre.
 */
require_once("../src/lib/zapcallib.php");


function printEventList(): bool
{
/**
 * Die Startfuktion für die Ausgabe der Termine als Liste in index.php.
 * Nach erfolgreicher initialisierung läuft die Funktion durch das Array
 * der Events (ZCalNode´s vom Typ 'VEVENT') ruft für jedes Element erst
 * die Funktion zur Umwandlung in ein assoziatives Array und dann die
 * Ausgabefunktion auf. Gibt einen Boolean zurück.
 */
    $events = initEvents();
    $helper = new ZDateHelper();
    date_default_timezone_set("UTC");

    /**
     * Startdatum für wiederkehrende Termine aktualisieren
     *
     * @var ZCiCalNode $event
     */
    foreach ($events as $event)
    {
        /**
         * @var ZCiCalDataNode $eventDTStart
         */
        $eventDTStart = $event->data['DTSTART'];
        /** @var string[] $eventDTStartValues */
        $eventDTStartValues = $eventDTStart->value;
        $event_start = $eventDTStartValues[0] ?? '';
        $unix_start = $helper->fromiCaltoUnixDateTime($event_start);
        $rrule = $event->data['RRULE']->value[0];

        //todo rrule always set
        if (EventIsPast($unix_start) === true AND isset ($rrule))
        {
            $new_unix = calculateNextStart($unix_start, $rrule);
            $date = date('Ymd', $new_unix);
            $time = date('His', $new_unix);
            $connector = 'T';
            $new_start = "{$date}{$connector}{$time}";
            $event->data['DTSTART']->value[0] = $new_start;
        }
    }

    /**
     * Eventliste nach Startdatum sortieren
     */
    usort($events, 'compareEventStart');

    /**
     * Termine ausgeben
     */
    foreach ($events as $event)
    {
        printListItem($event);
    }
    return true;
}

function printListItem(ZCiCalNode $event): bool
/**
 * Die Ausgabefunktion für die Ausgabe der Termine als Liste. Hier wird die
 * Zeitangabe des iCal-Formates in Datum, Uhrzeit und Wochentag umgewandelt.
 * Anschließend erfolgt die Ausgabe der einzelnen Teile.
 */
{
    date_default_timezone_set("UTC");
    $helper = new ZDateHelper();
    $hyph = " - ";
    $uhr = " Uhr";
    $komma = ", ";

    $event_start = $event->data['DTSTART']->value[0];
    $event_url = lowerURL($event->data['URL']->parameter['value']);
    $event_title = $event->data['SUMMARY']->value[0];
    $event_location = trim($event->data['LOCATION']->value[0], '"');
    $event_descr = trim($event->data['DESCRIPTION']->value[0], '"');

    $unix = $helper->fromiCaltoUnixDateTime($event_start);
    $event_date = date('d.m.Y', $unix);
    $event_time = date('H:i', $unix);
    $event_day = toGerman(date('l', $unix));
    $dateline = "{$event_day}{$komma}{$event_date}{$hyph}{$event_time}{$uhr}";

    displayHeadline($dateline, $event_title);
    displayLocation($event_location);
    displayDescription($event_descr);
    displayURL($event_url);
    return true;
}

function displayHeadline(string $dateline, $event_title): bool
{
    echo "\n<section class='termin'>\n";
    echo "<p class='headline'>" . $dateline . ": " . $event_title . "</p>\n";
    return true;
}

function displayLocation(string $location): bool
{
    echo "<ul class='events'>\n";
    echo "<li>$location</li>\n";
    return true;
}

function displayDescription(string $description): bool
{
    echo "<li>$description</li>\n";
    return true;
}

function displayURL(string $url): bool
{
    $stripped = trim($url, '"');
    echo "<li><a href=" . $url . ">" . $stripped . "</a></li>\n";
    echo "</ul>\n";
    echo "</section>\n";
    return true;
}

/**
 *  Die Startfuktion für die Ausgabe der Termine als Tabelle in termine.php
 */

function printEventTable(): bool
{
    $events = initEvents();
    printTableHead();
    foreach ($events as $event)
    {
        $event_array = getEventArray($event);
        printTableItem($event_array);
    }
    echo "\t</tbody>\n";
    echo "</table>\n";
    return true;
}

function printTableHead()
{
    echo "\n\r<table class='w3-table w3-bordered'>\n";
    echo "\t<thead>\n";
    echo "\t<tr>\n";
    echo "\t\t<th>Datum</th>\n";
    echo "\t\t<th>Wochentag</th>\n";
    echo "\t\t<th>Zeit</th>\n";
    echo "\t\t<th>Ort</th>\n";
    echo "\t\t<th>Titel</th>\n";
    echo "\t\t<th>Beschreibung</th>\n";
    echo "\t</tr>\n";
    echo "\t</thead>\n";
    echo "\t<tbody>\n";
}

function printTableItem($event_array): bool
{
    date_default_timezone_set("UTC");
    $time = $event_array['DTSTART'];
    $helper = new ZDateHelper();
    $unix = $helper->fromiCaltoUnixDateTime($time);
    $event_date = date('d.m.Y', $unix);
    $event_time = date('H:i', $unix);
    $event_day = toGerman(date('l', $unix));
    $event_uid = $event_array['UID'];
    $event_url = lowerURL($event_array['URL']);
    $event_title = trim($event_array['SUMMARY'], '"');
    $event_descr = trim($event_array['DESCRIPTION'], '"');
    $event_location = trim($event_array['LOCATION'], '"');
    echo "\t<tr id='" . $event_uid . "'>\n";
    echo "\t\t<td>" . $event_date . "</td>\n";
    echo "\t\t<td>" . $event_day . "</td>\n";
    echo "\t\t<td>" . $event_time . " Uhr</td>\n";
    echo "\t\t<td>" . $event_location . "</td>\n";
    echo "\t\t<td>" . $event_title . "</td>\n";
    echo "\t\t<td>" . $event_descr;
    printURL($event_url);
    echo "\t</tr>\n";
    return true;
}

function printURL(string $url): bool
{
    $stripped = trim($url, '"');
    if ($url != '')
    {
        echo "</br><a class='event' href=" . $url . ">" . $stripped . "</a></td>\n";
    }
    else
    {
        echo "</td>\n";
    }
    return true;
}

/**
 *  Funktionen, die von beiden Ausgaben benutzt werden.
 */

/**
 * @return ZCiCalNode[]|null
 */
function initEvents(): ?array
/**
 * Allgemeingültige Funktion zur Initialisierung. Enthält die Schritte, die
 * von beiden Ausgaben gleichermaßen gebraucht werden.
 *  - Erstellen des iCalendar Objekts (initCalendar).
 *  - Schaut, ob der Kalender überhaupt Events enthält (printEventCount).
 *  - Sammel alle Events in einer Liste (grabEvents).
 *  Gibt zweidimmensionales assoziatives Array oder Null zurück.
 */
{
    $iCalObj = initCalendar();
    if (!isset ($iCalObj))
    {
        printError("Fehler beim Initialisieren des Kalenders");
        return null;
    }
    $count = printEventCount($iCalObj);
    if ($count == 0 or $count == false)
    {
        return null;
    }
    $events = grabEvents($iCalObj);
    return $events;
}

function initCalendar(): ?ZCiCal
/**
 * Erstellt das Kalenderobjekt vom Typ ZCiCal.
 * Gibt das Kalenderobjekt oder Null zurück.
 */
{
    $iCalFile = '../public/krautspace.ics';
    $iCalString = file_get_contents($iCalFile);
    if ($iCalString == false)
    {
        printError("Kann Kalenderdatei nicht lesen");
        return null;
    }
    $iCalObj = new ZCiCal($iCalString);
    return $iCalObj;
}

function printEventCount(ZCiCal $iCalObj): ?int
/**
 * Gibt die Anzahl der Events zurück, die das übergebene
 * Kalenderobjekt enthält. Im Fehlerfall wird Null zurück
 * gegeben.
 */
{
    $eventCount = $iCalObj->countEvents();
    if (!isset ($eventCount))
    {
        printError("Fehler beim Parsen des Kalenders");
        return null;
    }
    // echo "<p>$eventCount anstehende Events</p>";
    return $eventCount;
}

function grabEvents(ZCiCal $iCalObj): ?array
{
/**
 * Läuft durch das iCalendar objekt und sammelt alle Nodes vom Typ
 * 'VEVENT' ein. Gibt ein Array mit Objekten vom Typ 'ZCiCalNode' zurück.
 * Im Fehlerfall wird Null zurück gegeben.
 */
    $events = [];
    if (isset ($iCalObj->tree->child))
    {
        foreach ($iCalObj->tree->child as $node)
        {
            if ($node->getName() == "VEVENT")
            {
                $events[] = $node;
            }
        }
    }
    else
    {
        printError("Cant find nodes");
        return null;
    }
    return $events;
}

function getEventArray(ZCiCalNode $node): array
{
/**
 * Bekommt eine Event Node vom Typ 'ZiCalNode' übergeben und extrahiert
 * daraus die gewünschten Elemente. Bildet daraus ein zweidimmensionales
 * assoziatives Array. Gibt dieses Array zurück.
 * Wird derzeit nur von der tabellarischen Ausgabe referenziert, welche
 + momentan nicht genutzt wird.
 */
    /**
     * @var ZCiCalNode $node
     * @var ZCiCalDataNode $event
     */
    $event = $node->data;
    $event_array = [];
    $keys = array('DTSTART', 'SUMMARY', 'DESCRIPTION', 'URL', 'LOCATION');

    foreach ($keys as $key)
    {
        $event_array[$key] = $event[$key]->value[0];
        if ($key === 'DTSTART')
        {
            if (isset ($event[$key]->parameter['tzid']))
            {
                $event_array['TZ'] = $event[$key]->parameter['tzid'];
            }
        }
        else if ($key === 'URL')
        {
            $event_array[$key] = $event[$key]->parameter['value'];
        }
    }
    return $event_array;
}

function printError($errMsg)
{
    echo "\n\r<p>$errMsg</p>\n\r";
    return true;
}

function toGerman(string $day): string
{
    switch ($day)
    {
        case 'Monday':
            return 'Montag';
        case 'Tuesday':
            return 'Dienstag';
        case 'Wednesday':
            return 'Mittwoch';
        case 'Thursday':
            return 'Donnerstag';
        case 'Friday':
            return 'Freitag';
        case 'Saturday':
            return 'Samstag';
        case 'Sunday':
            return 'Sonntag';
        default:
            return '?';
    }
}

function lowerURL(string $url): string
{
    $old = array('HTTPS', 'HTTP', 'FTP', 'WWW', 'SSH');
    $new = array('https', 'http', 'ftp', 'www', 'ssh');
    $new_url = str_replace($old, $new, $url);
    return $new_url;
}

function calculateNextStart(int $unix_start, string $rrule): int
/**
 * Berechnet für wiederkehrende Termine den aktuell nächsten Termin.
 * dabei werden zur Zeit nur der der Zeitraum zwischen zwei Terminen
 * und eine mögliche Anzahl der Termine berücksichtigt. Gibt den neuen
 * Termin als Unix-Zeitstempel zurück.
 */
{
    $counter = 0;

    $rule_array = getRuleArray($rrule);
    if (isset ($rule_array['COUNT']))
    {
        $count = $rule_array['COUNT'];
    }
    if (isset ($rule_array['FREQ']))
    {
        $frequency = $rule_array['FREQ'];
    }
    if (isset ($rule_array['INTERVAL']))
    {
        $interval = $rule_array['INTERVAL'];
    }
    if (isset ($rule_array['UNTIL']))
    {
        //todo implement
        $until_string = $rule_array['UNTIL'];
    }

    $freq_offset = getOffset($frequency);
    if (isset ($interval))
    {
        $offset = $freq_offset * $interval;
    }
    else
    {
        $offset = $freq_offset;
    }
    while ($unix_start <= time())
    {
        if (isset ($count))
        {
            if ($counter >= $count)
            {
                break;
            }
        }
        $unix_start = $unix_start + $offset;
        $counter = $counter + 1;
    }
    return $unix_start;
}

function getOffset(string $frequence): int
{
    switch ($frequence)
    {
        case 'HOURLY':
            return 3600;
        case 'DAILY':
            return 86400;
        case 'WEEKLY':
            return 604800;
        default:
            return 1;
    }
}

function getRuleArray(string $rrule): array
/**
 * Zerlegt den String einer RRULE und gibt die einzelnen Elemente als
 * assoziatives Array zurück.
 */
{
    $rule_array = [];
    $rule_strings = explode(';', $rrule);
    foreach ($rule_strings as $r_string)
    {
        $rule = explode('=', $r_string);
        $rule_array[$rule[0]] = $rule[1];
    }
    return $rule_array;
}

function EventIsPast(int $unix_start): bool
/**
 * Prüft, ob die übergebenen Unixzeit älter als der aktuelle Tag ist. Gibt
 * Wahr oder Falsch zurück.
 */
{
    $event_date = date('d.m.Y', $unix_start);
    $day_end = strtotime($event_date) + 86400;
    $actual_date = time();

    if ($day_end < $actual_date)
    {
        return true;
    }
    return false;
}

/**
 * Bekommt zwei Eventnodes übergeben und vergleicht deren Startdatum. Die
 * Funktion wird intern von ausort() benutzt, um das Array mit den Eventnodes
 * nach Datum zu sortieren.
 *
 * @param ZCiCalNode $event_a
 * @param ZCiCalNode $event_b
 * @return int
 */
function compareEventStart(ZCiCalNode $event_a, ZCiCalNode $event_b): int
{
    $a = $event_a->data['DTSTART']->value[0];
    $b = $event_b->data['DTSTART']->value[0];
    return $a <=> $b;
}
