/****************************************************************************/
//Utility Functions : appx-client-util.js
//01/28/2014
//Chris Smith
/****************************************************************************/

//binary.js: http://ats.oka.nu/titaniumcore/js/tools/binary.readme.txt
//s2ba_be(i) Converts a 16bit-integer to a byte array in big-endian byte order.
//i2ba_be(i) Converts a 32bit-integer to a byte array in big-endian byte order.
//DataStream.js: https://github.com/kig/DataStream.js - might be useful too?
"use strict";
function hton16(i) {
    return [
        0xff & (i >> 8),
        0xff & (i >> 0)
    ];
}

function hton32(i) {
    return [
        0xff & (i >> 24),
        0xff & (i >> 16),
        0xff & (i >> 8),
        0xff & (i >> 0)
    ];
}

// Utilities

function cleanClassName(x) {
    return (x.replace(/[^_0-9a-zA-Z]/g, "_"));
}

function ab2int32(buf) {
    return new DataView(new Uint8Array(buf).buffer).getUint32(0);
}

// Convert buffer to string
function ab2str(buf) {
    return String.fromCharCode.apply(null, new Uint16Array(buf)) + " ";
}

// Convert buffer to string
function ab3str(buf) {
    return String.fromCharCode.apply(" ", new Uint8Array(buf)) + " <br/>";
}

// Convert buffer to string
function ab4str(array) {
    var string = "";
    for (var i = 0; i < array.length; i++) {
        if (array[i] != 0) {
            string += String.fromCharCode(array[i]);
        }
        else {
            string += " ";
        }
    }
    return string + " ";
}

// Convert buffer to string
function buf2str(buf) {
    var str = '';
    var charcode;
    logactivity("got buffer:  " + JSON.stringify(buf));
    for (var i = 0; i < buf.length; i++) {
        charcode = buf.readUInt8(i);
        if (charcode == 0) {
            str += " ";
        }
        else {
            str += String.fromCharCode(charcode);
        }
    }
    buf = [];
    return str;
}

// Convert hex to ascii
function hex2a(hex) {
    var str = '';
    for (var i = 0; i < hex.length; i += 2) {
        str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
    }
    return str;
}

// Convert string to buffer
function str2ab(str, length) {
    var buf = new Uint16Array(length); // 2 bytes for each char
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        buf[i] = str.charCodeAt(i);
    }
    return buf;
}

// Convert string to buffer
function str3ab(str, length) {
    var buf = new Uint8Array(length); // 2 bytes for each char
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        buf[i] = str.charCodeAt(i);
    }
    return buf;
}

// Convert string to buffer
function str4ab(str) {
    var buf = new Uint8Array(str.length); // 1 bytes for each char
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        buf[i] = str.charCodeAt(i);
    }
    return buf;
}

function toUTF8Array(str) {
    var utf8 = [];
    for (var i = 0; i < str.length; i++) {
        var charcode = str.charCodeAt(i);
        if (charcode < 0x80) utf8.push(charcode);
        else if (charcode < 0x800) {
            utf8.push(0xc0 | (charcode >> 6),
                0x80 | (charcode & 0x3f));
        }
        else if (charcode < 0xd800 || charcode >= 0xe000) {
            utf8.push(0xe0 | (charcode >> 12),
                0x80 | ((charcode >> 6) & 0x3f),
                0x80 | (charcode & 0x3f));
        }
        // surrogate pair
        else {
            i++;
            // UTF-16 encodes 0x10000-0x10FFFF by
            // subtracting 0x10000 and splitting the
            // 20 bits of 0x0-0xFFFFF into two halves
            charcode = 0x10000 + (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff))
            utf8.push(0xf0 | (charcode >> 18),
                0x80 | ((charcode >> 12) & 0x3f),
                0x80 | ((charcode >> 6) & 0x3f),
                0x80 | (charcode & 0x3f));
        }
    }
    return utf8;
}

function getUnicodeCharacter(cp) {
    if (cp >= 0 && cp <= 0xD7FF || cp >= 0xE000 && cp <= 0xFFFF) {
        return String.fromCharCode(cp);
    }
    else if (cp >= 0x10000 && cp <= 0x10FFFF) {

        // we substract 0x10000 from cp to get a 20-bits number
        // in the range 0..0xFFFF
        cp -= 0x10000;

        // we add 0xD800 to the number formed by the first 10 bits
        // to give the first byte
        var first = ((0xffc00 & cp) >> 10) + 0xD800

        // we add 0xDC00 to the number formed by the low 10 bits
        // to give the second byte
        var second = (0x3ff & cp) + 0xDC00;

        return String.fromCharCode(first) + String.fromCharCode(second);
    }
}

