<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2012 - 2013 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flVariables Class
 *
 * Class to provide persistent storage to variables using a database.
 *
 * @package fusionLib
 */
class flVariables implements Iterator, Countable {

	/**
	 * The configuration data.
	 *
	 * @var array
	 */
	protected $data = array();

	/**
	 * The configs namespace.
	 *
	 * @var string
	 */
	protected $namespace = null;

	/**
	 * The database object.
	 *
	 * @var flDb
	 */
	protected $db;

	/**
	 * Construct a new configuration object.
	 *
	 * @param string $namespace Name of the variable data to load.
	 * @param flDb $db The database to use to read the data.
	 */
	function __construct($namespace, $db) {
		$this->namespace = $namespace;
		$this->db = $db;
		$this->data = array();
		$result = $db->query('SELECT keyname,value FROM fl_variables WHERE namespace=?', $namespace);
		while($row = $result->fetch())
			$this->data[$row['keyname']] = $row['value'];
	}

	/**
	 * Delete all the data items in the namespace.
	 */
	function deleteNamespace() {
		$this->db->delete('fl_variables', 'namespace=?', $this->namespace);
	}

	/**
	 * Return the namespace for the data.
	 *
	 * @return string The namespace.
	 */
	function getNamespace() {
		return $this->namespace;
	}

	/**
	 * Set the value of an item.
	 *
	 * @param string $key The name of the item to set.
	 * @param mixed $value The value to set for the item.
	 */
	function __set($key, $value) {
		$this->db->replace('fl_variables', array('namespace' => $this->namespace, 'keyname' => $key, 'value' => $value));
		$this->data[$key] = $value;
	}

	/**
	 * Set the value of an item only if the value in the database is the same as the loaded value.
	 *
	 * @param string $key The name of the item to set.
	 * @param mixed $value The value to set for the item.
	 * @return bool true if the value was updated, else false.
	 */
	function setIfNotChanged($key, $value) {
		$r = $this->db->query('UPDATE fl_variables SET value=? WHERE namespace=? && keyname=? && value=?', $value, $this->namespace, $key, $this->data[$key]);
		return $r && $this->db->affectedRows();
	}

	/**
	 * Unset an item.
	 *
	 * @param string $key The name of the item to unset.
	 */
	function __unset($key) {
		$this->db->delete('fl_variables', 'namespace=? && keyname=?', $this->namespace, $key);
		unset($this->data[$key]);
	}

	/**
	 * Get an item.
	 *
	 * If the items is not present then the default value is returned.
	 *
	 * @param string $key The name of the item to get.
	 * @param mixed $default The default value to use if the item is not present.
	 * @return mixed The value assigned to the key or the default value.
	 */
	function get($key, $default = null) {
		return isset($this->data[$key]) ? $this->data[$key] : $default;
	}

	/**
	 * Read an item value.
	 *
	 * If the value is not present then an error is reported and the function never returns.
	 *
	 * @param string $key The name of the item to get.
	 * @return mixed The value assigned to the item.
	 * @throws flVariablesException
	 */
	function __get($key) {
		if(!isset($this->data[$key]))
			throw new flVariablesException("Unknown variable '$key' in config '$this->namespace'.");
		return $this->data[$key];
	}

	/**
	 * Test if a variable is set.
	 *
	 * @param string $name The name of the key to test for.
	 * @return bool True if the key is present; else false.
	 */
	function __isset($name) {
		return isset($this->data[$name]);
	}

	/**
	 * Number of items.
	 *
	 * @return int The number of items.
	 */
	function count() {
		return count($this->data);
	}

	/**
	 * Move to the next item.
	 *
	 * @return mixed The item value.
	 */
	function next() {
		return next($this->data);
	}

	/**
	 * Move to the first item.
	 *
	 * @return mixed The value of the first item.
	 */
	function rewind() {
		return reset($this->data);
	}

	/**
	 * Fetch the key.
	 *
	 * @return mixed The key.
	 */
	function key() {
		return key($this->data);
	}

	/**
	 * The current item.
	 *
	 * @return mixed The current item value.
	 */
	function current() {
		return current($this->data);
	}

	/**
	 * Test if the array pointer is valid.
	 * @return bool true if the position in the array is valid; else false.
	 */
	function valid() {
		return current($this->data) !== false;
	}
}
