﻿

//Prototype extensions to base JS objects



// --------------------------------------------------------------------------------------
// -- MATH ------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------

if (!Math.bool) Math.bool = function () {
    return Math.floor(Math.random() * 1.9999) == 0;
}

if (!Math.rand) Math.rand = function (pMin, pMax, pArrOfExcludedIds) { //pMax is INclusive
    if (typeof (pArrOfExcludedIds) == "undefined")
        pArrOfExcludedIds = [];

    if (pArrOfExcludedIds == null || pArrOfExcludedIds.length == 0) {
        return Math.floor(Math.random() * (pMax - pMin + 1) + pMin);
    }

    var roll = pArrOfExcludedIds[0];
    while (pArrOfExcludedIds.contains(roll))
        roll = Math.floor(Math.random() * (pMax - pMin + 1) + pMin);
    return roll;
}

// --------------------------------------------------------------------------------------
// -- ARRAY -----------------------------------------------------------------------------
// --------------------------------------------------------------------------------------

if (!Array.prototype.contains) Array.prototype.contains = function (obj, pUseStrictTypeComparison) {
    if (typeof (pUseStrictTypeComparison) == "undefined")
        pUseStrictTypeComparison = true;

    if (pUseStrictTypeComparison) {
        var i = this.length;
        while (i--) {
            if (this[i] === obj) {
                return true;
            }
        }
    } else {
        var i = this.length;
        while (i--) {
            if (this[i] == obj) {
                return true;
            }
        }
    }
    return false;
}

if (!Array.prototype.insert) Array.prototype.insert = function (begin, end, v) {
    while (begin + 1 < end && this[begin + 1] < v) {
        this.swap(begin, begin + 1);
        ++begin;
    }
    this[begin] = v;
}


if (!Array.prototype.getRand) Array.prototype.getRand = function () {
    return this[Math.rand(0, this.length - 1)];
}




//http://www.hunlock.com/blogs/Mastering_Javascript_Arrays
if (!Array.prototype.sortNum) Array.prototype.sortNum = function () {
    return this.sort(function (a, b) { return a - b; });
}

//http://www.hunlock.com/blogs/Mastering_Javascript_Arrays
if (!Array.prototype.shuffle) Array.prototype.shuffle = function () {
    for (var rnd, tmp, i = this.length; i; rnd = parseInt(Math.random() * i), tmp = this[--i], this[i] = this[rnd], this[rnd] = tmp);
};



/*
http://www.hunlock.com/blogs/Mastering_Javascript_Arrays
If you need to be able to compare Arrays this is the prototype to do it. 
Pass an Array you want to compare and if they are identical the method will return true. 
If there's a difference it will return false. The match must be identical so '80' is not the same as 80.

Usage:
var myArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var yourArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
document.writeln(myArray.compare(yourArray)); // outputs: true;

yourArray[0]='1';
document.writeln(myArray.compare(yourArray)); // outputs: false;
yourArray[0]='one';
document.writeln(myArray.compare(yourArray)); // outputs: false;
yourArray[0]=1;
document.writeln(myArray.compare(yourArray)); // outputs: true;
*/
if (!Array.prototype.compare) Array.prototype.compare = function (testArr) {
    if (this.length != testArr.length) return false;
    for (var i = 0; i < testArr.length; i++) {
        if (this[i].compare) {
            if (!this[i].compare(testArr[i])) return false;
        }
        if (this[i] !== testArr[i]) return false;
    }
    return true;
}

