<?php

// Internal status flags (for bitwise operations)
/** @constant METTING_ANY Meeting has any flag set. */
define('MEETING_ANY', 0);

/** @constant MEETING_ADDED Meeting has just been added. */
define('MEETING_ADDED', 1);

/** @constant MEETING_MODIFIED Meeting has been modified. */
define('MEETING_MODIFIED', 2);

/** @constant MEETING_DELETED Meeting has been deleted. */
define('MEETING_DELETED', 4);

/**
 * Moment_Driver:: defines an API for implementing storage backends for Moment.
 *
 * $Horde: moment/lib/Driver.php,v 1.6 2003/09/01 22:56:36 jan Exp $
 *
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @version $Revision: 1.6 $
 * @since   Moment 1.0
 * @package moment
 */
class Moment_Driver {

    /**
     * Array holding the current meetings.  Each array entry is a hash
     * describing a meeting.  The array is indexed numerically by meeting ID.
     * @var array $_meetings
     */
    var $_meetings = array();

    /**
     * String containing the current username.  This indicates the organizer of
     * the meetings.
     * @var string $_organizer
     */
    var $_organizer = '';

    /**
     * Attempts to return a concrete Moment_Driver instance based on $driver.
     *
     * @param string    $driver     The type of concrete Moment_Driver subclass
     *                              to return.  The is based on the storage
     *                              driver ($driver).  The code is dynamically
     *                              included.
     *
     * @param string    $orangizer  The name of the user who organized the
     *                              meetings
     *
     * @param array     $params     (optional) A hash containing any additional
     *                              configuration or connection parameters a
     *                              subclass might need.
     *
     * @return mixed    The newly created concrete Moment_Driver instance, or
     *                  false on an error.
     */
    function &factory($user = null, $driver = null, $params = null)
    {
        if (is_null($user)) {
            $user = Auth::getAuth();
        }

        if (is_null($driver)) {
            $driver = $GLOBALS['conf']['meetings']['driver'];
        }

        $driver = basename($driver);

        if (is_null($params)) {
            $params = Horde::getDriverConfig('meetings', $driver);
        }

        require_once dirname(__FILE__) . '/Driver/' . $driver . '.php';
        $class = 'Moment_Driver_' . $driver;
        if (class_exists($class)) {
            return new $class($user, $params);
        } else {
            return false;
        }
    }

    /**
     * Attempts to return a reference to a concrete Moment_Driver instance
     * based on $driver.  It wil only create a new instance if no
     * Moment_Driver instance with the same parameters currently exists.
     *
     * This should be used if multiple storage sources are required.
     *
     * This method must be invoked as: $var = &Moment_Driver::singleton()
     *
     * @param string    $driver     The type of concrete Moment_Driver subclass
     *                              to return.  The is based on the storage
     *                              driver ($driver).  The code is dynamically
     *                              included.
     *
     * @param string    $user       The name of the user who oraganized the
     *                              meetings.
     *
     * @param array     $params     (optional) A hash containing any additional
     *                              configuration or connection parameters a
     *                              subclass might need.
     *
     * @return mixed    The created concrete Moment_Driver instance, or false
     *                  on error.
     */
    function &singleton($user = null, $driver = null, $params = null)
    {
        static $instances;

        if (is_null($user)) {
            $user = Auth::getAuth();
        }

        if (is_null($driver)) {
            $driver = $GLOBALS['conf']['meetings']['driver'];
        }

        if (is_null($params)) {
            $params = Horde::getDriverConfig('meetings', $driver);
        }

        if (!isset($instances)) {
            $instances = array();
        }

        $signature = serialize(array($user, $driver, $params));
        if (!isset($instances[$signature])) {
            $instances[$signature] = &Moment_Driver::factory($user, $driver, $params);
        }

        return $instances[$signature];
    }

    /**
     * Lists meetings based on the given criteria.  All meetings will be returned
     * by default.
     *
     * @param optional constant $criteria  A set of flags specifying the
     *                                     set of meetings to return.
     *
     * @return array    Returns a list of the requested meetingss.
     */
    function listMeetings($criteria = MEETING_ANY)
    {
        /* Return all of the meetings by default. */
        if ($criteria == MEETING_ANY) {
            return $this->_meetings;
        }

        /* Otherwise, return only the set of meetings that match $criteria. */
        $return_set = array();
        foreach ($this->_meetings as $meeting_id => $meeting) {
            if ($this->_meetings[$meeting_id]['flags'] & $criteria) {
                $return_set[$meeting_id] = $meeting;
            }
        }

        return $return_set;
    }

    /**
     * Reteive a specific meeting by its id.
     *
     * @param interger  The ID of the meeting to return.
     *
     * @return mixed    (array) A hash of the meeting.
     *                  (PEAR_Error) No found
     */
    function getMeeting($id)
    {
        if (!array_key_exists($id, $this->_meetings)) {
            return PEAR::raiseError(_("Unknown meeting ID"));
        }
        return $this->_meetings[$id];
    }

