/**
 * @fileoverview Hash table classes with all necessary helper functions (No-Prototyping version)
 *
 * @author      Marc-Oliver Stühmer
 * @version     1.0.20080825
 */


/**
 * Return a clone of the given object
 *
 * <br /><br />
 * <b>NOTE:</b> Do not use for internal JavaScript objects apart from Array!
 *
 * @param   {Object} object             The object to clone
 * @param   {Boolean} [deep=false]      Clone recursively? (<b>optional</b>: <i>false</i>)
 * @return  {Object}                    The cloned object
 */
function clone(object, deep)
{
    deep = deep == null ? false : deep;

    if (object instanceof Array) {
        var array = new Array();
        for (var i = 0; i < object.length; i++) {
            if (deep) {
                array[i] = clone(object[i], true);
            } else {
                array[i] = object[i];
            }
        }
        return array;
    }

    if (object == null || typeof object != 'object') {
        return object;
    }

    if (object.clone) {
        var retobj = object.clone(deep);
    } else {
        var retobj = new Object();
        for (var i in object) {
            if (deep) {
                retobj[i] = clone(object[i], true);
            } else {
                retobj[i] = object[i];
            }
        }
    }
    return retobj;
}

/**
 * Serialize the given value
 *
 * <br /><br />
 * <b>NOTE:</b> Do not use for internal JavaScript objects apart from Array!
 *
 * @param   {mixed} value               The value to serialize
 * @return  {String}                    The serialized value
 * @throws  {TypeError}
 */
function serialize(value)
{
    if (value === null) {
        return 'N;';
    }

    if (value instanceof Array) {
        var str = 'a:' + value.length + ':{';
        for (var i = 0; i < value.length; i++) {
            str += serialize(i) + serialize(value[i]);
        }
        return str + '}';
    }

    if (typeof value == 'number') {
        if (Math.floor(value) == value) {
            return 'i:' + value + ';';
        } else {
            return 'd:' + value + ';';
        }
    }

    if (typeof value == 'string') {
        return 's:' + value.length + ':"' + value + '";';
    }

    if (typeof value == 'boolean') {
        return 'b:' + Number(value) + ';';
    }

    if (typeof value == 'object') {
        if (value.serialize) {
            return value.serialize();
        } else {
            var len  = 0;
            var str  = '';
            var name = value.getClassName ? value.getClassName() : 'Object';
            for (var i in value) {
                if (typeof value[i] != 'function') {
                    str += serialize(i) + serialize(value[i]);
                    len++;
                }
            }
            return 'O:' + name.length + ':"' + name + '":' + len + ':{' + str + '}';
        }
    }

    throw new TypeError('Could not serialize value: ' + value);
}

/**
 * Convert the given string back into a JavaScript value
 *
 * @param   {String} string             The string to unserialize
 * @return  {mixed}                     The resulting value
 * @throws  {TypeError}
 */