function decodeUTF16LE(binaryStr) {
    var cp = [];
    for (var i = 0; i < binaryStr.length; i += 2) {
        cp.push(
            binaryStr.charCodeAt(i) |
            (binaryStr.charCodeAt(i + 1) << 8)
        );
    }

    return String.fromCharCode.apply(String, cp);
}

function fixedCharCodeAt(str, idx) {
    // ex. fixedCharCodeAt ('\uD800\uDC00', 0); // 65536
    // ex. fixedCharCodeAt ('\uD800\uDC00', 1); // 65536
    idx = idx || 0;
    var code = str.charCodeAt(idx);
    var hi, low;

    // High surrogate (could change last hex to 0xDB7F to treat high
    // private surrogates as single characters)
    if (0xD800 <= code && code <= 0xDBFF) {
        hi = code;
        low = str.charCodeAt(idx + 1);
        if (isNaN(low)) {
            throw 'High surrogate not followed by low surrogate in fixedCharCodeAt()';
        }
        return ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
    }
    if (0xDC00 <= code && code <= 0xDFFF) { // Low surrogate
        // We return false to allow loops to skip this iteration since should have
        // already handled high surrogate above in the previous iteration
        return false;
    }
    return code;
}


/*
 * Accepts a date, a mask, or a date and a mask.
 * Returns a formatted version of the given date.
 * The date defaults to the current date/time.
 * The mask defaults to dateFormat.masks.default.
 */

APPX.DateFormatter2 = function (date, datemsk, timemsk) {
    var mymask = datemsk.replace(/y/g, "YY").replace(/YYYY/g, "CCYY").replace(/mm/g, "MM");

    if (datemsk.length > 0 && timemsk.length > 0)
        mymask += " ";
    mymask += timemsk.replace(/l/g, "LLL");

    var offset = 0;
    var pad = -1;
    var result = "";

    while (offset < mymask.length) {
        switch (mymask[offset]) {
            case 'C':
                pad = 0;
                break;
            case 'Y':
                pad = 2;
                break;
            case 'M':
                pad = 4;
                break;
            case 'd':
                pad = 6;
                break;
            case 'H':
                pad = 8;
                break;
            case 'm':
                pad = 10;
                break;
            case 's':
                pad = 12;
                break;
            case 'L':
                pad = 14;
                break;
        }

        if (pad >= 0) {
            while (result.length < pad) result += "-";
            result += date.substring(offset, offset + 2);
            offset++;;
            pad = -1;
        }

        offset++;
    }

    while (result.length < 16) result += "-";

    return result;
}

APPX.DateFormatter = 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 = APPX.DateFormatter;

        // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
        if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object 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 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
APPX.DateFormatter.masks = {
    "default": "ddd mmm dd yyyy HH:MM:ss",
    shortDate: "m/d/yy",
    shortY2KDate: "mm/dd/yyyy",
    shortY2KDateTime: "mm/dd/yyyy HH:MM:ss",
    shortMySqlDate: "yyyy-mm-dd",
    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",
    appxDateTime: "yyyymmddHHMMss00",
    isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};

// Internationalization strings
APPX.DateFormatter.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"
    ]
};

