<?php
/**
 * YANA library
 *
 * @package yana
 * @licence http://creativecommons.org/licenses/by-nc-sa/2.5/
 */

/**
 * «utility» Hashtable
 *
 * @access      public
 * @static
 * @package     yana
 * @subpackage  utilities
 */
class Hashtable
{
    /**
     * get
     *
     * @access public
     * @static
     * @param array $hash
     * @param string $key
     * @return mixed
     */
    function &get(&$hash, $key)
    {
        assert('is_array($hash);');
        $result = null;

        if (!Hashtable::_checkKey($key)) {
            $result = null;
            return $result;
        }

        if ($key == '*') {
            return $hash;
        } else {
            $list_of_keys = explode(".", $key);
            assert('is_array($list_of_keys) && count($list_of_keys) > 0;');
            $a =& Hashtable::_get($hash, $list_of_keys);
            return $a;
        }
    }

    /**
     * _get
     *
     * @access private
     * @static
     * @param array $hash
     * @param array $list_of_keys
     * @return mixed
     * @ignore
     */
    function &_get(&$hash, &$list_of_keys)
    {
        $key_name = array_shift($list_of_keys);
        if (isset($hash[$key_name]) && !is_null($hash[$key_name])) {
            $result = &$hash[$key_name];
            if (count($list_of_keys)==0) {
                return $result;
            } else {
                $a =& Hashtable::_get($result, $list_of_keys);
                return $a;
            }
        } else {
            $result = null;
            return $result;
        }
    }

    /**
     * set
     *
     * @access public
     * @static
     * @param array $hash
     * @param string $key
     * @param mixed $value
     * @return boolean
     */
    function set(&$hash, $key, $value)
    {
        assert('is_array($hash);');

        if (!Hashtable::_checkKey($key)) {
            return false;
        }

        if ($key == '*') {
            if (is_array($value)) {
                $hash = &$value;
                return true;
            } else {
                trigger_error("Wrong argument type: only values of type array may be assigned to a Hashtable. Found '".gettype($value)."' instead.", E_USER_WARNING);
                return false;
            }
        } else {
            $list_of_keys = explode(".", $key);
            assert('is_array($list_of_keys) && count($list_of_keys) > 0;');
            $result = &$hash;
            for ($i=0; $i < count($list_of_keys)-1; $i++)
            {
                if (!isset($result[$list_of_keys[$i]]) || is_scalar($result[$list_of_keys[$i]])) {
                    $result[$list_of_keys[$i]] = array();
                }
                $result = &$result[$list_of_keys[$i]];
            }
            $result[$list_of_keys[count($list_of_keys)-1]] = &$value;
            return true;
        }
    }

    /**
     * set
     *
     * @access public
     * @static
     * @param array $hash
     * @param string $key
     * @param string $type
     * @return boolean
     */
    function setType(&$hash, $key, $type)
    {
        assert('is_string($type) && !empty($type);');
        if (!is_string($type)) {
            trigger_error("Wrong argument type: expect argument 3 '\$type' to be of type string. Found '".gettype($type)."' instead.", E_USER_WARNING);
            return false;
        } else {
            $field =& Hashtable::get($hash, $key);
            if (!is_null($field)) {
                return settype($field, $type);
            } else {
                return false;
            }
        }
    }

    /**
     * exists
     *
     * @access public
     * @static
     * @param array $hash
     * @param string $key
     * @return boolean
     */
    function exists(&$hash, $key)
    {
        return !is_null(Hashtable::get($hash, $key));
    }

    /**
     * remove
     *
     * @access public
     * @static
     * @param array $hash
     * @param string $key
     * @return boolean
     */
    function remove(&$hash, $key)
    {
        assert('is_array($hash);');

        if (!Hashtable::_checkKey($key)) {
            return false;
        }

        if ($key == '*') {
            unset($hash);
            return true;
        } else {
            $list_of_keys = explode(".", $key);
            assert('is_array($list_of_keys) && count($list_of_keys) > 0;');
            $stack = array();
            $stack[0] = &$hash;
            $result = &$hash;
            for ($i=0; $i < count($list_of_keys)-1; $i++)
            {
                if (!isset($result[$list_of_keys[$i]])) {
                    return false;
                } else {
                    $result = &$result[$list_of_keys[$i]];
                    $stack[count($stack)] = &$result;
                }
            }
            $j = array_pop($list_of_keys);
            if (!isset($stack[count($stack)-1][$j])) {
                return false;
            } else {
                unset($stack[count($stack)-1][$j]);
                array_pop($stack);
                for ($i=count($stack)-1; $i > -1; $i--)
                {
                    $j = array_pop($list_of_keys);
                    if (is_array($stack[$i][$j]) && count($stack[$i][$j])==0) {
                        unset($stack[$i][$j]);
                    } else {
                        break;
                    }
                }
                return true;
            }    
        }
    }
    
    /**
     * _checkKey
     *
     * @access private
     * @static
     * @param string $key
     * @return boolean
     * @ignore
     */
    function _checkKey($key)
    {
        assert('is_string($key);');
        if (!is_string($key) || ($key != '*' && !preg_match("/^[\w\d-_\.\/ \*]+$/" ,$key))) {
            trigger_error("Syntax error: '".gettype($key)."(".$key.")' is not a valid hash-key.", E_USER_ERROR);
            return false;
        } else {
            return true;
        }
    }
}
?>