<?php

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

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

/** @constant MEMO_MODIFIED Memo has been modified. */
define('MEMO_MODIFIED', 2);

/** @constant MEMO_DELETED Memo has been deleted. */
define('MEMO_DELETED', 4);

/**
 * Mnemo_Driver:: defines an API for implementing storage backends for Mnemo.
 *
 * $Horde: mnemo/lib/Driver.php,v 1.14 2003/04/28 20:03:02 jan Exp $
 *
 * @author  Jon Parise <jon@horde.org>
 * @version $Revision: 1.14 $
 * @since   Mnemo 0.1
 * @package mnemo
 */
class Mnemo_Driver {

    /**
     * Array holding the current memo list.  Each array entry is a hash
     * describing a memo.  The array is indexed numerically by memo ID.
     * @var array $memos
     */
    var $_memos = array();

    /**
     * String containing the current username.  This indicates the owner of
     * the current memo list.
     * @var string $user
     */
    var $_user = '';

    /**
     * Attempts to return a concrete Mnemo_Driver instance based on $driver.
     *
     * @param string    $driver     The type of concrete Mnemo_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 owns these memos.
     *
     * @param array     $params     (optional) A hash containing any additional
     *                              configuration or connection parameters a
     *                              subclass might need.
     *
     * @return mixed    The newly created concrete Mnemo_Driver instance, or
     *                  false on an error.
     */
    function &factory($driver, $user, $params = array())
    {
        $driver = String::lower(basename($driver));
        require_once dirname(__FILE__) . '/Driver/' . $driver . '.php';
        $class = 'Mnemo_Driver_' . $driver;
        if (class_exists($class)) {
            return new $class($user, $params);
        } else {
            return false;
        }
    }

    /**
     * Attempts to return a reference to a concrete Mnemo_Driver instance
     * based on $driver.  It wil only create a new instance if no
     * Mnemo_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 = &Mnemo_Driver::singleton()
     *
     * @param string    $driver     The type of concrete Mnemo_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 owns these memos.
     *
     * @param array     $params     (optional) A hash containing any additional
     *                              configuration or connection parameters a
     *                              subclass might need.
     *
     * @return mixed    The created concrete Mnemo_Driver instance, or false
     *                  on error.
     */
    function &singleton($driver, $user, $params = array())
    {
        static $instances;

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

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

        return $instances[$signature];
    }

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

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

        return $return_set;
    }

    /**
     * Adds a new memo to the $memos list.
     *
     * @param string $body      The memo text.
     * @param string $category  The category of the memo.
     *
     * @return integer  The numeric ID of the new memo.
     */
    function addMemo($body, $category)
    {
        /* Create a new memo memo. */
        $memo['body'] = $body;
        $memo['category'] = $category;
        $memo['flags'] = MEMO_ADDED;

        /* Add it to the $memos list. */
        $this->_memos[] = $memo;
        $keys = array_keys($this->_memos);

        $id = $keys[(count($keys) - 1)];
        $this->setMemoDescription($id);
        return $id;
    }

    /**
     * Update the description (short summary) of a memo.
     *
     * @param int $memo_id  The memo to update.
     *
     * @access public
     */
    function setMemoDescription($memo_id)
    {
        $body = $this->_memos[$memo_id]['body'];
        if (!strstr($body, "\n") && String::length($body) <= 64) {
            $this->_memos[$memo_id]['desc'] = trim($body);
        } else {
            $lines = explode("\n", $body);
            if (!is_array($lines)) {
                $this->_memos[$memo_id]['desc'] = trim(String::substr($body, 0, 64));
            } else {
                // Move to a line with more than spaces.
                $i = 0;
                while (isset($lines[$i]) && !preg_match('|[^\s]|', $lines[$i])) {
                    $i++;
                }
                if (String::length($lines[$i]) <= 64) {
                    $this->_memos[$memo_id]['desc'] = trim($lines[$i]);
                } else {
                    $this->_memos[$memo_id]['desc'] = trim(String::substr($lines[$i], 0, 64));
                }
            }
        }
    }

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

        return false;
    }

    /**
     * Removes a memo from the $memos list.
     *
     * @param int       $id         The ID of the memo to delete.
     *
     * @return boolean  True if the memo was successfully marked as deleted.
     */
    function deleteMemo($id)
    {
        if (isset($this->_memos[$id])) {
            $this->_memos[$id]['flags'] |= MEMO_DELETED;
            return ($this->_memos[$id]['flags'] & MEMO_DELETED);
        }

        return false;
    }

    /**
     * Sets the requested flag to the given value.
     *
     * @param int       $id         The ID of the memo memo 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->_memos[$id])) {
            if ($state) {
                $this->_memos[$id]['flags'] |= $flag;
            } else {
                $this->_memos[$id]['flags'] &= ~$flag;
            }
        }
    }

    /**
     * Gets the requested flag's value.
     *
     * @param int       $id         The ID of the memo memo 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->_memos[$id])) {
            return ($this->_memos[$id]['flags'] & $flag);
        }

        return null;
    }

    /**
     * Purges those memos that have the "deleted" flag set.
     */
    function purgeDeleted()
    {
        $deleted_ids = array_keys($this->listMemos(MEMO_DELETED));
        foreach ($deleted_ids as $memo_id) {
            unset($this->_memos[$memo_id]);
        }
    }

    /**
     * Delete all memos from the list
     */
    function deleteAll()
    {
        foreach ($this->_memos as $id => $memo) {
            $this->_memos[$id]['flags'] |= MEMO_DELETED;
         }
    }

}
