<?php
/*
 * XXX
 */

class CalenDoku_CalendarComponent {
    var $text;
    var $type;
    var $invalid;
    var $iscalendoku = false;
    var $icalfields = array();
    var $properties = array();
    var $renderfields = array();

    function __construct($plugin, $text) {
        if (!preg_match('/^BEGIN:VEVENT/', $text))
            return;
        $text = preg_replace('/^BEGIN:VEVENT/', '', $text);
        if (!preg_match('/END:VEVENT$/', $text))
            return;
        $text = preg_replace('/END:VEVENT$/', '', $text);

        $this->iscalendoku = true;
        $this->plugin = $plugin;
        $this->explodeText($text);
        $this->invalid = false;
        $this->fillRendered();
    }

    function explodeText($text) {
        $textlines = preg_split('/\n/', $text);
        $curfield = '';

        foreach ($textlines as $line) {
            if (preg_match('/^ /', $line)) {
                $line = preg_replace('/^ /', '', $line);
                if (!strcmp($curfield, ''))
                    continue;
                $this->icalfields[$curfield] = $this->icalfields[$curfield]."\n".$line;
            } else {
                $pregretval = preg_split('/:/', $line, 2);
                $curfield = $pregretval[0];
                $value = $pregretval[1];
                $properties = preg_split('/;/', $curfield);
                $curfield = $properties[0];
                if (count($properties) > 1)
                    for ($i = 1; $i < count($properties); $i++) {
                        $propretval = preg_split('/=/', $properties[$i]);
                        $this->properties[$curfield][$propretval[0]] = $propretval[1];
                    }

                if (strcmp($this->icalfields[$curfield], ''))
                    continue;
                $this->icalfields[$curfield] = $value;
            }
        }
    }

    function parseDateTime($datestr, $tzstring) {
        if ($this->dttype == 0) {
            $datetime = preg_split('/T/', $datestr, 2);
            if (count($datetime) > 1) {
                $this->dttype = 2;
            } else {
                $this->dttype = 1;
            }
        }
        $datetime = preg_split('/T/', $datestr, 2);
        $year = substr($datetime[0], 0, 4);
        $month = substr($datetime[0], 4, 2);
        $day = substr($datetime[0], 6, 2);
        if ($this->dttype == 2) {
            $hour = substr($datetime[1], 0, 2);
            $minute = substr($datetime[1], 2, 2);
            $second = substr($datetime[1], 4, 2);
        } else {
            $hour = 0;
            $minute = 0;
            $second = 0;
        }

        $date = new DateTime();
        $date->setDate(intval($year), intval($month), intval($day));
        $date->setTime(intval($hour), intval($minute), intval($second));

        if (intval($date->format('U')) < 86400) {
            $this->invalid = true;
            return new DateTime();
        }


        /*
         * Wow, this is annoying!
         * PHP doesn't have proper support for date/time objects. The timezone 
         * settings are totally confused (you cannot ask a DateTimeZone object 
         * for its offset, but only a DateTime object) and important 
         * functionality is missing (subtracting time and getting a new DateTime 
         * object). Thus, this strange tango here...
         *
         * A DateTime object will always have its time set to UTC. If you set a 
         * timezone for the DateTime object, then it will be treated to have the 
         * offset of this timezone. So if you have a time given in a certain 
         * timezone and want to translate it to another one, you have to convert 
         * it to seconds since epoch, generate another object which has the 
         * timezone set, get the offset from it, subtract that, then generate a 
         * new object with these seconds set.
         *
         * For time with just any timezone set, we have to translate it back to 
         * UTC by setting the timezone to UTC, taking seconds, subtracting the 
         * offset from the local timezone, then convert it back.
         */
        if (strlen($this->plugin->getConf('defaulttimezone')) > 0) {
            $mytz = new DateTimeZone($this->plugin->getConf('defaulttimezone'));
        } else {
            $mytz = new DateTimeZone(date_default_timezone_get());
        }
        $utctz = new DateTimeZone('UTC');
        if ($this->dttype > 1) {
            if ($this->plugin->getConf('showlocaltime')) {
                $zpos = strpos($datestr, 'Z');
                if ($zpos !== false) {
                    $date->setTimeZone($mytz);
                } else if (strlen($tzstring) == 0) {
                    $date->setTimeZone($utctz);
                    $datesecs = intval($date->format('U'));
                    $date->setTimeZone($mytz);
                    $datesecs = strval($datesecs - intval($date->getOffset()));
                    $date = DateTime::createFromFormat('U', $datesecs);
                    $date->setTimeZone($mytz);
                } else {
                    $date->setTimeZone($mytz);
                    $datesecs = strval($date->format('U')) + intval($date->getOffset());
                    $date = DateTime::createFromFormat('U', $datesecs);
                    $date->setTimeZone($mytz);
                }
            } else if (strlen($tzstring) == 0) {
                $date->setTimeZone($utctz);
                $datesecs = intval($date->format('U'));
                $date->setTimeZone($mytz);
                $datesecs = strval($datesecs - intval($date->getOffset()));
                $date = DateTime::createFromFormat('U', $datesecs);
                $date->setTimeZone($mytz);
            } else {
                $date->setTimeZone($mytz);
                $datesecs = strval($date->format('U')) + intval($date->getOffset());
                $date = DateTime::createFromFormat('U', $datesecs);
                $date->setTimeZone($mytz);
            }
        } else {
            $date->setTimeZone($mytz);
            $datesecs = strval($date->format('U')) + intval($date->getOffset());
            $date = DateTime::createFromFormat('U', $datesecs);
            $date->setTimeZone($mytz);
        }

        return $date;
    }
    