//use to Sort an array of objects
// "-PropertyName" for desc
function SortByProperty(property) {
    var sortOrder = 1;
    if (property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a, b) {
        var result = a[property] == null ? -1 : b[property] == null ? 1 : (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result * sortOrder;
    }
}

/*
**Function to return the difference between 2 numbers for array sorting purposes
**
**@param a: first number to compare
**@param b: second number to compare
**
**@return: integer based on which number is bigger
**
*/
function numberSort(a, b){
    return (a - b);
}

//CharView
var appxColor = {
    black: [0, 0, 0], //#000000
    blue: [0, 0, 255], //#0000ff
    cyan: [0, 255, 255], //#00ffff
    darkBlue: [107, 35, 142], //0x6b238e (#00008b)
    darkCyan: [95, 159, 159], //0x5F9F9F (#008b8b)
    darkGray: [169, 169, 169], //#a9a9a9
    darkGreen: [47, 79, 47], //0x2F4F2F (#006400)
    darkMagenta: [142, 35, 107], //0x8E236B (#8b008b)
    darkRed: [142, 35, 35], //0x8E2323 (#8b0000)
    green: [0, 255, 0], //#00ff00//#008000(128)
    lightGray: [211, 211, 211], //#d3d3d3
    magenta: [255, 0, 255], //#ff00ff
    orange: [255, 165, 0], //#ffa500
    red: [255, 0, 0], //#ff0000
    white: [255, 255, 255], //#ffffff
    yellow: [255, 255, 0], //#ffff00
};
var appxColorTable = [
    appxColor.black,
    appxColor.blue,
    appxColor.cyan,
    appxColor.green,
    appxColor.magenta,
    appxColor.red,
    appxColor.yellow,
    appxColor.white,
    appxColor.darkBlue,
    appxColor.darkCyan,
    appxColor.darkGreen,
    appxColor.darkMagenta,
    appxColor.darkRed,
    appxColor.darkGray,
    appxColor.lightGray
];

function appxColorXlate(colorName, alpha) {
    try {
        var clr = null;
        if (colorName != null && colorName.length > 0) {
            // First, look for hex encoded RGB values
            var colorArray = colorName.split('');
            if (colorArray[0] == '#' && ((colorArray.length == 7) || (colorArray.length == 9))) {
                var r = colorName.substring(1, 3); //from, to - parseInt(,16=toHex)
                var g = colorName.substring(3, 5);
                var b = colorName.substring(5, 7);
                clr = [r, g, b];
                if (!alpha) alpha = (colorArray.length == 9 ? colorName.substring(7, 9) : 100); //255
            }

            if (!clr) {
                // If that failed, look for numeric table positions
                var c = colorArray[0];
                if (c >= '0' && c <= '9') //isDigit
                {
                    try {
                        clr = colorTable[parseInt(colorName) - 1];
                    }
                    catch (e) {
                        clr = null;
			console.log(e.stack);
                    }
                }
                else {
                    // OK, last chance.  Maybe it is a color name
                    colorName = colorName.toLowerCase();
                    if (colorName == "black") clr = appxColor.black;
                    if (colorName == "blue") clr = appxColor.blue;
                    if (colorName == "cyan") clr = appxColor.cyan;
                    if (colorName == "darkGray") clr = appxColor.darkGray;
                    if (colorName == "gray") clr = appxColor.gray;
                    if (colorName == "green") clr = appxColor.green;
                    if (colorName == "lightGray") clr = appxColor.lightGray;
                    if (colorName == "magenta") clr = appxColor.magenta;
                    if (colorName == "orange") clr = appxColor.orange;
                    if (colorName == "pink") clr = appxColor.pink;
                    if (colorName == "red") clr = appxColor.red;
                    if (colorName == "white") clr = appxColor.white;
                    if (colorName == "yellow") clr = appxColor.yellow;
                }
            }
        }

        if (!clr || clr.length != 3) {
            console.log("CharView.xlateColor(" + colorName + ") Can't translaet color.  Using 'orange' instead.");
            clr = appxColor.orange;
        }
        if (!alpha) alpha = 1;
        else if (alpha > 1) alpha = Math.round(alpha) / 100;
        return 'rgba(' + clr[0] + ',' + clr[1] + ',' + clr[2] + ',' + alpha + ')'
    }
    catch (ex) {
        console.log('appxXlateColor: ' + ex);
	console.log(ex.stack);
    }
}

function base64ToArrayBuffer(base64) {
    var binary_string = window.atob(base64);
    var len = binary_string.length;
    var bytes = new Uint8Array(len);
    for (var i = 0; i < len; i++) {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes.buffer;
}

function Str2byteArray( str ) {
    var arObj = str4ab( str );
    var ar = [];
    for( var i=0; i<arObj.length; i++ )
	ar.push(arObj[i]);
    return ar;
}

function StrGetSubstr( str, prefix, suffix ) {
    var idx1 = str.indexOf(prefix);
    var str1 = str.substr(idx1+prefix.length);
    var idx2 = str1.indexOf(suffix);
    var str2 = str1.substr(0,idx2);
    return str2;
}

/*
**Function to check browser versions. Checks are made in specific order to allow
**for browsers reporting multiple browser support. IE does not specify any browser
**so the default if we get through every check is to return IE.
**
**@return: String of browser version.
*/
function checkBrowser(){
    var ua = navigator.userAgent;
    
    if (ua.indexOf("Edge") != -1){
	return "Edge";
    }

    if (ua.indexOf("OPR") != -1){
	return "Opera";
    }
    
    if (ua.indexOf("Chrome") != -1){
	return "Chrome";
    }

    if (ua.indexOf("Safari") != -1){
	return "Safari";
    }

    if (ua.indexOf("Firefox") != -1){
	return "Firefox";
    }

    return "IE";
}

/*
**Function to take a number and round it away from zero. ex -1.8 == -2 && 1.8 = 2
**
**@param x: number to round
**
**@return a: rounded number
*/
function roundAwayFromZero(x) {
    var a;
    if (x < 0){
	a = Math.floor(x);
    }else{
	a = Math.ceil(x);
    }
    return a;
}

/*
**Function to take a number and round it towards zero. ex -1.8 == -1 && 1.8 = 1
**
**@param x: number to round
**
**@return a: rounded number
*/
function roundTowardsZero(x) {
    var a;
    if (x < 0){
	a = Math.ceil(x);
    }else{
	a = Math.floor(x);
    }
    return a;
}
