﻿/* ************************************************************************************ */
/* LINQ to JavaScript (JSLINQ) v1.03 - http://jslinq.com                                */
/* Copyright (C) 2008 Chris Pietschmann (http://pietschsoft.com). All Rights Reserved.  */
/* This project is licensed under the Microsoft Reciprocal License (Ms-RL)              */
/* This license can be found here: http://www.codeplex.com/JSLINQ/license               */
/* ************************************************************************************ */

function From(array){
/// <summary>From LINQ to JavaScript Operator. This method was included to support it's LINQ counterpart.</summary>
/// <param name="array">The Array to return.</param>
/// <returns>Array</returns>
    return array;
};

Array.prototype.WhereAndMatch = function(clause, options) {
    /// <summary>Where LINQ to JavaScript Operator</summary>
    /// <param name="clause">The clause used to determine query matches.</param>
    /// <param name="funcIsValid">Optional pointer to allow cancel of task</param>
    Application.ShowLoader('Filtering:' + clause, null, true);
    var item;
    var newArray = [];
    var matchIndex = [];

    var iLength = this.length;
    if (!options) options = {};
    if (!options.chunkSize) options.chunkSize = 1;


    OS.Time('Where:' + clause);

    if (options.isMultiThreaded) {
        var arrCurrent = this;
        OS.ThreadedFunction(options.threadKey, parseInt(iLength / options.chunkSize) + 1, {
            init: function() {
                return { fullArray: arrCurrent, items: [], matchIndex: [] };
            },
            work: function(iteration, payload) {
                //Thread work
                try {
                    for (var iChunk = 0; iChunk < options.chunkSize; iChunk++) {
                        if (this.isCancelled()) break;
                        var ixChunk = (iteration * options.chunkSize) + iChunk;
                        if (ixChunk >= iLength) break;
                        var val = payload.fullArray[ixChunk];
                        if (clause(val, ixChunk)) {
                            payload.items.push(val);
                            payload.matchIndex.push(ixChunk);
                        }
                    }
                    return payload;
                } catch (e) {
                    if (e != $break) throw e;
                    OS.Log('Cancelling Query: ' + clause);
                    this.isCancelled = function() { return true; };
                    return payload;
                }
            }, onChunkComplete: function(iteration, payload) {
                if (options.onChunkComplete) options.onChunkComplete(iteration, payload);
            }, onFinish: function(payload) {
                if (options.onFinish) options.onFinish(payload);
                Application.HideLoader('Filtering:' + clause);
                OS.TimeEnd('Where:' + clause);
            }, isCancelled: function() {
                if (!options.IsCancelled) return false;
                return options.IsCancelled();
            }, onCancel: function() {
                OS.TimeEnd('Where:' + clause);
                Application.HideLoader('Filtering:' + clause);
                OS.Log('Query cancelled: ' + clause);
                if (options.onCancel) options.onCancel();
            }
        });
        return;
    }

    // The clause was passed in as a Method that return a Boolean
    for (var ix = 0; ix < iLength; ix++) {
        var val = this[ix];
        if (options.IsCancelled && options.IsCancelled(val)) {
            OS.TimeEnd('Where:' + clause);
            Application.HideLoader('Filtering:' + clause);
            throw $break;
            break;
        }
        if (clause(val, ix)) {
            newArray.push(val);
            matchIndex.push(ix);
        }
    }
    OS.TimeEnd('Where:' + clause);
    Application.HideLoader('Filtering:' + clause);

    return { items: newArray, matches: matchIndex };
};
Array.prototype.Where = function(clause, options) {
    var arrMatch = this.WhereAndMatch(clause, options);
    if (!arrMatch) return [];
    return arrMatch.items;
}
Array.prototype.Top = function(number) {
    var newArray = [];
    var iLen = this.length;
    for (var i = 0; i < number; i++) {
        if (iLen <= i) break;
        newArray.push(this[i]);
    }
    return newArray;
}
Array.prototype.Select = function(clause) {
    /// <summary>Select LINQ to JavaScript Operator</summary>
    /// <param name="clause">The clause used to determine what values to select.</param>
    var item;
    var newArray = [];
    // The clause was passed in as a Method that returns a Value
    var iLen = this.length;
    for (var i = 0; i < iLen; i++) {
        var val = clause(this[i]);
        if (val) newArray.push(val);
    }
    return newArray;
};

Array.prototype.OrderBy = function(clause) {
    /// <summary>OrderBy LINQ to JavaScript Operator</summary>
    /// <param name="clause">The clause used to determine how to order the data.</param>
    var clauseMethod = clause;
    var tempArray = this.clone();

    OS.Time('OrderBy:' + clause);
    var arrOrder = tempArray.sort(function(a, b) {
        var x = clauseMethod(a);
        var y = clauseMethod(b);
        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
    });
    OS.TimeEnd('OrderBy:' + clause);
    return arrOrder;
};

