﻿
/* Nice round, useful for graphs */

var niceRound = function(value) {
    //if (value < 1.5) return 1;
    var zeroes = Math.floor(Math.log(value + 0.0001) / Math.log(10));
    var base = Math.pow(10, zeroes);
    var increment = value / base;
    if (increment < 1.5) { return 1 * base; }
    else if (increment < 3.5) { return 2 * base; }
    else if (increment < 7.5) { return 5 * base; }
    else { return 10 * base; }
}

/* Re-binding functions */

var bind = function(obj, func) {
    return function() { func.apply(obj, arguments); }
}

var event = function(obj, func) {
    return function() {
        var a = [this];
        for (var k = 0; k < arguments.length; ++k) {
            a.push(arguments[k]);
        }
        func.apply(obj, a);
    }
}

/* URL manipulators */

function getController() {
    var regex = new RegExp("^http\:\/\/(.+)[/]+([-_0-9a-zA-Z]+)[\?|/]?(.*)$");
    var results = regex.exec(window.location.href);
    var ControllerString;
    if (results != null) {
        ControllerString = "/" + results[2];
    }
    return ControllerString;
}

function getFirstController() {
    var regex = new RegExp("^http\:\/\/([-_0-9a-zA-Z.:]+)[/]+([-_0-9a-zA-Z]+)[\?|/]?(.*)$");
    var results = regex.exec(window.location.href);
    var ControllerString;
    if (results != null) {
        ControllerString = "/" + results[2];
    }
    return ControllerString;
}

function getURLParams() {
    var data = window.location.toString().split("?");
    var obj = {};
    if (data.length > 1) {
        data = data[1].split("#")[0].split("&");
        for (var k = 0; k < data.length; ++k) {
            keyval = data[k].split("=");
            if (keyval.length > 1) {
                obj[keyval[0]] = keyval[1];
            }
        }
    }
    return obj;
}

function makeURLParams(newParams) {
    var baseUrl = window.location.toString().split("?")[0];
    var paramPart = [], pCount = 0;
    for (var i in newParams) {
        ++pCount;
        paramPart.push(i + "=" + newParams[i]);
    }
    if (pCount) { return baseUrl + "?" + paramPart.join("&"); }
    else { return baseUrl; }
}


/* Date time formatting */

var Util = {
    formatTime: function(timestamp) {
        var time = '';
        var s = Math.floor(timestamp / 1000);
        var hh = Math.floor(s / 3600);
        if (hh > 0) {
            if (hh < 10) {
                time += '0';
                time += hh;
            } else {
                time += hh;
            }
            time += ':';
        }
        var min = Math.floor((s % 3600) / 60);
        if (min < 10) {
            time += '0';
            time += min;
        } else {
            time += min;
        }
        var sec = s % 60;
        time += ':';
        if (sec < 10) {
            time += '0';
            time += sec;
        } else {
            time += sec;
        }
        return time;
    },
    minutesToHrMinutes: function(minutes) {
        var ret = ""
        if (minutes >= 60) { ret = Math.floor(minutes / 60) + " hours "; }
        return ret + (minutes % 60) + " minutes";
    }
}

function formatDate(dx, fmt, monthNames) {
    var d = new Date(dx);
    //dx.setUTCSeconds(d);
    var leftPad = function(n) {
        n = "" + n;
        return n.length == 1 ? "0" + n : n;
    };
    var r = [];
    var escape = false;
    if (monthNames == null)
        monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
    for (var i = 0; i < fmt.length; ++i) {
        var c = fmt.charAt(i);

        if (escape) {
            switch (c) {
                case 'h': c = "" + d.getUTCHours(); break;
                case 'H': c = leftPad(d.getUTCHours()); break;
                case 'M': c = leftPad(d.getUTCMinutes()); break;
                case 'S': c = leftPad(d.getUTCSeconds()); break;
                case 'd': c = "" + d.getUTCDate(); break;
                case 'm': c = "" + (d.getUTCMonth() + 1); break;
                case 'y': c = "" + d.getUTCFullYear(); break;
                case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
            }
            r.push(c);
            escape = false;
        }
        else {
            if (c == "%")
                escape = true;
            else
                r.push(c);
        }
    }
    return r.join("");
}