    function renderDateTime($date) {
        if ($this->dttype > 1) {
            return $date->format($this->plugin->getConf('datetimeformat'));
        }
        return $date->format($this->plugin->getConf('dateformat'));
    }

    function checkStartEndType() {
        if (isset($this->properties['DTSTART']['VALUE'])) {
            if (!strcmp($this->properties['DTSTART']['VALUE'], 'DATE')) {
                $this->dttype = 1;
            } else if (!strcmp($this->properties['DTSTART']['VALUE'], 'DATETIME')) {
                $this->dttype = 2;
            }
        } else if (isset($this->properties['DTEND']['VALUE'])) {
            if (!strcmp($this->properties['DTEND']['VALUE'], 'DATE')) {
                $this->dttype = 1;
            } else if (!strcmp($this->properties['DTSTART']['VALUE'], 'DATETIME')) {
                $this->dttype = 2;
            }
        } else {
            $this->dttype = 0;
        }
    }

    function renderDTSTART() {
        if (isset($this->icalfields['DTSTART'])) {
            $this->dtstart = $this->parseDateTime($this->icalfields['DTSTART'], $this->properties['DTSTART']['TZID']);
            $this->dtstartint = intval($this->dtstart->format('U'));
            $this->renderfields['DTSTART'] = $this->renderDateTime($this->dtstart);
        } else {
            $this->dtstartint = 0;
            $this->renderfields['DTSTART'] = '';
        }
    }

    function renderDTEND() {
        if (isset($this->icalfields['DTEND'])) {
            $this->dtend = $this->parseDateTime($this->icalfields['DTEND'], $this->properties['DTEND']['TZID']);
            $this->dtendint = intval($this->dtend->format('U'));
            $this->renderfields['DTEND'] = $this->renderDateTime($this->dtend);
        } else {
            $this->dtendint = 0;
            $this->renderfields['DTEND'] = '';
        }
    }

    function renderSTRING($field) {
        $this->renderfields[$field] = $this->icalfields[$field];
    }

    // XXX
    function renderMULTISTRING($field, $desc) {
        if (isset($this->icalfields[$field]))
            $this->renderfields[$field] = $this->icalfields[$field];
        else
            $this->renderfields[$field] = '';
    }

    // XXX
    /*
     * geo        = "GEO" geoparam ":" geovalue CRLF
     * geoparam   = *(";" other-param)
     * geovalue   = float ";" float
     * ;Latitude and Longitude components
     */
    function renderGEO() {
        $geostr = preg_split('/;/', $this->icalfields['GEO']);
        $this->renderfields['LATITUDE'] = floatval($geostr[0]);
        $this->renderfields['LONGITUDE'] = floatval($geostr[1]);
    }