/*
Heavily modified from original found here: http://www.hunlock.com/blogs/Mastering_Javascript_Arrays
Array.indexOf() is a nice method but this extension is a little more powerful and flexible. 
First it will return an array of all the indexes it found (it will return false if it doesn't find anything). 
Second in addition to passing the usual string or number to look for you can actually pass a regular expression, 
which makes this the ultimate Array prototype in my book.

var tmp = [5,9,12,18,56,1,10,42,'blue',30, 7,97,53,33,30,35,27,30,'35','Ball', 'bubble'];
//         0/1/2 /3 /4/5 /6 /7     /8  /9/10/11/12/13/14/15/16/17/  18/    19/      20
var thirty=tmp.findit(30);             // Returns 9, 14, 17
var thirtyfive=tmp.findit('35');       // Returns 18
var thirtyfive=tmp.findit(35);         // Returns 15
var haveBlue=tmp.findit('blue');       // Returns 8
var notFound=tmp.findit('not there!'); // Returns false
var regexp1=tmp.findit(/^b/);          // returns 8,20    (first letter starts with b)
var regexp1=tmp.findit(/^b/i);         // returns 8,19,20 (same as above but ignore case)

2/11/2010 - Completely re-written by Aaron Murray
- NOTE: This looks like redundant copy/pasted loops, but it performs quicker than making a really small function with all sorts of IFs in each iteration of the array (which kills performance on big arrays)
- Added option for returning only the first matching index
- Added option for value comparison without strict type comparison
- Store the array length and index as a local variable. (6x faster than checking the arr.length property each time)
- Avoid the array initialization check in every match because it is slow for big arrays.
- Split the for loop into two loops in order to avoid a redundant typeof(searchStr) loopkup for every item (a check that rarely ever matches)
- Added property check for search string to avoid checking on nulls

NOTE: even will all of this tuning, it is still fairly slow. Full scan on an array of 250 elements can be done approx 8 times/ms

*/
///pSearchFor: can be a string, or an object, or even a function
///pOnlyReturnFirstIndex: true will return the int value of the first matching index
///pStrictTypeComparison: true uses === which requires type to match, false uses == which doesn't check type and will try to convert the value
if (!Array.prototype.findit) Array.prototype.findit = function (pSearchFor, pOnlyReturnFirstIndex, pStrictTypeComparison) {
    if (this.length < 1)
        return -1;
    //validate args / set defaults
    if ((typeof (pSearchFor) == "undefined") || (pSearchFor == null))
        return false; //not searching for anything...
    if ((typeof (pOnlyReturnFirstIndex) == "undefined") || (pOnlyReturnFirstIndex == null))
        pOnlyReturnFirstIndex = false;
    if ((typeof (pStrictTypeComparison) == "undefined") || (pStrictTypeComparison == null))
        pStrictTypeComparison = true;
    var len = this.length;
    var i;
    if (pOnlyReturnFirstIndex) {
        //only loop until we find a match
        var returnArray = [];
        if (typeof (pSearchFor) == 'function') {//special case testing for functions
            for (i = 0; i < len; i++) {
                if (pSearchFor.test(this[i])) {
                    return i;
                }
            }
        } else if (pStrictTypeComparison) { //strict comparison. Both value and type must match. "==="  (equals without type coersion)...meaning: "0" != 0 != false
            for (i = 0; i < len; i++) {
                if (this[i] === pSearchFor) {
                    return i;
                }
            }
        } else { //loose comparison. Value checking only. (equals without type coersion)...meaning: "0" == 0 == false
            for (i = 0; i < len; i++) {
                if (this[i] == pSearchFor) {
                    return i;
                }
            }
        }
        return false;
    } else {
        //we want to loop through the entire array no matter what...and return all matching indexes
        var returnArray = [];
        if (typeof (pSearchFor) == 'function') {//special case testing for functions
            for (i = 0; i < len; i++) {
                if (pSearchFor.test(this[i])) {
                    returnArray.push(i);
                }
            }
        } else if (pStrictTypeComparison) { //strict comparison. Both value and type must match. "==="  (equals without type coersion)...meaning: "0" != 0 != false
            for (i = 0; i < len; i++) {
                if (this[i] === pSearchFor) {
                    returnArray.push(i);
                }
            }
        } else { //loose comparison. Value checking only. (equals without type coersion)...meaning: "0" == 0 == false
            for (i = 0; i < len; i++) {
                if (this[i] == pSearchFor) {
                    returnArray.push(i);
                }
            }
        }
        return returnArray.length <= 0 ? false : returnArray;
    }
}

// Array Remove by Index - By John Resig (MIT Licensed)
if (!Array.prototype.remove) Array.prototype.remove = function (from, to) {
    var rest = this.slice((to || from) + 1 || this.length);
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
};

if (!Array.prototype.swap) Array.prototype.swap = function (a, b) {
    var tmp = this[a];
    this[a] = this[b];
    this[b] = tmp;
}

//Remove all elements by value - by Aaron Murray
if (!Array.prototype.removeit) Array.prototype.removeit = function (a) {
    if (this.length < 1)
        return;
    var index = this.findit(a, true, true);
    if (index < 0 || index === false)
        return;
    while (index !== false && index >= 0 && this.length > 0) {
        this.splice(index, 1);
        index = this.findit(a, true, true);
    }
}

//Remove all elements from an array from this array (by value) - by Aaron Murray
if (!Array.prototype.removethem) Array.prototype.removethem = function (arr) {
    if (this.length < 1)
        return;
    for (var i = 0; i < arr.length; i++)
        this.removeit(arr[i]);
}