var dateFormat = function() {
    var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
		timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
		timezoneClip = /[^-+\dA-Z]/g,
		pad = function(val, len) {
		    val = String(val);
		    len = len || 2;
		    while (val.length < len) val = "0" + val;
		    return val;
		};

    // Regexes and supporting functions are cached through closure
    return function(date, mask, utc) {
        var dF = dateFormat;

        // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
        if (arguments.length == 1 && (typeof date == "string" || date instanceof String) && !/\d/.test(date)) {
            mask = date;
            date = undefined;
        }

        // Passing date through Date applies Date.parse, if necessary
        date = date ? new Date(date) : new Date();
        if (isNaN(date)) throw new SyntaxError("invalid date");

        mask = String(dF.masks[mask] || mask || dF.masks["default"]);

        // Allow setting the utc argument via the mask
        if (mask.slice(0, 4) == "UTC:") {
            mask = mask.slice(4);
            utc = true;
        }

        var _ = utc ? "getUTC" : "get",
			d = date[_ + "Date"](),
			D = date[_ + "Day"](),
			m = date[_ + "Month"](),
			y = date[_ + "FullYear"](),
			H = date[_ + "Hours"](),
			M = date[_ + "Minutes"](),
			s = date[_ + "Seconds"](),
			L = date[_ + "Milliseconds"](),
			o = utc ? 0 : date.getTimezoneOffset(),
			flags = {
			    d: d,
			    dd: pad(d),
			    ddd: dF.i18n.dayNames[D],
			    dddd: dF.i18n.dayNames[D + 7],
			    m: m + 1,
			    mm: pad(m + 1),
			    mmm: dF.i18n.monthNames[m],
			    mmmm: dF.i18n.monthNames[m + 12],
			    yy: String(y).slice(2),
			    yyyy: y,
			    h: H % 12 || 12,
			    hh: pad(H % 12 || 12),
			    H: H,
			    HH: pad(H),
			    M: M,
			    MM: pad(M),
			    s: s,
			    ss: pad(s),
			    l: pad(L, 3),
			    L: pad(L > 99 ? Math.round(L / 10) : L),
			    t: H < 12 ? "a" : "p",
			    tt: H < 12 ? "am" : "pm",
			    T: H < 12 ? "A" : "P",
			    TT: H < 12 ? "AM" : "PM",
			    Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
			    o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
			    S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
			};

        return mask.replace(token, function($0) {
            return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
        });
    };
} ();

// Some common format strings
dateFormat.masks = {
    "default": "ddd mmm dd yyyy HH:MM:ss",
    shortDate: "m/d/yy",
    mediumDate: "mmm d, yyyy",
    longDate: "mmmm d, yyyy",
    fullDate: "dddd, mmmm d, yyyy",
    shortTime: "h:MM TT",
    mediumTime: "h:MM:ss TT",
    longTime: "h:MM:ss TT Z",
    isoDate: "yyyy-mm-dd",
    isoTime: "HH:MM:ss",
    isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
    isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};

// Internationalization strings
dateFormat.i18n = {
    dayNames: [
		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
		"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
	],
    monthNames: [
		"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
		"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
	]
};

// For convenience...
Date.prototype.format = function(mask, utc) {
    return dateFormat(this, mask, utc);
};


var makeDateString = function(ticks, fmt) {
    var numTicks = parseInt(ticks);
    if (fmt) { return dateFormat(numTicks, fmt); }
    return dateFormat(numTicks, "dd/mm/yyyy HH:MM");
}


function duration(d1, d2) {
    var diff = dateToMS(d2) - dateToMS(d1);
    var diffDays = diff / 1000 / 3600 / 24;
    var wks = Math.floor(diffDays / 7);
    var dys = Math.round(diffDays - (wks * 7));
    var result = " "
    if (wks) { result += (wks + " weeks "); }
    if (dys) { result += (dys + " days "); }
    if (result != " ")
        return result;
    else
        return " 0 days "
}

function dateToMS(d) {
    return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
}

function textJoin(stringArray, separator, lastSeparator) {
    if (stringArray.length > 1) {
        var res = stringArray[0];
        for (var k = 1; k < stringArray.length - 1; ++k) {
            res += separator + stringArray[k];
        }
        res += lastSeparator + stringArray[stringArray.length - 1];
        return res;
    }
    else {
        if (stringArray.length > 0)
            return stringArray[0];
        else { return ""; }
    }
}



/* Convertor */

