

/*

    String prototype functions 

*/

String.prototype.count = function(match) {
    var res = this.match(new RegExp(match),"g");
    if (res == null) 
        return 0;
    else
        return res.length;
}

String.prototype.startsWith = function(match) {
    return this.indexOf(match) == 0;
}


/*
    Cross-Browser Split 0.3
    By Steven Levithan <http://stevenlevithan.com>
    MIT license
    Provides a consistent cross-browser, ECMA-262 v3 compliant split method
*/

String.prototype._$$split = String.prototype._$$split || String.prototype.split;

String.prototype.split = function (s /* separator */, limit) {
    // if separator is not a regex, use the native split method
    if (!(s instanceof RegExp))
        return String.prototype._$$split.apply(this, arguments);

    var flags = (s.global ? "g" : "") + (s.ignoreCase ? "i" : "") + (s.multiline ? "m" : ""),
        s2 = new RegExp("^" + s.source + "$", flags),
        output = [],
        origLastIndex = s.lastIndex,
        lastLastIndex = 0,
        i = 0, match, lastLength;

    /* behavior for limit: if it's...
    - undefined: no limit
    - NaN or zero: return an empty array
    - a positive number: use limit after dropping any decimal
    - a negative number: no limit
    - other: type-convert, then use the above rules
    */
    if (limit === undefined || +limit < 0) {
        limit = false;
    } else {
        limit = Math.floor(+limit);
        if (!limit)
            return [];
    }

    if (s.global)
        s.lastIndex = 0;
    else
        s = new RegExp(s.source, "g" + flags);

    while ((!limit || i++ <= limit) && (match = s.exec(this))) {
        var emptyMatch = !match[0].length;

        // Fix IE's infinite-loop-resistant but incorrect lastIndex
        if (emptyMatch && s.lastIndex > match.index)
            s.lastIndex--;

        if (s.lastIndex > lastLastIndex) {
            // Fix browsers whose exec methods don't consistently return undefined for non-participating capturing groups
            if (match.length > 1) {
                match[0].replace(s2, function () {
                    for (var j = 1; j < arguments.length - 2; j++) {
                        if (arguments[j] === undefined)
                            match[j] = undefined;
                    }
                });
            }

            output = output.concat(this.slice(lastLastIndex, match.index));
            if (1 < match.length && match.index < this.length)
                output = output.concat(match.slice(1));
            lastLength = match[0].length; // only needed if s.lastIndex === this.length
            lastLastIndex = s.lastIndex;
        }

        if (emptyMatch)
            s.lastIndex++; // avoid an infinite loop
    }

    // since this uses test(), output must be generated before restoring lastIndex
    output = lastLastIndex === this.length ?
        (s.test("") && !lastLength ? output : output.concat("")) :
        (limit ? output : output.concat(this.slice(lastLastIndex)));
    s.lastIndex = origLastIndex; // only needed if s.global, else we're working with a copy of the regex
    return output;
};





/*

    utility functions 

*/


/* adapted from jQuery */
function bindReady (callback) {
    // Mozilla, Opera and webkit nightlies currently support this event
    if ( document.addEventListener ) {
        // Use the handy event callback
        document.addEventListener( "DOMContentLoaded", function(){
            document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
            callback.call();
        }, false );

    // If IE event model is used
    } 
    else if ( document.attachEvent ) {
        // ensure firing before onload,
        // maybe late but safe also for iframes
        document.attachEvent("onreadystatechange", function(){
            if ( document.readyState === "complete" ) {
                document.detachEvent( "onreadystatechange", arguments.callee );
                callback.call();
            }
        });

        // If IE and not an iframe
        // continually check to see if the document is ready
        if ( document.documentElement.doScroll && window == window.top ) (function(){
            try {
                // If IE is used, use the trick by Diego Perini
                // http://javascript.nwbox.com/IEContentLoaded/
                document.documentElement.doScroll("left");
            } catch( error ) {
                setTimeout( arguments.callee, 0 );
                return;
            }

            callback.call();
        })();
    }
    else {
        window.onload = callback;
    }
}




function checkEmailAddress(addr) {
    var r = /^.+@.+\..+$/;
    if (addr.match(r))
        return true;
    else
        return false;
}

/* from Powers - Learning Javascript 3rd ed. */
function readCookie(key) {
    if (! navigator.cookieEnabled) return null;

    var cookie = document.cookie;
    var first = cookie.indexOf(key+"=");

    // cookie exists
    if (first >= 0) {
        var str = cookie.substring(first,cookie.length);
        var last = str.indexOf(";");

        // if last cookie
        if (last < 0) last = str.length;

        // get cookie value
        str = str.substring(0,last).split("=");
        return unescape(str[1]);
    } 
    else {
        return null;
    }
}

/* from Powers - Learning Javascript 3rd ed. */
function eraseCookie (key) {
    if (! navigator.cookieEnabled) return;
    
    var cookieDate = new Date(2000,11,10,19,30,30); /* in the past */
    document.cookie=key + "=; expires=" + cookieDate.toGMTString(  ) + "; path=/";
}

function setCookie(name, value, expireDays) {
    if (! navigator.cookieEnabled) return;

    var exdate = new Date();
    exdate.setDate(exdate.getDate() + expireDays);
    document.cookie= name +  "=" + escape(value) +
        ((expireDays == null) ? "" : ";expires="+exdate.toUTCString());
}


