<?php
/**
 * Nag_Driver:: defines an API for implementing storage backends for Nag.
 *
 * $Horde: nag/lib/Driver.php,v 1.57.2.4 2005/07/20 01:00:59 chuck Exp $
 *
 * @author  Jon Parise <jon@horde.org>
 * @since   Nag 0.1
 * @package Nag
 */
class Nag_Driver {

    /**
     * Array holding the current task list. Each array entry is a hash
     * describing a task. The array is indexed by taskId.
     *
     * @var array
     */
    var $_tasks = array();

    /**
     * String containing the current tasklist.
     *
     * @var string
     */
    var $_tasklist = '';

    /**
     * Hash containing connection parameters.
     *
     * @var array
     */
    var $_params = array();

    /**
     * Lists tasks based on the given criteria. All tasks will be
     * returned by default.
     *
     * @return array  Returns a list of the requested tasks.
     */
    function listTasks()
    {
        return $this->_tasks;
    }

    /**
     * List all alarms near $date.
     *
     * @param integer $date  The unix epoch time to check for alarms.
     *
     * @return array  An array of tasks that have alarms that match.
     */
    function listAlarms($date)
    {
        if (!count($this->_tasks)) {
            $ret = $this->retrieve();
            if (is_a($ret,'PEAR_Error')) {
                return $ret;
            }
        }
        $now = time();
        $alarms = array();
        foreach ($this->_tasks as $task_id => $task) {
            if ($task['alarm'] &&
                ($task['due'] - ($task['alarm'] * 60)) <= $date) {
                $alarms[$task_id] = $task;
            }
        }
        return $alarms;
    }

    /**
     * Generate a universal / unique identifier for a task. This is
     * NOT something that we expect to be able to parse into a
     * tasklist and a taskId.
     *
     * @return string  A nice unique string (should be 255 chars or less).
     */
    function generateUID()
    {
        return date('YmdHis') . '.' .
            substr(base_convert(microtime(), 10, 36), -16) .
            '@' . $GLOBALS['conf']['server']['name'];
    }

    /**
     * Export this task in iCalendar format.
     *
     * @param array  $task      The task (hash array) to export.
     * @param object $calendar  A Horde_iCalendar object that acts as the container.
     *
     * @return object  Horde_iCalendar_vtodo object for this event.
     */
    function toiCalendar($task, &$calendar)
    {
        global $prefs;

        $vTodo = &Horde_iCalendar::newComponent('vtodo', $calendar);

        $vTodo->setAttribute('UID', $task['uid']);

        if (isset($task['name'])) {
            $vTodo->setAttribute('SUMMARY', $task['name']);
        }

        if (isset($task['desc'])) {
            $vTodo->setAttribute('DESCRIPTION', $task['desc']);
        }

        if (isset($task['priority'])) {
            $vTodo->setAttribute('PRIORITY', $task['priority']);
        }

        if (isset($task['due']) && !empty($task['completed'])) {
            $vTodo->setAttribute('DUE', $task['due']);
        }

        if (!empty($task['completed'])) {
            $vTodo->setAttribute('STATUS', 'COMPLETED');

            // Some applications won't consider a task completed
            // unless it has a COMPLETED: date. Fill in today's date
            // for now, as we don't currently track completion date in
            // Nag.
            $vTodo->setAttribute('COMPLETED', time());
        }

        if (!empty($task['category'])) {
            $vTodo->setAttribute('CATEGORIES', $task['category']);
        }

        /**
         * @TODO Alarms.
         */

        /* Get the task's history. */
        require_once 'Horde/History.php';
        $history = &Horde_History::singleton();
        $log = $history->getHistory('nag:' . $task['tasklist_id'] . ':' . $task['uid']);
        foreach ($log->getData() as $entry) {
            switch ($entry['action']) {
            case 'add':
                $created = $entry['ts'];
                break;

            case 'modify':
                $modified = $entry['ts'];
                break;
            }
        }
        if (!empty($created)) {
            $vTodo->setAttribute('DCREATED', $created);
        }
        if (!empty($modified)) {
            $vTodo->setAttribute('LAST-MODIFIED', $modified);
        }

        return $vTodo;
    }

    /**
     * Create a task (hash array) from a Horde_iCalendar_vtodo object.
     *
     * @param Horde_iCalendar_vtodo $vTodo  The iCalendar data to update from.
     *
     * @return array memo (hash array) created from vtodo
     */
    function fromiCalendar($vTodo)
    {
        $r = array();

        $name = $vTodo->getAttribute('SUMMARY');
        if (!is_array($name) && !is_a($name, 'PEAR_Error')) {
            $r['name'] = $name;
        }

        $uid = $vTodo->getAttribute('UID');
        if (!is_array($uid) && !is_a($uid, 'PEAR_Error')) {
            $r['uid'] = $uid;
        }

        $desc = $vTodo->getAttribute('DESCRIPTION');
        if (!is_array($desc) && !is_a($desc, 'PEAR_Error')) {
            $r['desc'] = $desc;
        }

        $priority = $vTodo->getAttribute('PRIORITY');
        if (!is_array($priority) && !is_a($priority, 'PEAR_Error')) {
            $r['priority'] = $priority;
        }

        $due = $vTodo->getAttribute('DTSTAMP');
        if (!is_array($due) && !is_a($due, 'PEAR_Error')) {
            $r['due'] = $due;
        }

        $cat = $vTodo->getAttribute('CATEGORIES');
        if (!is_array($cat) && !is_a($cat, 'PEAR_Error')) {
            $r['category'] = $cat;
        }

        $status = $vTodo->getAttribute('STATUS');
        if (!is_array($status) && !is_a($status, 'PEAR_Error') && !strcasecmp($status, 'COMPLETED')) {
            $r['completed'] = 1;
        }

        /**
         * @TODO Alarms.
         */

        return $r;
    }

    /**
     * Attempts to return a concrete Nag_Driver instance based on $driver.
     *
     * @param string    $tasklist   The name of the tasklist to load.
     *
     * @param string    $driver     The type of concrete Nag_Driver subclass
     *                              to return.  The is based on the storage
     *                              driver ($driver).  The code is dynamically
     *                              included.
     *
     * @param array     $params     (optional) A hash containing any additional
     *                              configuration or connection parameters a
     *                              subclass might need.
     *
     * @return mixed    The newly created concrete Nag_Driver instance, or
     *                  false on an error.
     */
    function &factory($tasklist = '', $driver = null, $params = null)
    {
        if (is_null($driver)) {
            $driver = $GLOBALS['conf']['storage']['driver'];
        }

        $driver = basename($driver);

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

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

    /**
     * Attempts to return a reference to a concrete Nag_Driver
     * instance based on $driver. It will only create a new instance
     * if no Nag_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 = &Nag_Driver::singleton()
     *
     * @param string    $tasklist   The name of the tasklist to load.
     *
     * @param string    $driver     The type of concrete Nag_Driver subclass
     *                              to return.  The is based on the storage
     *                              driver ($driver).  The code is dynamically
     *                              included.
     *
     * @param array     $params     (optional) A hash containing any additional
     *                              configuration or connection parameters a
     *                              subclass might need.
     *
     * @return mixed    The created concrete Nag_Driver instance, or false
     *                  on error.
     */
    function &singleton($tasklist = '', $driver = null, $params = null)
    {
        static $instances;

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

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

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

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

        return $instances[$signature];
    }

}
