/**
 * @namespace
 * @memberof L3D
 */
L3D.utils = (function() {

var utils = {};

/**
 * @constructor
 * @description Linked list in javascript
 * @memberof L3D.utils
 */
var List = function() {

    /**
     * @private
     * @type {Number}
     * @description number of elements in the list
     */
    this._size = 0;

    /**
     * @private
     * @type {Object}
     * @description first chain element of the list
     */
    this._begin = null;

    /**
     * @private
     * @type {Object}
     * @description last chain element of the list
     */
    this._end = null;
};

/**
 * Size of the list
 * Complexity O(1)
 * @returns {Number} the number of elements in the list
 */
List.prototype.size = function() {
    return this._size;
};

/**
 * Push an element at the end of the list
 * Complexity O(1)
 * @param element {Object} object to push at the end of the list
 */
List.prototype.push = function(element) {
    if (this._size === 0) {
        this._begin = { data : element, next: null, prev: null };
        this._end = this._begin;
        this._size = 1;
    } else {
        var newObject = { data: element, next: null, prev: this._end };
        this._end.next = newObject;
        this._end = newObject;
        this._size++;
    }
};

/**
 * Sorts the list by creating an array, sorting it, and recopying it to the list
 * @param comparator {function} comparator between objects
 * Complexity O(size() * log (size()))
 */
List.prototype.sort = function(comparator) {

    if (comparator === undefined) {
        comparator = priv.defaultComparator;
    }

    var array = [];
    this.forEach(function(elt) {
        array.push(elt);
    });

    array.sort(function(a, b) {
        return comparator(a, b);
    });

    var size = this.size();
    this.clear();

    for (var i = 0; i < size; i++) {
        this.push(array[i]);
    }
};

/**
 * Removes and returns the last element of the list
 * Complexity O(1)
 * @returns {Object} the last element of the list
 */
List.prototype.pop = function() {
    var tmp = this._end;
    this._end = null;
    return tmp.data;
};

/**
 * Apply a call back to each element of the list
 * Complexity O(size())
 * @param callback {function} callback to call on all elements of the list
 */
List.prototype.forEach = function(callback) {
    var chain = this._begin;

    while (chain !== null) {
        callback(chain.data);
        chain = chain.next;
    }
};

/**
 * Apply a call back to each element of the list in reverse order
 * Complexity O(size())
 * @param callback {function} callback to call on all elements of the list in reverse order
 */
List.prototype.forEachInverse = function(callback) {
    var chain = this._end;

    while (chain !== null) {
        callback(chain.data);
        chain = chain.prev;
    }
};

/**
 * Get the ith element of the list
 * Complexity O(ith)
 * @param ith {Number} index of the element to get
 * @returns {Object} the ith element if it exists, null otherwise
 */
List.prototype.at = function(ith) {
    if (ith < 0 || ith >= this.size()) {
        return null;
    }

    var chain = this._begin;
    for (var i = 0; i < ith; i++) {
        chain = chain.next;
    }

    return chain.data;
};

/**
 * Empty the list
 */
List.prototype.clear = function() {
    this._begin = null;
    this._end = null;
    this._size = 0;
};

/**
 * Insert an element at the right place in a sorted list
 * Precondition : the list must be sorted
 * Complexity : O(i) where i is the number of elements lower than elt
 * @param elt {Object} element to add
 * @param comparator {function} classic js comparator
 */
List.prototype.insertSorted = function(elt, comparator) {
    var newElement;

    if (comparator === undefined) {
        comparator = priv.defaultComparator;
    }

    if (this._begin === null) {
        // Inserted in front (empty list)
        this.push(elt);
    } else if (comparator(this._begin.data, elt) > 0) {
        // Inserted in front (smallest element)
        newElement = {prev: null, next: this._begin, data: elt};

        this._begin.prev = newElement;

        this._begin = newElement;
        this._size ++;

    } else if (comparator(this._end.data, elt) < 0) {
        // Inserted in end
        this.push(elt);
    } else {
        // Inserted in the middle
        var chain = this._begin;

        while (chain.next !== null) {
            // If chain < elt < chain.next
            if (comparator(chain.next.data, elt) > 0) {
                newElement = {data: elt, next: chain.next, prev: chain};
                if (chain.next) {
                    chain.next.prev = newElement;
                }
                chain.next = newElement;
                this._size ++;
                return;
            }

            // Next step
            chain = chain.next;
        }
    }
};

/**
 * Checks if a list is sorted
 * Complexity : O(size()) if the list is sorted, O(i) where i is the first non-sorted element in the list
 * @param comparator {function} classic js comparator
 * @returns {Boolean} true if the list is sorted, false otherwise
 */
List.prototype.isSorted = function(comparator) {
    var chain = this._begin;

    if (comparator === undefined) {
        comparator = priv.defaultComparator;
    }

    while (chain.next !== null) {
        if (comparator(chain.data, chain.next.data) > 0) {
            return false;
        }
        chain = chain.next;
    }

    return true;
};

/**
 * Returns an iterator to the begin of the list
 * @returns {Iterator} an interator to the first element
 */
List.prototype.begin = function() {
    return new Iterator(this._begin, 0);
};

/**
 * Returns an iterator to the end of the list
 * @returns {Iterator} an interator to the first element
 */
List.prototype.end = function() {
    return new Iterator(this._end, this.size() - 1);
};

/**
 * @constructor
 * @description Reprensents an iterator to an element of a list
 * @param chain {Object} chain element of a list
 * @param counter {Number} index of the current element
 * @memberof L3D.utils
 */
var Iterator = function(chain, counter) {
    this._chain = chain;
    this._counter = counter;
};

/**
 * Go to the next element
 * @method
 */
Iterator.prototype.next = function() {
    this._chain = this._chain.next;
    this._counter ++;
};

/**
 * Go to the previous element
 * @method
 */
Iterator.prototype.prev = function() {
    this._chain = this._chain.prev;
    this._counter --;
};

/**
 * Returns the current element
 * @method
 * @returns {Object} current element
 */
Iterator.prototype.get = function() {
    return this._chain.data;
};

/**
 * Checks if there is a element after the current element
 * @method
 * @returns {Boolean} true if the element exists, false otherwise
 */
Iterator.prototype.hasNext = function() {
    return this._chain.next !== null;
};

/**
 * Checks if there is a element before the current element
 * @method
 * @returns {Boolean} true if the element exists, false otherwise
 */
Iterator.prototype.hasPrev = function() {
    return this._chain.prev !== null;
};

/**
 * Compares two iterators of the same list
 * @param it2 {Iterator} second iterator of the comparison
 * @returns {Boolean} result of this < it2
 */
Iterator.prototype.lowerThan = function(it2) {
    return distance(this, it2) > 0;
};

/**
 * Compares two iterators of the same list
 * @method
 * @param it2 {Iterator} second iterator of the comparison
 * @returns {Boolean} result of this > it2
 */
Iterator.prototype.greaterThan = function(it2) {
    return distance(this, it2) < 0;
};

/**
 * Compute the distance between two iterators
 * @method
 * @private
 * @param it1 {Iterator} first iterator of the computation
 * @param it2 {Iterator} second iterator of the computation
 * @returns {Number} distance between it1 and it2
 */
var distance = function(it1, it2) {
    return it2._counter - it1._counter;
};

priv = {};

priv.defaultComparator = function(a,b) {
    if (a < b)
        return -1;
    if (a > b)
        return 1;
    return 0;
};

utils.List = List;
utils.Iterator = Iterator;

return utils;

})();