    function renderINT() {
        $this->renderfields[$field] = $this->icalfields[$field];
    }

    function renderCALADDRESS($field) {
        $this->renderfields[$field] = $this->icalfields[$field];
    }

    function fillRendered() {
        /* DTSTART/DTEND value */
        $this->checkStartEndType();
        $this->renderDTSTART();
        $this->renderDTEND();

        foreach($this->icalfields as $field => $value) {
            /* DTSTART */
            if (!strcmp($field, 'DTSTART'))
                continue;
            /* DTEND */
            else if (!strcmp($field, 'DTEND'))
                continue;
            /* CATEGORIES */
            else if (!strcmp($field, 'CATEGORIES'))
                $this->renderMULTISTRING('CATEGORIES', 'CATEGORY');
            /* GEO */
            else if (!strcmp($field, 'GEO'))
                $this->renderGEO();
            else if (!strcmp($field, 'PRIORITY'))
                $this->renderINT('PRIORITY');
            else if (!strcmp($field, 'ATTENDEE'))
                $this->renderCALADDRESS('ATTENDEE');
            else if (!strcmp($field, 'ORGANIZER'))
                $this->renderCALADDRESS('ORGANIZER');
            /* SUMMARY
             * DESCRIPTION
             * LOCATION
             * COMMENT
             * CONTACT
             * ORGANIZER
             * UID
             * CLASS
             * STATUS
             * RESOURCES
             * RELATED-TO
             * URL. We render it as a string and leave it up to the user to define
             * a template which makes it a link.
             */
            else
                $this->renderSTRING($field);
            // XXX: GEO
        }
    }

    function isWithinTime($startdate, $enddate) {
        if (!strcmp(($enddate->format('U')), '0'))
            $enddate = $this->dtstart;

        if ($this->dtstartint > intval($enddate->format('U'))
            || intval($startdate->format('U')) > $this->dtendint)
            return false;
        return true;
    }
}

class CalenDoku_CalendarObject {
    function __construct($plugin, $page) {
        $text = preg_replace('/^[\s\n]*BEGIN:VCALENDAR\n(.*)\nEND:VCALENDAR[\n\s]*$/s', '\1', $page, -1, $count);
        if ($count < 1)
            return;

        $this->iscalendoku = true;
        $this->plugin = $plugin;
        $this->separateComponents($text);
    }

    function separateComponents($text) {
        /* Get properties of the calendar. */
        // XXX

        /* Get the calendar components. */
        $this->components = array();
        if (preg_match_all('/BEGIN:VEVENT\n.*?END:VEVENT/s', $text, $vevents)) {
            foreach ($vevents as $vevent) {
                $component = new CalenDoku_CalendarComponent($this->plugin, $vevent[0]);
                array_push($this->components, $component);
            }
        }
        /* XXX: VTODO, VJOURNAL, VFREEBUSY, VTIMEZONE, VALARM. */
    }
}

function reHandleText($text, $pos, $handler) {
    $instructions = p_get_instructions($text);
    foreach ($instructions as $instruction)
        $handler->_addCall($instruction[0],
            $instruction[1] ? $instruction[1] : array(),
            $pos);
}

/**
 * Guess the time format. We try several date and time formats which are common 
 * and take the first one that fits.
 *
 * If we have less than eight numbers, we assume it is a Unix timestamp (with 
 * eight numbers, we might hit YYYYMMDD). This means, we won't be able to serve 
 * dates before Sat Mar  3 10:46:40 CET 1973. We allow no dates with two-digit 
 * years. En exception is there for a '0', which is diretly parsed to epoch.
 *
 * Then, we try dates this order:
 *   1. European date               DD.MM.YYYY
 *   2. European date w/o year      DD.MM.
 *   3. Technical date              YYYY-MM-DD
 *   4. Technical date w/o day      YYYY-MM
 *   5. Terror date                 YYYYMMDD
 *   6. Year only                   YYYY
 * Since American dates are ambiguous, they are not checked.
 *
 * Now check for the time:
 *   1. 24h time w/o seconds        hh:mm
 *   2. 24h time                    hh:mm:ss
 *   3. Terror time                 hhmmss
 *
 * Returns an DateTime object filled with the date from the argument.
 */