var Convertor = {
    METRIC: 0,
    IMPERIAL: 1,
    UNIT_SYSTEM: 0, //default

    SPEED: ['km/h', 'mph'],
    DISTANCE: ['m', 'yd'],
    ALTITUDE: ['m', 'ft'],
    DISTANCE_KM: ['km', 'mi'],
    CALORIES: 'kcal',
    MS_KM_RATIO: 3.6,
    METER_TO_YARD: [1.0, 1.0936133],
    METER_TO_FEET: [1.0, 3.2808399],
    MY_TO_KM: [1000.0, 1760.0],
    KM_TO_MILES: [1.0, 0.621371],
    KG_TO_LBS: [1.0, 2.20462262],
    CM_TO_FT: [1.0, 0.032808399],

    toDistance: function(val, ms) {
        val *= Convertor.METER_TO_YARD[ms];
        if (val <= Convertor.MY_TO_KM[ms] && ms != 1) {
            return val;
        } else {
            val = val / Convertor.MY_TO_KM[ms];
            return val;
        }
    },
    
    toDistanceKM: function(val, ms) {
        val *= Convertor.METER_TO_YARD[ms];
        val = val / Convertor.MY_TO_KM[ms];
        return val;
    },
    
    toDistanceString: function(val, ms) {
        if (ms == null) { return Convertor.toDistanceString(val, Convertor.UNIT_SYSTEM); }
        val *= Convertor.METER_TO_YARD[ms];
        if (val <= Convertor.MY_TO_KM[ms] && ms != 1) {
            return val.toFixed(0) + ' ' + Convertor.DISTANCE[ms];
        } else {
            val = val / Convertor.MY_TO_KM[ms];
            return val.toFixed(2) + ' ' + Convertor.DISTANCE_KM[ms];
        }
    },

    toDistanceStringNMS: function(val) {
        return this.toDistanceString(val, Convertor.UNIT_SYSTEM);
    },

    toAltitudeString: function(val, ms) {
        if (ms == null) { return Convertor.toDistanceString(val, Convertor.UNIT_SYSTEM); }
        val *= Convertor.METER_TO_FEET[ms];
        return val.toFixed(0) + ' ' + Convertor.ALTITUDE[ms];
    },
    toAltitude: function(val, ms) {
        if (ms == null) { return Convertor.toDistanceString(val, Convertor.UNIT_SYSTEM); }
        val *= Convertor.METER_TO_FEET[ms];
        return val;
    },

    toAltitudeStringNMS: function(val) {
        return this.toAltitudeString(val, Convertor.UNIT_SYSTEM);
    },

    toSpeedString: function(val, ms) {
        if (ms == null) { return this.toSpeedStringNMS(val); }
        return this.toSpeed(val, ms) + ' ' + Convertor.SPEED[ms];
    },
    toSpeedStringNMS: function(val) {
        return this.toSpeed(val) + ' ' + Convertor.SPEED[Convertor.UNIT_SYSTEM];
    },

    toSpeed: function(val, ms) {
        if (ms == null) { return this.toSpeedNMS(val); }
        return (val * Convertor.MS_KM_RATIO * Convertor.KM_TO_MILES[ms]).toFixed(1);
    },
    toSpeedFull: function(val, ms) {
        return (val * Convertor.MS_KM_RATIO * Convertor.KM_TO_MILES[ms]);
    },
    toSpeedNMS: function(val) {
        return (val * Convertor.MS_KM_RATIO * Convertor.KM_TO_MILES[Convertor.UNIT_SYSTEM]).toFixed(1);
    },

    toPaceString: function(val, ms) {
        if (ms == null) { return this.toPaceString(val, Convertor.UNIT_SYSTEM); }
        var valConv = val;
        var minkm = (1 / valConv) * 60;
        var seckm = Math.round((minkm - Math.floor(minkm)) * 60);
        if (seckm < 10) { seckm = "0" + seckm.toString(); }
        return Math.floor(minkm) + ':' + seckm + ' min/' + Convertor.DISTANCE_KM[ms];
    },

    toCaloriesString: function(val) { return val + ' ' + Convertor.CALORIES; }

}




/* Natural sort */