function unserialize(string)
{
    function _unserialize(string)
    {
        function readLength(string)
        {
            return Number(string.substring(2, string.indexOf(':', 2)));
        }

        var type = string.charAt(0);
        switch (type) {
            case 'N':
                return [null, string.substr(2)];
            case 'a':
                var len  = readLength(string);   // Number of array elements
                var tail = string.substr(string.indexOf(':', 2) + 2);

                var arr = new Array();
                var index, value;
                for (var i = 0; i < len; i++) {
                    index = _unserialize(tail);
                    tail  = index[1];
                    value = _unserialize(tail);
                    tail  = value[1];
                    arr[index[0]] = value[0];
                }
                tail = tail.substr(1);   // Remove closing '}'
                return [arr, tail];
            case 'i':
            case 'd':
                var num  = Number(string.substring(2, string.indexOf(';')));
                var tail = string.substr(string.indexOf(';') + 1);
                return [num, tail];
            case 's':
                var len   = readLength(string);   // Length of string
                var start = string.indexOf(':', 2) + 2;
                var str   = string.substr(start, len);
                var tail  = string.substr(start + len + 2);
                return [str, tail];
            case 'b':
                var bool = Boolean(Number(string.substr(2, 1)));
                var tail = string.substr(4);
                return [bool, tail];
            case 'O':
                var len  = readLength(string);   // Length of class name
                var name = string.substr(string.indexOf(':', 2) + 2, len);
                var tail = string.substr(string.indexOf(':', 2) + 2 + len);
                len  = readLength(tail);   // Number of object properties
                tail = tail.substr(len.toString().length + 4);

                var obj = eval('new ' + name + '()');
                var key, value, func;
                for (var i = 0; i < len; i++) {
                    key   = _unserialize(tail);
                    tail  = key[1];
                    value = _unserialize(tail);
                    tail  = value[1];
                    if (key[0].indexOf('\0') != -1) {
                        key[0] = key[0].replace(/\x00.*\x00/, '');
                        func = '_set' + key[0].substr(0, 1).toUpperCase() + key[0].substr(1);
                        obj[func](value[0]);
                    } else {
                        obj[key[0]] = value[0];
                    }
                }
                tail = tail.substr(1);   // Remove closing '}'
                return [obj, tail];
            default:
                throw new TypeError('Could not unserialize string: ' + string);
        }
    }

    return _unserialize(string)[0];
}

/**
 * Extend the given class
 *
 * @param   {Function} child            The child class
 * @param   {Function} parent           The class to extend
 */
function extend(child, parent)
{
    // child.prototype = new parent(); <- causes side effects!
    // Use dummy object to avoid side effects when calling the constructor by inheritance
    var obj = new Function();
    obj.prototype = parent.prototype;
    child.prototype = new obj();

    child.prototype.constructor = child;
}

/**
 * Shuffle an array using the Fisher-Yates algorithm
 *
 * @param   {Array} array               The array to shuffle
 */