function guessDateTime($datestr) {
    $rexu = '(0|[0-9]{9,})';
    $rexy = '([0-9]{4})';
    $rexm = '(0[1-9]|1[12])';
    $rexd = '([0-2][0-9]|3[01])';
    $rexH = '([0-1][0-9]|2[0-3])';
    $rexM = '([0-5][0-9])';
    $rexS = '([0-5][0-9]|60)';
    $rexsep = '(?=[T\s]*)';
    $rexend = '.*?';

    /* %s */
    if (preg_match('/^'.$rexu.$rexsep.'$/', $datestr)) {
        $date = DateTime::createFromFormat('U', $datestr);
        return $date;
    }

    $year = date('Y');
    $month = date('m');
    $day = date('d');
    /* DD.MM.YYYY */
    if (preg_match('/^'.$rexd.'\.'.$rexm.'\.'.$rexy.$rexsep.'/', $datestr, $match)) {
        $datestr = preg_replace('/^'.$rexd.'\.'.$rexm.'\.'.$rexy.$rexsep.'/', '', $datestr);
        $day = $match[1];
        $month = $match[2];
        $year = $match[3];
    /* DD.MM. */
    } else if (preg_match('/^'.$rexd.'\.'.$rexm.$rexsep.'/', $datestr, $match)) {
        $datestr = preg_replace('/^'.$rexd.'\.'.$rexm.$rexsep.'/', '', $datestr);
        $year = date('Y');
        $month = $match[2];
        $day = $match[1];
    /* YYYY-MM-DD */
    } else if (preg_match('/^'.$rexy.'-'.$rexm.'-'.$rexd.$rexsep.'/', $datestr, $match)) {
        $datestr = preg_replace('/^'.$rexy.'-'.$rexm.'-'.$rexd.$rexsep.'/', '', $datestr);
        $year = $match[1];
        $month = $match[2];
        $day = $match[3];
    /* YYYY-MM */
    } else if (preg_match('/^'.$rexy.'-'.$rexm.$rexsep.'/', $datestr, $match)) {
        $datestr = preg_replace('/^'.$rexy.'-'.$rexm.$rexsep.'/', '', $datestr);
        $year = $match[1];
        $month = $match[2];
        $day = '1';
    /* YYYYMMDD */
    } else if (preg_match('/^'.$rexy.$rexm.$rexd.$rexsep.'/', $datestr, $match)) {
        $datestr = preg_replace('/^'.$rexy.$rexm.$rexd.$rexsep.'/', '', $datestr);
        $year = $match[1];
        $month = $match[2];
        $day = $match[3];
    } else if (preg_match('/^'.$rexy.$rexsep.'/', $datestr, $match)) {
        $datestr = preg_replace('/^'.$rexy.$rexsep.'/', '', $datestr);
        $year = $match[1];
        $month = '1';
        $day = '1';
    }

    $hour = '0';
    $mins = '0';
    $secs = '0';
    /* hh:mm */
    if (preg_match('/^'.$rexH.':'.$rexM.$rexend.'$/', $datestr, $match)) {
        $hour = $match[1];
        $mins = $match[2];
        $secs = '0';
    /* hh:mm:ss */
    } else if (preg_match('/^'.$rexH.':'.$rexM.':'.$rexS.$rexend.'$/', $datestr, $match)) {
        $hour = $match[1];
        $mins = $match[2];
        $secs = $match[3];
    /* hhmmss */
    } else if (preg_match('/^'.$rexH.$rexM.$rexS.$rexend.'$/', $datestr, $match)) {
        $hour = $match[1];
        $mins = $match[2];
        $secs = $match[3];
    }

    $date = new DateTime();
    $date->setDate(intval($year), intval($month), intval($day));
    $date->setTime(intval($hour), intval($mins), intval($secs));

    return $date;
}
//Setup VIM: ex: et ts=4 enc=utf-8 :