var naturalSortAsc = function(a, b) {
    // setup temp-scope variables for comparison evauluation
    var x = a.toString().toLowerCase() || '';
    var y = b.toString().toLowerCase() || '';
    var nC = String.fromCharCode(0),
                xN = x.replace(/([-]{0,1}[0-9.]{1,})/g, nC + '$1' + nC).split(nC),
                yN = y.replace(/([-]{0,1}[0-9.]{1,})/g, nC + '$1' + nC).split(nC),
                xD = (new Date(x)).getTime(), yD = (new Date(y)).getTime();
    // natural sorting of dates
    if (xD && yD && xD < yD)
        return -1;
    else if (xD && yD && xD > yD)
        return 1;
    // natural sorting through split numeric strings and default strings
    for (var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++)
        if ((parseFloat(xN[cLoc]) || xN[cLoc]) < (parseFloat(yN[cLoc]) || yN[cLoc]))
        return -1;
    else if ((parseFloat(xN[cLoc]) || xN[cLoc]) > (parseFloat(yN[cLoc]) || yN[cLoc]))
        return 1;
    return 0;
}
var naturalSortDesc = function(a, b) {
    // setup temp-scope variables for comparison evauluation
    var x = a.toString().toLowerCase() || '', y = b.toString().toLowerCase() || '',
                nC = String.fromCharCode(0),
                xN = x.replace(/([-]{0,1}[0-9.]{1,})/g, nC + '$1' + nC).split(nC),
                yN = y.replace(/([-]{0,1}[0-9.]{1,})/g, nC + '$1' + nC).split(nC),
                xD = (new Date(x)).getTime(), yD = (new Date(y)).getTime();
    // natural sorting of dates
    if (xD && yD && xD < yD)
        return 1;
    else if (xD && yD && xD > yD)
        return -1;
    // natural sorting through split numeric strings and default strings
    for (var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++)
        if ((parseFloat(xN[cLoc]) || xN[cLoc]) < (parseFloat(yN[cLoc]) || yN[cLoc]))
        return 1;
    else if ((parseFloat(xN[cLoc]) || xN[cLoc]) > (parseFloat(yN[cLoc]) || yN[cLoc]))
        return -1;
    return 0;
}



var stripNaturalSortAsc = function(a, b) {
    return naturalSortAsc(a.replace(/(<([^>]+)>)/ig, ""), b.replace(/(<([^>]+)>)/ig, ""));
}

var stripNaturalSortDesc = function(a, b) {
    return naturalSortDesc(a.replace(/(<([^>]+)>)/ig, ""), b.replace(/(<([^>]+)>)/ig, ""));
}

function stringCleanup(str) {
    //var newstr = str.toString().replace("\n", "");
    var newstr = str.replace(/[\s]+[\s]+/, " ").replace(/^\s+/, "").replace(/\s+$/, "");
    return newstr;
}

var distanceSort = function(a, b) {
    var splitA = stringCleanup(a).toString().split(" "), splitB = stringCleanup(b).toString().split(" ");
    //if (splitA.length > 1 && splitB.length > 1)
    var realA = splitA[0] * 1.0;
    var realB = splitB[0] * 1.0;
    if (splitA[1] == 'km') realA *= 1000;
    if (splitB[1] == 'km') realB *= 1000;

    if (splitA[1] == 'mi') realA *= 1760; // yards
    if (splitB[1] == 'mi') realB *= 1760;

    return (realA < realB) ? -1 : ((realA == realB) ? 0 : 1);

}

var distanceSortAsc = function(a, b) {
    return distanceSort(a, b)
}
var distanceSortDesc = function(a, b) {
    return (0 - distanceSort(a, b));
}


var durationSortAsc = function(a, b) {
    return durationToNumber(stringCleanup(b)) - durationToNumber(stringCleanup(a));
}

var durationSortDesc = function(a, b) {
    return durationToNumber(stringCleanup(a)) - durationToNumber(stringCleanup(b));
}



var durationToNumber = function(dur) {
    var durationToNumberPart = function(num, text) {
        var reDays = /[d]+.*/i;
        if (reDays.exec(text)) { return num * 1.0; }
        else { return num * 7.0; }
    }
    if (dur == "Today") return 0;
    if (dur == "Finished" || dur == "Ended") return 9999;
    if (dur == "Not limited") { return 9998; }
    var splitDur = dur.toString().split(" ");
    var durNum = 0;
    if (splitDur.length > 1) {
        for (var k = 0; k < splitDur.length - 1; k += 2) {
            durNum += durationToNumberPart(splitDur[k], splitDur[k + 1]);
        }
    }
    return durNum;
}