function shuffleArray(array)
{
    var i = array.length;

    if (i == 0) {
        return;
    }

    var j, tmp;
    while (--i) {
        j = Math.floor(Math.random() * (i + 1));
        tmp      = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
}


/**
 * @class       Exception to throw when invalid arguments are passed to a function
 *
 * @extends     Error
 * @constructor
 * @param       {String} message        The error message
 */
InvalidArgumentError = function (message)
{
    /**
     * The error message
     * @type    String
     */
    this.message = message;
    /**
     * The name of the exception
     * @type    String
     */
    this.name = 'InvalidArgumentError';

};
extend(InvalidArgumentError, Error);


/**
 * @class       HashTable class
 *
 * @constructor
 */
HashTable = function ()
{
    /**
     * Array that holds the HashTable's elements
     * @type    Array
     */
    var data = new Array();

    /**
     * Return the internal data array
     * @private
     *
     * @param   {Boolean} [copy]        Return a copy (clone) of the array? (<b>optional</b>)
     * @param   {Boolean} [deep=true]   Clone recursively? (<b>optional</b>: <i>true</i>)
     * @return  {Array}                 The data array
     */
    this._getData = function (copy, deep)
    {
        copy = copy == null ? false : copy;
        deep = deep == null ? true : deep;
        if (copy) {
            return clone(data, deep);
        } else {
            return data;
        }
    };

    /**
     * Set the internal data array
     * @private
     *
     * @param   {Array} array           The new data array
     * @param   {Boolean} [copy]        Copy (clone) the array before setting? (<b>optional</b>)
     * @param   {Boolean} [deep=true]   Clone recursively? (<b>optional</b>: <i>true</i>)
     */
    this._setData = function (array, copy, deep)
    {
        copy = copy == null ? false : copy;
        deep = deep == null ? true : deep;
        if (copy) {
            data = clone(array, deep);
        } else {
            data = array;
        }
    };

    /**
     * Retrieve the value for the given key
     *
     * @param   {String} key            The key to get the value for
     * @return  {mixed}                 The value for the given key
     */
    this.get = function (key)
    {
        for (var i = 0; i < data.length; i++) {
            if (data[i]['key'] == key) {
                return data[i]['value'];
            }
        }
        return undefined;
    };

    /**
     * Set the value of the given key
     *
     * @param   {String} key            The key to set the value of
     * @param   {mixed} value           The value to set
     * @throws  {TypeError}
     */
    this.set = function (key, value)
    {
        if (typeof key != 'string' && typeof key != 'number') {
            throw new TypeError('Key must be String');
        }
        if (value === undefined) {
            throw new TypeError('Value must not be undefined for key \'' + key + '\'');
        }
        key = String(key);

        var element = new Object();
        element['key']   = key;
        element['value'] = value;

        var index = this.indexOf(key);
        if (index === false) {
            data.push(element);
        } else {
            data[index] = element;
        }
    };

    /**
     * Remove the element with the given key
     *
     * @param   {String} key            Key of the element to remove
     * @return  {mixed}                 The value of the removed element
     */
    this.remove = function (key)
    {
        var index = this.indexOf(key);
        if (index === false) {
            return undefined;
        }
        var elements = data.splice(index, 1);

        return elements[0]['value'];
    };

    /**
     * Remove the element with the given key
     * (alias for remove() function)
     *
     * @param   {String} key            Key of the element to remove
     * @return  {mixed}                 The value of the removed element
     */
    this.removeKey = function (key)
    {
        return this.remove(key);
    };

    /**
     * Remove all elements with the given value
     *
     * @param   {mixed} value           Value of the element(s) to remove
     */
    this.removeValue = function (value)
    {
        var i = 0;
        while (i < data.length) {
            if (data[i]['value'] === value) {
                data.splice(i, 1);
            } else {
                i++;
            }
        }
    };

    /**
     * Determine whether the given key exists
     *
     * @param   {String} key            The key to check
     * @return  {Boolean}               Does the key exist?
     */
    this.contains = function (key)
    {
        return this.indexOf(key) !== false;
    };

    /**
     * Determine whether the given key exists
     * (alias for contains() function)
     *
     * @param   {String} key            The key to check
     * @return  {Boolean}               Does the key exist?
     */
    this.containsKey = function (key)
    {
        return this.contains(key);
    };

    /**
     * Determine whether the given value exists
     *
     * @param   {mixed} value           The value to check
     * @return  {Boolean}               Does the value exist?
     */
    this.containsValue = function (value)
    {
        return Boolean(this.countValue(value));
    };

    /**
     * Return the number of occurrences of the given value
     *
     * @param   {mixed} value           The value to count
     * @return  {Number}                The number of occurrences of the value
     */
    this.countValue = function (value)
    {
        var count = 0;
        for (var i = 0; i < data.length; i++) {
            if (data[i]['value'] === value) {
                count++;
            }
        }
        return count;
    };

    /**
     * Return the index of the given key
     *
     * @param   {String} key            The key to get the index of
     * @return  {mixed}                 The index or false if key does not exist
     */
    this.indexOf = function (key)
    {
        for (var i = 0; i < data.length; i++) {
            if (data[i]['key'] == key) {
                return i;
            }
        }
        return false;
    };

    /**
     * Get the current number of elements
     *
     * @return  {Number}                The number of elements
     */
    this.count = function ()
    {
        return data.length;
    };

    /**
     * Test whether the HashTable contains no element
     *
     * @return  {Boolean}               Is the HashTable empty?
     */
    this.isEmpty = function ()
    {
        return data.length == 0;
    };

    /**
     * Empty the HashTable
     */
    this.clear = function ()
    {
        data = new Array();
    };

    /**
     * Remove all duplicate values from the HashTable<br />
     * Only the first key of a duplicate value will be kept!
     */
    this.unique = function ()
    {
        function _unique(array)
        {
            var uniq = new Array();
            if (array.length > 0) {
                var first = array[0];
                var i = 1;
                while (i < array.length) {
                    if (array[i]['value'] === first['value']) {
                        array.splice(i, 1);
                    } else {
                        i++;
                    }
                }
                uniq = [first].concat(_unique(array.slice(1)));
            }
            return uniq;
        }
        data = _unique(data);
    };

    /**
     * Extract a part of the HashTable (similar to Array.slice())
     *
     * @param   {Number} begin          Index of the first element to keep (use negative value to count from end)
     * @param   {Number} [end]          Index of the first element to delete (use negative value to count from end) (<b>optional</b>)
     */
    this.slice = function (begin, end)
    {
        data = data.slice.apply(data, arguments);
    };

    /**
     * Return all keys as array
     *
     * @return  {Array}                 The key array
     */
    this.getKeys = function ()
    {
        var keys = new Array();
        for (var i = 0; i < data.length; i++) {
            keys.push(data[i]['key']);
        }
        return keys;
    };

    /**
     * Return all values as array
     *
     * @return  {Array}                 The value array
     */
    this.getValues = function ()
    {
        var values = new Array();
        for (var i = 0; i < data.length; i++) {
            values.push(data[i]['value']);
        }
        return values;
    };

    /**
     * Exchange all key/value pairs<br />
     * If a value has several occurrences, the latest key will be used as its value<br />
     * Values that are not String will be discarded!
     *
     * @return  {Boolean}               Could all elements be flipped?
     **/
    this.flip = function ()
    {
        var src = data;
        this.clear();

        var success = true;
        for (var i = 0; i < src.length; i++) {
            try {
                this.set(src[i]['value'], src[i]['key']);
            } catch (e) {
                success = false;
            }
        }
        return success;
    };

    /**
     * Merge other HashTable into current HashTable<br />
     * Identical keys will be overwritten!
     *
     * @param   {HashTable} hashtable   The HashTable object to merge
     */
    this.merge = function (hashtable)
    {
        var keys = hashtable.getKeys();

        for (var i = 0; i < keys.length; i++) {
            this.set(keys[i], hashtable.get(keys[i]));
        }
    };

    /**
     * Intersect current HashTable with other HashTable;
     * keep only keys contained in both HashTables
     *
     * @param   {HashTable} hashtable   The HashTable to intersect with
     */
    this.intersect = function (hashtable)
    {
        var i = 0;
        while (i < data.length) {
            if (!hashtable.contains(data[i]['key'])) {
                this.remove(data[i]['key']);
            } else {
                i++;
            }
        }
    };

    /**
     * Subtract other HashTable from current HashTable;
     * keep only keys not contained in other HashTable
     *
     * @param   {HashTable} hashtable   The HashTable object to subtract
     */
    this.diff = function (hashtable)
    {
        var i = 0;
        while (i < data.length) {
            if (hashtable.contains(data[i]['key'])) {
                this.remove(data[i]['key']);
            } else {
                i++;
            }
        }
    };

    /**
     * Return a clone of the current HashTable object
     *
     * @param   {Boolean} [deep=true]   Clone recursively? (<b>optional</b>: <i>true</i>)
     * @return  {HashTable}             The cloned object
     */
    this.clone = function (deep)
    {
        deep = deep == null ? true : deep;
        var hash = new HashTable();
        hash._setData(data, true, deep);
        return hash;
    };

    /**
     * Serialize the current HashTable
     *
     * @return  {String}                The serialized object
     */
    this.serialize = function ()
    {
        var len  = 1;   // Number of properties to serialize
        var name = 'HashTable';

        var str = 'O:' + name.length + ':"' + name + '":' + len + ':{'
            + serialize('\0' + name + '\0data') + serialize(data);
        return str + '}';
    };

    /**
     * Return a string representation of the HashTable object
     *
     * @return  {String}                The HashTable as string
     */
    this.toString = function ()
    {
        if (!HashTable._tabcount) {
            HashTable._tabcount = 0;   // Static counter for number of leading tabs
        }

        var str = '';
        var firstline = true;
        var tabs, space;
        for (var i = 0; i < data.length; i++) {

            var count = HashTable._tabcount;
            if (firstline && count > 0) {
                count = 1;
                firstline = false;
            }

            tabs = '';
            for (var j = 0; j < count; j++) {
                tabs += '\t';
            }

            space = ' ';
            if (data[i]['value'] instanceof HashTable) {
                HashTable._tabcount++;
            } else {
                space = '\t';
            }

            str += tabs + data[i]['key'] + ':' + space + data[i]['value'];
            if (i < data.length - 1) {
                str += '\n';
            }
        }
        if (HashTable._tabcount > 0) {
            HashTable._tabcount--;
        }
        return str;
    };

};


/**
 * @class       HashTable class with Iterator functions
 *
 * @extends     HashTable
 * @constructor
 */
IteratorHashTable = function ()
{
    // Call the parent constructor
    HashTable.call(this);

    /**
     * The current array index
     * @type    Number
     */
    var idx = 0;

    /**
     * Set the index variable
     * @private
     *
     * @param   {Number} index          The value to set
     */
    this._setIdx = function (index)
    {
        idx = index;
    };

    /**
     * Return the current index
     *
     * @return  {Number}                The current index value
     */
    this.getIndex = function ()
    {
        return idx;
    };

    /**
     * Return the key for the given index
     *
     * @param   {Number} index          The index to get the key for
     * @return  {String}                The key at the given position or undefined
     */
    this.getKey = function (index)
    {
        return index >= 0 && index < this.count() ? this._getData()[index]['key'] : undefined;
    };

    /**
     * Return the value for the given index
     *
     * @param   {Number} index          The index to get the value for
     * @return  {mixed}                 The value at the given position or undefined
     */
    this.getValue = function (index)
    {
        return index >= 0 && index < this.count() ? this._getData()[index]['value'] : undefined;
    };

    /**
     * Return the key of the current element
     *
     * @return  {String}                The key of the current element or undefined
     */
    this.key = function ()
    {
        return idx < this.count() ? this._getData()[idx]['key'] : undefined;
    };

    /**
     * Return the current element value
     *
     * @return  {mixed}                 The current element value or undefined
     */
    this.value = function ()
    {
        return idx < this.count() ? this._getData()[idx]['value'] : undefined;
    };

    /**
     * Return the current element value
     * (alias for value() function)
     *
     * @return  {mixed}                 The current element value or undefined
     */
    this.current = function ()
    {
        return this.value();
    };

    /**
     * Move forward to next element
     *
     * @return  {mixed}                 The next element value or undefined
     */
    this.next = function ()
    {
        idx++;
        return this.value();
    };

    /**
     * Rewind the Iterator to the first element
     *
     * @return  {mixed}                 The first element value or undefined
     */
    this.rewind = function ()
    {
        idx = 0;
        return this.value();
    };

    /**
     * Check if there is a current element after calls to rewind() or next()
     *
     * @return  {Boolean}               Is the current element valid?
     */
    this.valid = function ()
    {
        return this.key() != undefined;
    };

    /**
     * Iterate through all elements
     *
     * @param   {Function} iterator     The function to execute for each element
     */
    this.iterate = function (iterator)
    {
        var key;
        this.rewind();
        while ((key = this.key()) != undefined) {
            iterator();
            this.next();
        }
    };

    /**
     * Return a clone of the current IteratorHashTable object
     *
     * @param   {Boolean} [deep=true]   Clone recursively? (<b>optional</b>: <i>true</i>)
     * @return  {IteratorHashTable}     The cloned object
     */
    this.clone = function (deep)
    {
        deep = deep == null ? true : deep;
        var hash = new IteratorHashTable();
        hash._setData(this._getData(), true, deep);
        hash._setIdx(idx);
        return hash;
    };

    /**
     * Serialize the current IteratorHashTable
     *
     * @return  {String}                The serialized object
     */
    this.serialize = function ()
    {
        var len  = 2;   // Number of properties to serialize
        var name = 'IteratorHashTable';

        var str = 'O:' + name.length + ':"' + name + '":' + len + ':{'
            + serialize('\0' + name + '\0data') + serialize(this._getData())
            + serialize('\0' + name + '\0idx') + serialize(idx);
        return str + '}';
    };

};
extend(IteratorHashTable, HashTable);


/**
 * @class       IteratorHashTable class with sort functions
 *
 * @extends     IteratorHashTable
 * @constructor
 */
SortableIteratorHashTable = function ()
{
    // Call the parent constructor
    IteratorHashTable.call(this);

    /**
     * Reference to 'this', makes 'this' accessible to private functions
     * @type    SortableIteratorHashTable
     */
    var self = this;

    /**
     * Sort the data array
     *
     * @param   {String} field          The field to sort by ('key' or 'value')
     * @param   {String} [type='alpha'] The sort type (<b>optional</b>: <i>'alpha'</i>, 'alpha_nocase', 'num')
     * @param   {String} [order='asc']  The sort order (<b>optional</b>: <i>'asc'</i> or 'desc')
     * @throws  {InvalidArgumentError}
     */
    var sortData = function (field, type, order)
    {
        type  = type == null ? 'alpha' : type;
        order = order == null ? 'asc' : order;

        if (order != 'asc' && order != 'desc') {
            throw new InvalidArgumentError('Invalid argument for \'order\' parameter: ' + order);
        }

        self._getData().sort(function (e1, e2) {
            return SortableIteratorHashTable._compare(e1, e2, field, type);
        });

        if (order == 'desc') {
            self.reverse();
        }
    };

    /**
     * Sort the HashTable by its keys
     *
     * @param   {String} [type='alpha'] The sort type (<b>optional</b>: <i>'alpha'</i>, 'alpha_nocase', 'num')
     * @param   {String} [order='asc']  The sort order (<b>optional</b>: <i>'asc'</i> or 'desc')
     */
    this.sort = function (type, order)
    {
        sortData('key', type, order);
    };

    /**
     * Sort the HashTable by its keys
     * (alias for sort() function)
     *
     * @param   {String} [type='alpha'] The sort type (<b>optional</b>: <i>'alpha'</i>, 'alpha_nocase', 'num')
     * @param   {String} [order='asc']  The sort order (<b>optional</b>: <i>'asc'</i> or 'desc')
     */
    this.sortByKeys = function (type, order)
    {
        this.sort(type, order);
    };

    /**
     * Sort the HashTable by its values
     *
     * @param   {String} [type='alpha'] The sort type (<b>optional</b>: <i>'alpha'</i>, 'alpha_nocase', 'num')
     * @param   {String} [order='asc']  The sort order (<b>optional</b>: <i>'asc'</i> or 'desc')
     */
    this.sortByValues = function (type, order)
    {
        sortData('value', type, order);
    };

    /**
     * Sort the HashTable by values with a user defined compare function<br />
     * The function must accept the two values to compare as arguments and return -1, 0 or 1.
     *
     * @param   {Function} sortfunc     The compare function
     */
    this.uSortByValues = function (sortfunc)
    {
        self._getData().sort(function (e1, e2) {
            return sortfunc(e1['value'], e2['value']);
        });
    };

    /**
     * Reverse the HashTable
     */
    this.reverse = function ()
    {
        this._getData().reverse();
    };

    /**
     * Shuffle the HashTable
     */
    this.shuffle = function ()
    {
        shuffleArray(this._getData());
    };

    /**
     * Exchange the elements with the given keys
     *
     * @param   {String} key1           Key of the first element
     * @param   {String} key2           Key of the second element
     * @return  {Boolean}               Was the operation successful?
     */
    this.exchange = function (key1, key2)
    {
        var index1 = this.indexOf(key1);
        var index2 = this.indexOf(key2);
        return index1 !== false && index2 !== false && this.exchangeByIndex(index1, index2);
    };

    /**
     * Exchange the elements with the given keys
     * (alias for exchange() function)
     *
     * @param   {String} key1           Key of the first element
     * @param   {String} key2           Key of the second element
     * @return  {Boolean}               Was the operation successful?
     */
    this.exchangeByKeys = function (key1, key2)
    {
        return this.exchange(key1, key2);
    };

    /**
     * Exchange the elements with the given indexes
     *
     * @param   {Number} index1         Index of the first element
     * @param   {Number} index2         Index of the second element
     * @return  {Boolean}               Was the operation successful?
     */
    this.exchangeByIndex = function (index1, index2)
    {
        if (index1 < 0 || index1 >= this.count() || index2 < 0 || index2 >= this.count()) {
            return false;
        }
        var data = this._getData();   // Get a reference to the data array

        var tmp      = data[index1];
        data[index1] = data[index2];
        data[index2] = tmp;

        return true;
    };

    /**
     * Move the element with the given key to the first position
     *
     * @param   {String} key            Key of the element to move
     * @return  {Boolean}               Was the operation successful?
     */
    this.makeFirst = function (key)
    {
        var index = this.indexOf(key);
        if (index === false) {
            return false;
        }
        var data = this._getData();   // Get a reference to the data array

        var elements = data.splice(index, 1);
        data.unshift(elements[0]);

        return true;
    };

    /**
     * Move the element with the given key to the last position
     *
     * @param   {String} key            Key of the element to move
     * @return  {Boolean}               Was the operation successful?
     */
    this.makeLast = function (key)
    {
        var index = this.indexOf(key);
        if (index === false) {
            return false;
        }
        var data = this._getData();   // Get a reference to the data array

        var elements = data.splice(index, 1);
        data.push(elements[0]);

        return true;
    };

    /**
     * Return a clone of the current SortableIteratorHashTable object
     *
     * @param   {Boolean} [deep=true]   Clone recursively? (<b>optional</b>: <i>true</i>)
     * @return  {SortableIteratorHashTable} The cloned object
     */
    this.clone = function (deep)
    {
        deep = deep == null ? true : deep;
        var hash = new SortableIteratorHashTable();
        hash._setData(this._getData(), true, deep);
        hash._setIdx(this.getIndex());
        return hash;
    };

    /**
     * Serialize the current SortableIteratorHashTable
     *
     * @return  {String}                    The serialized object
     */
    this.serialize = function ()
    {
        var len  = 2;   // Number of properties to serialize
        var name = 'SortableIteratorHashTable';

        var str = 'O:' + name.length + ':"' + name + '":' + len + ':{'
            + serialize('\0' + name + '\0data') + serialize(this._getData())
            + serialize('\0' + name + '\0idx') + serialize(this.getIndex());
        return str + '}';
    };

};
extend(SortableIteratorHashTable, IteratorHashTable);

/**
 * Compare two elements with each other
 * @private
 * @static
 *
 * @param   {Object} e1                 The first element to compare
 * @param   {Object} e2                 The second element to compare
 * @param   {String} field              The field to compare ('key' or 'value')
 * @param   {String} type               The type of the compare ('alpha', 'alpha_nocase', 'num')
 * @return  {Number}                    The relation of the elements (-1, 0 or 1)
 * @throws  {InvalidArgumentError}
 */
SortableIteratorHashTable._compare = function (e1, e2, field, type)
{
    if (field != 'key' && field != 'value') {
        throw new InvalidArgumentError('Invalid argument for \'field\' parameter: ' + field);
    }

    var comp_val1 = e1[field];
    var comp_val2 = e2[field];

    if (comp_val1 == null && comp_val2 == null) {
        return 0;
    }
    if (comp_val1 == null || comp_val2 == null) {
        return comp_val1 == null ? 1 : -1;
    }

    if (type == 'num') {
        var nan1 = isNaN(parseFloat(comp_val1));
        var nan2 = isNaN(parseFloat(comp_val2));
        if (nan1 && nan2) {
            return 0;
        }
        if (nan1 || nan2) {
            return nan1 ? 1 : -1;
        }
    }

    switch (type) {
        case 'alpha':
            comp_val1 = comp_val1.toString();
            comp_val2 = comp_val2.toString();
            break;
        case 'alpha_nocase':
            comp_val1 = comp_val1.toString().toLowerCase();
            comp_val2 = comp_val2.toString().toLowerCase();
            break;
        case 'num':
            comp_val1 = parseFloat(comp_val1);
            comp_val2 = parseFloat(comp_val2);
            break;
        default:
            throw new InvalidArgumentError('Invalid argument for \'type\' parameter: ' + type);
    }

    return comp_val1 != comp_val2 ? (comp_val1 > comp_val2 ? 1 : -1) : 0;
};