//get sorted, distinct values - treat this like a static method - does not change original array
if (!Array.prototype.getDistinct) Array.prototype.getDistinct = function (pUseQuicksort) {
    if (this.length <= 1)
        return [];

//    //this is brutally slow - brute force
//    var c = [];
//    for (var i = 0; i < this.length; i++)
//        if (!c.contains(this[i]))
//            c.push(this[i]);
//    return c;


    //new copy of array
    var a = [];
    for (var i = 0; i < this.length; i++)
        a.push(this[i]);

    //sort so we can fast cheat on the distinct loop below
    if (pUseQuicksort)
        a.quickSort();
    else
        a.sort();


    //    //version 1 - pare down - 751ms for 2476 calls
    //    var len = a.length; //for speed
    //    for (var i = 0; i < len; i++) {
    //        if (i == a.length)
    //            break; //we're done
    //        if (a[i] == a[i + 1]) {
    //            a.remove(i + 1); //remove next dupe item
    //            i--; //don't increment our index because the array is shortening towards us
    //            len = a.length;
    //        }
    //    }
    //    return a;

    //version 2 - buildup - 532ms for 2476 calls
    var b = [];
    b.push(a[0]);
    for (var i = 0; i < a.length - 1; i++) {
        if (b[b.length - 1] != a[i + 1]) {
            b.push(a[i + 1]);
        }
    }
    return b;
}

//Stuff for QuickSort

//if (!Array.prototype.partition) //prototype already has a partition method...
Array.prototype.partition = function (begin, end, pivot, pArrayElementType, pOptionalValueProperty, pOptionalPropertyType, pOptionalArrHasGetAttribute) {
    pArrayElementType = pArrayElementType || 'number';
    pOptionalPropertyType = pOptionalPropertyType || 'number';
    pOptionalValueProperty = pOptionalValueProperty || null;
    pOptionalArrHasGetAttribute = pOptionalArrHasGetAttribute || typeof (this[0].getAttribute) != "undefined";

    var piv = this[pivot];
    this.swap(pivot, end - 1);
    var store = begin;
    var ix;
    var doSwap = false;

    if (pArrayElementType == 'number' || (pArrayElementType != 'string' && !pOptionalValueProperty)) {
        //default number sort
        var pivAsFloat = parseFloat(piv);
        for (ix = begin; ix < end - 1; ++ix) {
            if (parseFloat(this[ix]) <= pivAsFloat) {
                doSwap = true;
            }
        }
    } else if (pArrayElementType == 'string') {
        //text sort
        var pivAsUpper = piv.toUpperCase();
        for (ix = begin; ix < end - 1; ++ix) {
            if (this[ix].toUpperCase() <= pivAsUpper) {
                doSwap = true;
            }
        }
    } else {
        //object - we will sort using the property
        var hasGetAttribute = typeof (this[0].getAttribute) != "undefined";
        if (pOptionalPropertyType == 'string') {
            //text sort
            var pivAsUpper = piv.getAttribute(pOptionalValueProperty).toString().toUpperCase();
            for (ix = begin; ix < end - 1; ++ix) {
                if (hasGetAttribute) {
                    if (this[ix].getAttribute(pOptionalValueProperty).toString().toUpperCase() <= pivAsUpper) {
                        doSwap = true;
                    }
                } else {
                    var pivAsUpper = piv[pOptionalValueProperty].toString().toUpperCase();
                    if (this[ix][pOptionalValueProperty].toString().toUpperCase() <= pivAsUpper) {
                        doSwap = true;
                    }
                }
            }
        }
        else //if (!pOptionalPropertyType || pOptionalPropertyType == null || pOptionalPropertyType == 'number') 
        {
            //default number sort
            for (ix = begin; ix < end - 1; ++ix) {
                if (pOptionalArrHasGetAttribute) {
                    var pivAsUpper = parseFloat(piv.getAttribute(pOptionalValueProperty));
                    if (parseFloat(this[ix].getAttribute(pOptionalValueProperty)) <= pivAsUpper) {
                        doSwap = true;
                    }
                } else {
                    var pivAsFloat = parseFloat(piv[pOptionalValueProperty]);
                    if (parseFloat(this[ix][pOptionalValueProperty]) <= pivAsFloat) {
                        doSwap = true;
                    }
                }
            }
        }
    }

    if (doSwap) {
        this.swap(store, ix);
        ++store;
    }

    this.swap(end - 1, store);

    return store;
}