    /**
     * Look up the Id of a meeting by its UID.
     *
     * @param interger  The UID to search for.
     *
     * @return mixed    (string) The id of the meeting
     *                  (PEAR_Error) No found
     */
    function getMeetingIdByUid($uid)
    {
        foreach ($this->_meetings as $meeting_id => $meeting) {
            if ($meeting['uid'] == $uid) {
                return $meeting_id;
            }
        }
        return PEAR::raiseError(_("Unknown meeting UID"));
    }

    /**
     * Adds a new meeting to the $_meetings list.
     *
     * @param array $meeting A hash of the meeting.
     *
     * @return integer  The numeric ID of the new meeting.
     */
    function addMeeting($meeting)
    {
        require_once HORDE_BASE . '/lib/iCalendar.php';

        $uid = Horde_iCalendar::_exportDateTime(time()) . '-' . $this->_organizer . '@' . $_SERVER["SERVER_NAME"];

        $meeting['title']       = array_key_exists('title', $meeting)       ? $meeting['title'] : _('No Title');
        $meeting['description'] = array_key_exists('description', $meeting) ? $meeting['description'] : '';
        $meeting['location']    = array_key_exists('location', $meeting)    ? $meeting['location'] : '';
        $meeting['start']       = array_key_exists('start', $meeting)       ? $meeting['start'] : time();
        $meeting['end']         = array_key_exists('end', $meeting)         ? $meeting['end'] : $meeting['start'] + 1800;
        $meeting['attendees']   = array_key_exists('attendees', $meeting)   ? $meeting['attendees'] : array();
        $meeting['uid']         = array_key_exists('uid', $meeting)         ? $meeting['uid'] : $uid;
        $meeting['sequence']    = array_key_exists('sequence', $meeting)    ? $meeting['sequence'] : 0;
        $meeting['status']      = array_key_exists('status', $meeting)      ? $meeting['status'] : '';
        $meeting['flags'] = MEETING_ADDED;

        /* Add it to the $_meetings list. */
        $this->_meetings[] = $meeting;

        $keys = array_keys($this->_meetings);

        return $keys[(count($keys) - 1)];
    }

    /**
     * Modifies an attribute of a meeting in the $_meetings list.
     *
     * @param int       $id         The ID of the meeting to modify.
     * @param string    $attribute  The attribute of the meeting to modify.
     * @param mixed     $value      The new value for $attribute.
     *
     * @return boolean  True if the meeting was successfully marked as modified.
     */
    function modifyMeeting($id, $attribute, $value)
    {
        if (isset($this->_meetings[$id]) && isset($this->_meetings[$id][String::lower($attribute)])) {
            $this->_meetings[$id][String::lower($attribute)] = $value;
            $this->_meetings[$id]['flags'] |= MEETING_MODIFIED;
            return ($this->_meetings[$id]['flags'] & MEETING_MODIFIED);
        }

        return false;
    }

    /**
     * Removes a meeting from the $meetings list.
     *
     * @param int       $id         The ID of the meeting to delete.
     *
     * @return boolean  True if the meeting was successfully marked as deleted.
     */
    function deleteMeeting($id)
    {
        if (array_key_exists($id, $this->_meetings)) {
            $this->_meetings[$id]['flags'] |= MEETING_DELETED;
            return ($this->_meetings[$id]['flags'] & MEETING_DELETED);
        }

        return false;
    }

    /**
     * Sets the requested flag to the given value.
     *
     * @param int       $id         The ID of the meeting meeting to modify.
     * @param int       $flag       The flag to modify.
     * @param boolean   $state      The new state of the flag.
     */
    function setFlag($id, $flag, $state)
    {
        if (isset($this->_meetings[$id])) {
            if ($state) {
                $this->_meetings[$id]['flags'] |= $flag;
            } else {
                $this->_meetings[$id]['flags'] &= ~$flag;
            }
        }
    }

    /**
     * Gets the requested flag's value.
     *
     * @param int       $id         The ID of the meeting to examine.
     * @param int       $flag       The flag whose value to return.
     *
     * @return boolean  The state of the requested flag.
     */
    function getFlag($id, $flag)
    {
        if (isset($this->_meetings[$id])) {
            return ($this->_meetings[$id]['flags'] & $flag);
        }

        return null;
    }

    /**
     * Purges those meetings that have the "deleted" flag set.
     */
    function purgeDeleted()
    {
        $deleted_ids = array_keys($this->listMeetings(MEETING_DELETED));
        foreach ($deleted_ids as $meeting_id) {
            unset($this->_meetings[$meeting_id]);
        }
    }

    /**
     * Delete all meetings from the list
     *
     */
    function deleteAll()
    {
        foreach ($this->_meetings as $id => $meeting) {
            $this->_meetings[$id]['flags'] |= MEETING_DELETED;
        }
    }

}