Array.prototype.OrderByDescending = function(clause) {
    /// <summary>OrderByDescending LINQ to JavaScript Operator</summary>
    /// <param name="clause">The clause used to determine how to order the data.</param>
    var clauseMethod = clause;
    var tempArray = this.clone();

    OS.Time('OrderByDesc:' + clause);
    var arrOrder = tempArray.sort(function(a, b) {
        var x = clauseMethod(b);
        var y = clauseMethod(a);
        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
    });
    OS.TimeEnd('OrderByDesc:' + clause);
    return arrOrder;
};

Array.prototype.SelectMany = function(clause) {
    /// <summary>SelectMany LINQ to JavaScript Operator</summary>
    /// <param name="clause">The clause used to determine what values to select.</param>
    var clauseMethod = clause;

    var retVal = [];
    var iLen = this.length;
    for (var i = 0; i < iLen; i++) {
        retVal = retVal.concat(clauseMethod(this[i]));
    }
    return retVal;
};

Array.prototype.Count = function(clause){
    /// <summary>Count LINQ to JavaScript Operator</summary>
    /// <param name="clause">The clause used to determine what values to count.</param>
    if (clause == null) return this.length;
    return this.Where(clause).length;
};

Array.prototype.Distinct = function(clause) {
    /// <summary>Distinct LINQ to JavaScript Operator</summary>
    /// <param name="clause">The clause used to determine what values to select.</param>
    var clauseMethod = function(item) { return item; };
    if (clause != null) clauseMethod = clause;

    var item;
    var dict = new Hash();
    var retVal = [];

    for (var i = 0; i < this.length; i++) {
        var val = this[i];
        item = clauseMethod(val);
        if (dict.get(item) == null) {
            dict.set(item, true);
            retVal.push(val);
        }
    }
    dict = null;
    return retVal;
};

Array.prototype.Any = function(clause) {
    /// <summary>Any LINQ to JavaScript Operator</summary>
    /// <param name="clause">The clause used to determine if a match exists.</param>
    var clauseMethod = clause;

    var iLen = this.length;
    for (var index = 0; index < iLen; index++) {
        if (clauseMethod(this[index], index)) { return true; }
    }

    return false;
};

Array.prototype.All = function(clause) {
    /// <summary>All LINQ to JavaScript Operator</summary>
    /// <param name="clause">The clause used to determine if a match exists.</param>
    var clauseMethod = clause;

    var iLen = this.length;
    for (var index = 0; index < iLen; index++) {
        if (!clauseMethod(this[index], index)) { return false; }
    }

    return true;
};

Array.prototype.Reverse = function(){
    /// <summary>Reverse LINQ to JavaScript Operator</summary>
    return this.reverse();
};

Array.prototype.First = function(clause) {
    /// <summary>First LINQ to JavaScript Operator</summary>
    if (clause != null) {
        var clauseMethod = clause;
        var iLen = this.length;
        for (var index = 0; index < iLen; index++) {
            var val = this[index];
            if (clauseMethod(val, index)) return val;
        }
        return null;
    }

    // If no clause was specified, then return the First element in the Array
    if (this.length > 0)
        return this[0];
    else
        return null;
};

Array.prototype.Last = function(clause) {
    /// <summary>Last LINQ to JavaScript Operator</summary>
    var iLen = this.length;
    if (clause != null) {
        var clauseMethod = clause;
        for (var index = iLen - 1; index >= 0; index--) {
            var val = this[index];
            if (clauseMethod(val, index)) return val;
        }
        return null;
    }
    else {
        // If no clause was specified, then return the First element in the Array
        if (iLen > 0)
            return this[iLen - 1];
        else
            return null;
    }
};

Array.prototype.ElementAt = function(index){
    /// <summary>ElementAt LINQ to JavaScript Operator</summary>
    return this[index];
};

Array.prototype.Concat = function(array){
    /// <summary>Concat LINQ to JavaScript Operator - Is actually Idendical to the Array.concat method.</summary>
    return this.concat(array);
};


Array.prototype.Intersect = function(secondArray, clause){
    /// <summary>Intersect LINQ to JavaScript Operator</summary>
    var clauseMethod;
    if (clause != undefined)
    {
        if (typeof(clause) == "string")
        {
            clauseMethod = function(item, index, item2, index2){return eval(clause);};
        }
        else
        {
            clauseMethod = clause;
        }
    }
    else
    {
        clauseMethod = function(item, index, item2, index2){return item == item2;};
    }
    
    var result = [];
    for(var a = 0; a < this.length; a++){
        for(var b = 0; b < secondArray.length; b++){
            if (clauseMethod(this[a], a, secondArray[b], b)){
                result[result.length] = this[a];
            }
        }
    }
    return result;
};

Array.prototype.DefaultIfEmpty = function(defaultValue){
    var result = this;
    if (this.length == 0){
        result = defaultValue;
    }
    return result;
};

Array.prototype.ElementAtOrDefault = function(index, defaultValue){
    if(index >= 0 && index < this.length){
        return this[index];
    }
    return defaultValue;
};

Array.prototype.FirstOrDefault = function(defaultValue){
    return this.ElementAtOrDefault(0, defaultValue);
};

Array.prototype.LastOrDefault = function(defaultValue){
    return this.ElementAtOrDefault(this.length - 1, defaultValue);
};