if (!Array.prototype.qsort) Array.prototype.qsort = function (begin, end, pArrayElementType, pOptionalValueProperty, pOptionalPropertyType) {
    if (end - 1 > begin) {
        var pivot = begin + Math.floor(Math.random() * (end - begin));
        pivot = this.partition(begin, end, pivot, pArrayElementType, pOptionalValueProperty, pOptionalPropertyType, typeof (this[0].getAttribute) != "undefined");
        this.qsort(begin, pivot, pArrayElementType, pOptionalValueProperty, pOptionalPropertyType);
        this.qsort(pivot + 1, end, pArrayElementType, pOptionalValueProperty, pOptionalPropertyType);
    }
}


if (!Array.prototype.quickSort) Array.prototype.quickSort = function (pOptionalArrayElementType, pOptionalValueProperty, pOptionalPropertyType) {
    if (this.length <= 1)
        return;
    pOptionalArrayElementType = pOptionalArrayElementType || typeof(this[0]);
    pOptionalValueProperty = pOptionalValueProperty || null;
    pOptionalPropertyType = pOptionalPropertyType || null;
    this.qsort(0, this.length, pOptionalArrayElementType, pOptionalValueProperty, pOptionalPropertyType);
}





//Stuff for MergeSort

if (!Array.prototype.merge_inplace) Array.prototype.merge_inplace = function (begin, begin_right, end) {
    for (; begin < begin_right; ++begin) {
        if (this[begin] > this[begin_right]) {
            var v = this[begin];
            this[begin] = this[begin_right];
            this.insert(begin_right, end, v);
        }
    }
}

if (!Array.prototype.msort) Array.prototype.msort = function (begin, end) {
    var size = end - begin;
    if (size < 2) return;

    var begin_right = begin + Math.floor(size / 2);

    this.msort(begin, begin_right);
    this.msort(begin_right, end);
    this.merge_inplace(begin, begin_right, end);
}

if (!Array.prototype.merge_sort_inplace) Array.prototype.merge_sort_inplace = function () {
    this.msort(0, this.length);
}

if (!Array.prototype.fillRand) Array.prototype.fillRand = function (pCount, pType, pMin, pMax) {
    //validate params
    if (typeof(pCount) == 'undefined') pCount = this.length;
    if (pCount < 1)
        pCount = 100;
    if (this.length < pCount)
        this.length = pCount;
    if (typeof (pType) == 'undefined' || pType == null)
        pType = 'integer';
    else if ((typeof(pType)).toLowerCase() != 'string')
        pType = (typeof(pType));
    pType = pType.toLowerCase();

    //fill array based on type
    if (pType == 'integer' || pType == 'number') {
        if (typeof (pMin) == 'undefined') pMin = 0;
        if (typeof (pMax) == 'undefined') pMax = 999999;
        for (var i = 0; i < pCount; i++)
            this[i] = Math.rand(pMin, pMax, []);
    } else if (pType == 'float') {
        if (typeof (pMin) == 'undefined') pMin = 0;
        if (typeof (pMax) == 'undefined') pMax = 999999;
        for (var i = 0; i < pCount; i++)
            this[i] = (Math.random() * pMax) + pMin;
    } else if (pType == 'bool' || pType == 'boolean') {
        if (typeof (pMin) == 'undefined') pMin = 3;
        if (typeof (pMax) == 'undefined') pMax = 8;
        for (var i = 0; i < pCount; i++)
            this[i] = "".newWord(pMin, pMax, true, true, false);
    } else if (pType == 'word') {
        if (typeof (pMin) == 'undefined') pMin = 3;
        if (typeof (pMax) == 'undefined') pMax = 8;
        for (var i = 0; i < pCount; i++)
            this[i] = "".newWord(pMin, pMax, true, true, false);
    } else if (pType == 'string') {
        if (typeof (pMin) == 'undefined') pMin = 3;
        if (typeof (pMax) == 'undefined') pMax = 8;
        for (var i = 0; i < pCount; i++)
            this[i] = "".newWord(pMin, pMax, true, true, true);
    } else if (pType.indexOf('array') == 0) {
        if (typeof (pMin) == 'undefined') pMin = 3;
        if (typeof (pMax) == 'undefined') pMax = 8;
        for (var i = 0; i < pCount; i++) {
            this[i] = [];
            this[i].fillRand(Math.rand(pMin, pMax, []), 'integer', 0, 9999);
        }
    } else if (pType.indexOf('function') == 0) {
        if (typeof (pMin) == 'undefined') pMin = 3;
        if (typeof (pMax) == 'undefined') pMax = 8;
        for (var i = 0; i < pCount; i++) 
            this[i] = function() {};
    } else { //if (pType == 'object') 
        if (typeof (pMin) == 'undefined') pMin = 1;
        if (typeof (pMax) == 'undefined') pMax = 5;
        for (var i = 0; i < pCount; i++) {
            this[i] = {};
            this[i]["id"] = "".newWord(5, 8, true, false, true);
            var props = Math.rand(pMin, pMax, []);
            for (var p=0;p<props;p++)
                this[i]["".newWord()] = "".newWord();
        }
    } 
}



