/// class PD.BCL.List

/**
A list class that wraps a JavaScript array.
@class List
@constructor
@namespace Web
@param {Array} [array] The array with which to populate the {List}.
**/
PD.BCL.List = Class.extend({

    //===================================
    // FIELDS
    //===================================

    _array: [],

    //===================================
    // CONSTRUCTOR
    //===================================

    init: function (data) {

        if (data === null || data === undefined) {
            data = [];
        }

        if (!this._isArray(data)) {
            this._array = [data];
        }
        else {
            this._array = data;
        }

    },

    //===================================
    // PUBLIC METHODS
    //===================================

    /**
    Adds an object to the end of the List. All other `add*` methods in `PD.BCL.List` use this method to add elements.
    @method add
    @param {Object} item 
    **/
    add: function (item) {
        this._array[this._array.length] = item;
    },

    /**
    Adds the elements of the specified array to the end of the List.
    @method addRange
    @param {Array} items 
    **/
    addRange: function (items) {

        var i = 0;

        if (this._isArray(items)) {
            for (i; i < items.length; i++) {
                this.add(items[i]);
            }
        }

    },

    /**
    @method contains
    **/
    contains: function (item) {

        var result = false;

        this.each(function (index, item1) {
            if (item === item1) {
                result = true;
                return false;
            }
        });

        return result;

    },

    /**
    Gets the number of elements contained in the {List}.
    @method count
    @return {Integer} The number of elements contained in the {List}.
    **/
    count: function () {
        return this._array.length;
    },

    /**
    Removes all elements from the List.
    @method clear
    **/
    clear: function () {
        this._array = [];
    },

    /**
    A method for iterating over each element in the *List* through use of a callback function. 
    The callback function accepts two arguments **index** *Integer* and **element** *Object*. 
    The iteration can be cancelled at anytime by returning false from the callback function. 
    @method each
    @param {Function} func 
    **/
    each: function (func) {

        var i = 0,
            length = this._array.length;

        for (i; i < length; i++) {
            if (func(i, this._array[i]) === false) {
                break;
            }
        }

    },

    getAtIndex: function (index) {

        var result = null;

        if (this._array.length > index) {
            result = this._array[index];
        }

        return result;

    },

    /**
    Searches for the specified object and returns the zero-based index of the first occurrence within the entire List.
    @method indexOf
    @param {Object} item
    **/
    indexOf: function (item) {
        return this._array.indexOf(item);
    },

    /**
    Inserts an element into the List at the specified index. All other "insert" methods in this class use this method to insert elements.
    @method insert
    @param {Object} item 
    @param {Integer} index 
    **/
    insert: function (item, index) {

        if (this._array.length >= index) {
            this._array.splice(index, 0, item);
        }

    },

    /**
    Adds the elements of the specified array to the end of the List. 
    @method insertRange
    @param {Array} items 
    @param {Integer} index 
    **/
    insertRange: function (items, index) {

        var i = 0;

        if (!index) {
            index = 0;
        }

        if (this._isArray(items)) {

            items.reverse();

            for (i; i < items.length; i++) {
                this.insert(items[i], index);
            }

        }

    },

    /**
    Returns the element at the specified index within the List.
    @method item
    @param {Integer} index 
    @return {Object} The object at the specified index.
    **/
    item: function (index) {
        return this._array[index];
    },

    first: function () {

        var result = null;

        if (this._array.length > 0) {
            result = this._array[0];
        }

        return result;

    },

    last: function () {

        var result = null;

        if (this._array.length > 0) {
            result = this._array[this.count() - 1];
        }

        return result;
         
    },

    move: function (item, index) {

        var i = 0,
            removed = null;

        if (this._array.length <= index) {
            return;
        }

        removed = this.remove(item);

        if (index !== null && index !== undefined) {
            this.insert(removed, index);
        }
        else {
            this.add(removed);
        }

    },

    /**
    Removes the first occurrence of a specific object from the List.
    @method remove
    @param {Object} item
    **/
    remove: function (item) {

        var i = this.indexOf(item),
            result = null;

        if (i > -1) {
            result = this.removeAt(i);
        }

        return result;

    },

    /**
    Removes the first occurrence of a specific object from the List.
    @method removeItems
    **/
    removeItems: function (items) {

        var i = 0,
            index = 0,
            indexList = [];

        for (i; i < items.length; i++) {
            index = this.indexOf(items[i]);
            if (index > -1) {
                indexList[indexList.length] = index;
            }
        }

        indexList.sort(function (item1, item2) {
            return item2 - item1;
        });

        this.removeAtIndices(indexList);

    },

    /**
    Removes the element at the specified index of the List. All other "remove" methods in this class use this method to remove elements.
    @method removeAt
    @param {Integer} index
    **/
    removeAt: function (index) {

        var result = null;

        if (this._array.length > index) {
            result = this._array.splice(index, 1)[0];
        }
        else {
            throw "Index " + index + " exceeds the bounds of the array!";
        }

        return result;

    },

    /**
    Removes the elements at the specified indices of the List.
    @method removeAtIndices
    @param {Array} indicies
    **/
    removeAtIndices: function (indices) {

        var result = [],
            i = 0;

        if (this._isArray(indices)) {

            this._sortNumericArray(indices);

            for (i; i < indices.length; i++) {
                result[result.length] = this.removeAt(indices[i]);
            }

        }

        return result;

    },

    /**
    Reverses the order of the elements in the List<T>
    @method reverse
    **/
    reverse: function () {
        this._array.reverse();
    },

    sort: function (func) {
        this._array.sort(func);
    },

    toArray: function () {
        return this._array.slice();
    },

    clone: function () {
        return new PD.BCL.List(this.toArray());
    },

    transform: function (fn) {

        var result = new PD.BCL.List();

        this.each(function (index, item) {
            var value = fn(item);
            if (value !== undefined) {
                result.add(value);
            }
        });

        return result;

    },

    //===================================
    // PRIVATE METHODS
    //===================================

    /**
    Determines if the item passed to the method is an Array.
    @method _isArray
    @param {Object} arr
    @protected
    **/
    _isArray: function (arr) {
        return $.isArray(arr);
    },

    /**
    Sorts an array numerically.
    @method _sortNumericArray
    @param {Array} arr The array to sort.
    @param {Boolean} sortAscending Determines the sort direction.
    @protected
    **/
    _sortNumericArray: function (arr, sortAscending) {

        if (this._isArray(arr)) {

            arr.sort(function (item1, item2) {
                return item2 - item1;
            });

        }

    }

});