/* crossplatform e.innerText = str */
function setText (node, str) {
    if (node == null) return false;
    var fch = node.firstChild;

    if (fch == null) {
        var textNode = document.createTextNode(str);
        node.appendChild(textNode);
    }
    else if (fch.nodeType != 3 /* text node */) {
        var textNode = document.createTextNode(str);
        node.insertBefore(textNode, fch);
    }
    else {
        fch.nodeValue = str;
    }

    return true;
}

/* crossplatform return e.innerText
   IE parser completely ignores unix newlines - removes them from nodeValue
*/
function getText (node) {
    if (node == null) return false;
    var fch = node.firstChild;

    if (fch == null) {
        return "";
    }
    else if (fch.nodeType != 3 /* text node */) {
        return "";
    }
    else {
        return fch.nodeValue;
    }
}


function changeSelect (select, value) {
    for (var i = 0; i < select.length; i++) {
        if (select.options[i].value == value) {
            select.options[i].selected = true;
            return true;
        }
    }
    return false;
}

function getHashEntryFromList (list, name, value) {
    for (var i = 0; i < list.length; i++) {
        if (list[i][name] == value)
            return list[i];
    }
    return null;
}

function escapeRegExp (text) {
  if (!arguments.callee.sRE) {
    var specials = [
      '/', '.', '*', '+', '?', '|',
      '(', ')', '[', ']', '{', '}', '\\'
    ];
    arguments.callee.sRE = new RegExp(
      '(\\' + specials.join('|\\') + ')', 'g'
    );
  }
  return text.replace(arguments.callee.sRE, '\\$1');
}


function findAbsolutePos(obj) {
    var top = 0;
    var left = 0;
    do {
        top += obj.offsetTop;
        left += obj.offsetLeft; 
    } while ((obj = obj.offsetParent));

    return [top, left];
}

function debug (msg) {
    var li = document.createElement("li");
    li.innerHTML = msg + "<br>\n";
    document.body.appendChild(li);
}


function convertLineSeparators(str) {
    str = str.replace(/\r\n/g, "<br />");   // win
    str = str.replace(/\r/g, "<br />");     // mac
    str = str.replace(/\n/g, "<br />");     // unix

    return str;
}

function convertToUnixNewlines(str) {
    str = str.replace(/\r\n/g, "\n");       // win
    str = str.replace(/\r/g, "\n");         // mac

    return str;
}

function showWaitIndicator(iconUrl, posTop, posLeft) {
    var img = new Image();
    img.src = iconUrl;
    img.style.position = "absolute";
    img.style.top = posTop+"px";
    img.style.left = posLeft+"px";
    document.body.appendChild(img);
    return img;
}

function hideWaitIndicator(img) {
    if (img)
        document.body.removeChild(img);
}

function parseQuery (url) {
    var iq = url.indexOf("?");
    if (iq >= 0)
        url = url.substring(iq+1);

    var res = {};
    var parts = url.split("&");
    for (var i = 0; i < parts.length; i++) {
        var part = parts[i];
        var pair = part.split("=");
        res[unescape(pair[0])] = unescape(pair[1]);
    }

    return res;
}


function getWindowWidth() {
   if (window.innerWidth)
      return window.innerWidth;
   else if (document.documentElement && document.documentElement.clientWidth)
      return document.documentElement.clientWidth;
   else if (document.body && document.body.clientWidth)
      return document.body.clientWidth;
   else
      return null;
}


function textAreaInsertAtCaret (txtarea,text) { 
    if (txtarea.selectionStart || txtarea.selectionStart == '0') {
        var scrollPos = txtarea.scrollTop; 
        var strPos = txtarea.selectionStart; 
        var front = (txtarea.value).substring(0,strPos); 
        var back = (txtarea.value).substring(strPos,txtarea.value.length);
        txtarea.value = front + text + back; 
        strPos = strPos + text.length; 
        txtarea.selectionStart = strPos; 
        txtarea.selectionEnd = strPos; 
        txtarea.focus(); 
        txtarea.scrollTop = scrollPos; 
    }
    else {
        // old ie does not support this
        txtarea.value += text;
    }
}

function byteToHex (num) {
    var digits = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"];
    return digits[(num >> 4) & 0xf] + digits[num & 0xf];
}

function byteArrayToHexString (nn) {
    var out = "";
    for (var i = 0; i < nn.length; i++) {
        out += byteToHex(nn[i]);
    }
    return out;
}

function hexToByte (hex) {
    return parseInt("0x" + hex);
}

function hexStringToByteArray (hexstr) {
    var bytes = [];
    for (var i = 0; i < hexstr.length; i += 2) {
        bytes.push(hexToByte(hexstr.substring(i, i + 2)));
    }
    return bytes;
}

function stringToCodeSequence (str) {
    var seq = [];
    for (var i = 0; i < str.length; i++) {
        seq.push(str.charCodeAt(i));
    }
    return seq.join(":");
}

function codeSequenceToString (seq) {
    var codes = seq.split(":");
    var str = "";
    for (var i = 0; i < codes.length; i++) {
        str += String.fromCharCode(codes[i]);
    }
    return str;
}


/* this function breaks syntax highlighting in vim :) */

function escapeHTML (str) {
    str = str.replace(/&/g, "&amp;");
    str = str.replace(/"/g, "&quot;");
    str = str.replace(/>/g, "&gt;");
    str = str.replace(/</g, "&lt;");
    str = str.replace(/'/g, "&#39;");

    return str;
}

// this is not a full entity decoder
function unEscapeHTML (str) {
    str = str.replace(/&amp;/g, "&");
    str = str.replace(/&quot;/g, "\"");
    str = str.replace(/&gt;/g, ">");
    str = str.replace(/&lt;/g, "<");
    str = str.replace(/&#39;/g, "'");

    return str;
}