// --------------------------------------------------------------------------------------
// -- STRING ----------------------------------------------------------------------------
// --------------------------------------------------------------------------------------

//These 3 add trim, ltrim, rtrim to variables globally
//Example usage: var myString = " hi my name is "; myString.trim(); //myString == "hi my name is"
if (!String.prototype.trim) String.prototype.trim = function () {
    return this.replace(/^\s+|\s+$/g, "");
}
if (!String.prototype.ltrim) String.prototype.ltrim = function () {
    return this.replace(/^\s+/, "");
}
if (!String.prototype.rtrim) String.prototype.rtrim = function () {
    return this.replace(/\s+$/, "");
}
if (!String.prototype.endsWith) String.prototype.endsWith = function (str) {
    return (this.length >= str.length) && (this.substr(this.length - str.length, str.length) == str);
}
//get the count of letter matches between 2 strings
if (!String.prototype.charMatchCount) String.prototype.charMatchCount = function (str) {
    if (typeof (str) == "undefined" || str == null)
        return 0;
    var words = [];
    if (typeof (str) == "string")
        words.push(str);
    else
        words = str;

    var highestcount = 0;
    for (var x = 0; x < words.length; x++) {
        var word = words[x];
        var thiscount = 0;
        matchedIndeces = [];
        for (var i = 0; i < word.length; i++) {
            for (var j = 0; j < this.length; j++) {
                if (word.charAt(i) == this.charAt(j) && !matchedIndeces.contains(j)) {
                    thiscount++;
                    matchedIndeces.push(j);
                    break; //stop this inner loop
                }
            }
        }
        if (thiscount > highestcount)
            highestcount = thiscount;
    }
    return highestcount;
}

if (!String.prototype.startsWith) String.prototype.startsWith = function (pStr, pCaseSensitive) {
    if (typeof (pStr) == "undefined" || pStr == null || this.length <= pStr.length)
        return false;
    if (pCaseSensitive)
        return this.substr(0, pStr.length) == pStr;
    return this.toLowerCase().substr(0, pStr.length) == pStr.toLowerCase();
}
if (!String.prototype.contains) String.prototype.contains = function (str) {
    return this.indexOf(str) !== -1;
}

//This does not reverse in place - strings are immutable - it returns a reversed string
if (!String.prototype.reverse) String.prototype.reverse = function () {
    //a few length tests to start with for extra speed when calling hundreds of thousands of times
    var l = this.length;
    if (l <= 1)
        return this;
    if (l == 2)
        return this.charAt(1) + this.charAt(0);
    if (l == 3)
        return this.charAt(2) + this.charAt(1) + this.charAt(0);
    if (l == 4)
        return this.charAt(3) + this.charAt(2) + this.charAt(1) + this.charAt(0);
    if (l == 5)
        return this.charAt(4) + this.charAt(3) + this.charAt(2) + this.charAt(1) + this.charAt(0);

    return this.split('').reverse().join(''); //faster in IE than doing the loop below

    //faster in Chrome
    //    var s;
    //    for (i = this.length, s = ""; i > 0; s = s + this.charAt(--i)) { }
    //    return s;
}

//Treat this like a static method. It returns a new word instead of modifying the instance
//make a random word string var foo = String.newWord();
if (!String.prototype.newWord) String.prototype.newWord = function (pMinLength, pMaxLength, pAllowUpperCase, pAllowLowerCase, pAllowNumbers) {
    if (!pMinLength)
        pMinLength = 3;
    if (!pMaxLength)
        pMaxLength = 8;
    if (!pAllowUpperCase)
        pAllowUpperCase = true;
    if (!pAllowLowerCase)
        pAllowLowerCase = true;
    if (!pAllowNumbers)
        pAllowNumbers = false;

    var count = Math.rand(pMinLength, pMaxLength);

    var i, j;
    var word = '';
    for (j = 0; j < count; j++) {
        if (pAllowNumbers && Math.bool() && Math.bool()) {
            i = Math.rand(0, 9).toString(10);
        } else {
            var charCode = Math.rand(97, 122);
            if (pAllowUpperCase && Math.bool())
                charCode -= 32;
            i = String.fromCharCode(charCode);
        }
        word += i;
    }
    return word;
}

// --------------------------------------------------------------------------------------
// -- OBJECT ----------------------------------------------------------------------------
// --------------------------------------------------------------------------------------




