/*
 * Global JavaScript functions, stuff that should always be available.
 *
 * These functions should not depend on any 3rd party libraries.
 *
 * <b>WARNING:</b> Do not extend the Object object, otherwise Ext will fail. (ask urs)
 *
 *
 *
 *
 */

if (typeof(DEBUG) === 'undefined') { //someone might set it hardcoded, for example unit tests need to do that.
    var DEBUG = false;
    if (document.URL.indexOf('debug') > 0) DEBUG = true;
}

if (typeof(COMPRESSEDJS) === 'undefined') { //someone might set it hardcoded
    var COMPRESSEDJS = window.location.hostname !== 'localhost' && window.location.port != 8080  && !DEBUG;
    //COMPRESSEDJS = true;
}

if (!DEBUG) {
    //noinspection ReservedWordAsName
    var logConfig = {
        "default": 'error'
    };
}

// ************************* base language extensions *************************

/**
 * Tells if obj is an instance of cls.
 *
 * <b>Example:</b>
 * <pre><code>
 * var f = new package.Foo();
 * alert(isA(f, package.Foo));
 * </code></pre>
 * 
 * <p>
 * It is mostly the same as (obj instanceof cls), with the addition that
 * obj may implement its own instanceof comparator method named isA(cls).
 * This is required with multiple inheritance. There the instanceof
 * will only work for one class, thus this function must be used.
 *
 * @inline
 * @param obj
 * @param cls
 * @return {Boolean}
 */
function isA(obj, cls) {
    if (obj instanceof cls) return true;
    return (objectHasMethod(obj,'isA')) ? obj.isA(cls) : false;
}

/**
 * Tells if a passed variable is an object, and not null.
 *
 * <p>
 * <b>WARNING:</b> Also tells true for an array, since technically an array is
 * an object too. This behavior cannot be changed! if you want to distinguish
 * then ask for Ext.isArray()!
 *
 * <p>
 * <b>NOTE:</b> This is the same as Ext.isObject().
 * 
 * @inline
 * @param v
 * @return {Boolean}
 */
function isObject(v) {
    return v && (typeof v === "object");
}

/**
 * Copied from Ext, need it before it's loaded.
 * @param val
 */
function isArray(val) {
    return Object.prototype.toString.apply(val) === '[object Array]';
}


/**
 * Tells if a variable is empty.
 * This method is inspired by PHP.
 *
 * <pre>
 * Behaviour:
 *   undefined var, null value: true
 *   type number: zero=true, otherwise false
 *   type string: "0" or trimmed to "" = true, otherwise false
 *   type boolean: false=true, true=false
 *   type function: always false
 *   type array: if 0 elements then true
 *   type object: no elements = true, otherwise false.
 *                More explanation: Anything that is not in the prototype. Having
 *                Something in a parent class is enough to be not empty. Even a
 *                function/method (that isn't in the prototype) makes it not empty.
 *                This is important for a handler object for example, where the only
 *                purpose is to have an expected function to handle some logic.
 *                examples:
 *                var obj = {};               => empty
 *                var obj = {a:"foo"};        => not empty
 *                var obj = {a:undefined};    => not empty
 *                var obj = {a:function(){}}; => not empty
 * </pre>
 *
 * @see Ext.isEmpty() with slightly different behavior
 */
function isEmpty(val) {
    if (val === null || val === undefined) return true;

    var type = typeof(val);

//    if (type === 'undefined') {
//        return true;
    if (type === 'boolean') {
        return !val;
    } else if (type === 'string') {
        var v = val.trim();
        return (v === "" || v === "0");
    } else if (type === 'number') {
        return (val === 0);
    } else if (type === 'function') {
        return false;
    } else if (type === 'object') {
        if (isArray(val)) {
            return (val.length == 0);
        }
        //noinspection LoopStatementThatDoesntLoopJS
        for (var key in val) {
            return false; //got something
        }
        return true;
    } else {
        throw new Error("Unsupported js data type: "+type);
    }
}

/**
 * Tells if a variable is not defined, undefined, or null.
 * @param {Object} scope Pass window to check for a var in global scope.
 * @param {String} varName
 * @return {Boolean}
 */
function isNothing(scope, varName) {
    if (typeof(scope[varName]) == "undefined") return true;
    return (scope[varName] == null);
}

/**
 * Tells if an object, or an object used as HashMap, contains a certain property.
 * The property may be null.
 * There is a difference between this and checking for "undefined". A field may be
 * in use, but "undefined".
 * If you want to check for a variable in "global" scope then pass window as object.
 * @param object
 * @param fieldName
 * @return {Boolean}
 */
function objectContainsField(object, fieldName) {
    if (typeof(object[fieldName]) != "undefined") return true; //quick return
    for (var fld in object) {
        if (fieldName == fld) {
            return true;
        }
    }
    return false;
}

/**
 * Returns an object's field, or fallback if the type of the attribute is undefined.
 * @param object May not be undefined nor null.
 * @param fieldName
 * @param fallback The value to return if the field is undefined.
 */
function getObjectProperty(object, fieldName, fallback) {
    if (typeof(object[fieldName]) != 'undefined') {
        return object[fieldName];
    }
    return fallback;
}

/**
 * Tells if an object contains a certain method.
 * @inline
 * @param funcName
 * @return {Boolean}
 */
function objectHasMethod( object, funcName ) {
    return (typeof(object[funcName]) == "function");
}


/**
 * Appends the content of the given array at the end of this one.
 * @param appendMe
 * @return this array which has been modified (not only the returned array is modified)
 * @throws Error if appendMe is not an array
 * @deprecated use the js standard Array.concat() which works identical.
 */
Array.prototype.appendAll=function(appendMe) {
    if (!Ext.isArray(appendMe)) {
        throw new Error("Given param is not an array!");
    }
    for (var i=0; i<appendMe.length; i++) {
        this[this.length] = appendMe[i];
    }
    return this;
};

/**
 * Returns true if the array contains the given element, otherwise false.
 * @param element
 * @return {Boolean}
 */
Array.prototype.contains = function(element) {
    for (var i = 0; i < this.length; i++) {
        if (this[i] == element) {
            return true;
        }
    }
    return false;
};

/**
 * Removes a range of elements from an array.
 *
 * <pre>
 * Remove the second item from the array
 *   array.remove(1);
 * Remove the second-to-last item from the array
 *   array.remove(-2);
 * Remove the second and third items from the array
 *   array.remove(1,2);
 * Remove the last and second-to-last items from the array
 *   array.remove(-2,-1);
 * </pre>
 *
 * <b>WARNING: </b> this modifies both the current array as well as the returned one!
 *
 * <p>
 * Array Remove - By John Resig (MIT Licensed)
 * http://ejohn.org/blog/javascript-array-remove/
 * 
 * @param from
 * @param to
 * @return The new array which will equal the one on which this method was performed.
 */
Array.prototype.removeRange = 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);
};
/**
 * @see removeRange
 * @param pos
 */
Array.prototype.removePos = function(pos) {
  return this.removeRange(pos);
};

/**
 * Limits the string to the length specified.
 * @param {int} limit How many characters to allow at most, including attach if attached.
 * @param {String} attach Optional, defaults to "...", an example is the empty string "".
 * @return The new possibly shortened string.
 */
if (String.prototype.limit) {
    throw new Error("Someone overrode our custom String.prototype.limit! Included any new library lately?");
}
String.prototype.limit = function(limit, attach) {
    if (this.length <= limit) return this;
    if (typeof attach == "undefined") attach = "...";
    return this.substr(0, limit-attach.length) + attach;
};

/**
 * Copied from ext, need it here, ext only comes later.
 */
if (!String.prototype.trim) {
    String.prototype.trim = function(){
        var re = /^\s+|\s+$/g;
        return function(){ return this.replace(re, ""); };
    }();
}

//source: http://www.schuerig.de/michael/javascript/stdext.js
if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(prefix) {
    return (this.indexOf(prefix) === 0);
  };
}
//source: http://www.schuerig.de/michael/javascript/stdext.js
if (!String.prototype.endsWith) {
  String.prototype.endsWith = function(suffix) {
    var startPos = this.length - suffix.length;
    if (startPos < 0) {
      return false;
    }
    return (this.lastIndexOf(suffix, startPos) == startPos);
  };
}

if (!String.prototype.contains) {
  String.prototype.contains = function(findMe) {
    return (this.indexOf(findMe) != -1);
  };
}



/**
 *
 * source: http://javascript.about.com/library/blage.htm
 *
 * example:
 * var today = new Date();
 * var bday  = new Date(1920, 0, 2);
 * alert( today.ageLastBirthday(bday) );
 */
Date.prototype.ageLastBirthday = function(dob) {
    var cy = this.getFullYear();
    var by = dob.getFullYear();
    var db = new Date(dob);
    db.setFullYear(cy);
    var adj = (this-db<0) ? 1 : 0;
    return cy - by - adj;
};






// ************************* dynamic content loading functions *************************

/**
 * Load a CSS file asynchronous or synchronous depending on browser.
 * @param href location of the file (relative or absolute)
 * @param level in which we load a file ('debug', 'develop', 'release' or 'always'(default))
 * @author Urs Wolfer
 */
function loadCss(href, level) {
    level = level || 'always';
    if (level.toLowerCase() == 'debug') {
        if (!DEBUG) return;
    }
    if (level.toLowerCase() == 'develop') {
        if (COMPRESSEDJS) return;
    }
    if (level.toLowerCase() == 'release') {
        if (!COMPRESSEDJS || DEBUG) return;
    }
    for (var i = 0; i < document.getElementsByTagName("link").length; i++) { // check if it already has been added before
        var headerScriptElement = document.getElementsByTagName("link")[i];
        if (headerScriptElement.href.substring(headerScriptElement.href.length - href.length) == href) {
            return;
        }
    }

    if (navigator.userAgent.indexOf("Gecko") != -1 &&
            navigator.userAgent.indexOf("Konqueror") == -1
         && navigator.userAgent.indexOf("Safari") == -1) { // asynchronous loading for mozilla
        var cssElm = document.createElement("link");
        cssElm.rel = "stylesheet";
        cssElm.type = "text/css";
        var hdElm = document.getElementsByTagName("head")[0];
        hdElm.appendChild(cssElm);
        cssElm.href = href;
    } else {
        document.writeln("<link rel=\"stylesheet\" type=\"text\/css\" href=\"" + href + "\" />");
    }
}

/**
 * Load a JavaScript file asynchronous or synchronous depending on browser.
 * @param src location of the file (relative or absolute)
 * @param level in which we load a file ('debug', 'develop', 'release' or 'always'(default))
 * @author Urs Wolfer
 */
function loadJs(src, level) {
    level = level || 'always';
    if (level.toLowerCase() == 'debug') {
        if (!DEBUG) return;
    }
    if (level.toLowerCase() == 'develop') {
        if (COMPRESSEDJS) return;
    }
    if (level.toLowerCase() == 'release') {
        if (!COMPRESSEDJS || DEBUG) return;
    }
    for (var i = 0; i < document.getElementsByTagName("script").length; i++) { // check if it already has been added before
        var headerScriptElement = document.getElementsByTagName("script")[i];
        if (headerScriptElement.src.substring(headerScriptElement.src.length - src.length) == src) {
            return;
        }
    }

    if (loadJsAsync()) {
        loadJsDom(src);
    } else {
        document.writeln("<script type=\"text\/javascript\" src=\"" + src + "\"><\/script>");
    }
}

/**
 * asynchronous loading for mozilla (or something, FIXME urs cleanup)
 */
function loadJsAsync() {
    if (loadJsAsync.prototype.loadAsync === undefined) {
        var ua = navigator.userAgent;
        loadJsAsync.prototype.loadAsync = ua.indexOf("Gecko") != -1
                && ua.indexOf("Konqueror") == -1
                && ua.indexOf("Safari") == -1;
        // disable for firefox 4 and younger
        if (ua.contains("Firefox")) {
            var version = getFirefoxMajorVersion(ua);
            if (version === null || version >= 4) {
                loadJsAsync.prototype.loadAsync = false;
            } else if (version < 4) {
                loadJsAsync.prototype.loadAsync = true;
            }
        }
    }
    return loadJsAsync.prototype.loadAsync;
}
loadJsAsync.prototype.loadAsync = undefined; //cache

function getFirefoxMajorVersion(ua) {
    var results = ua.match(/.*?Firefox\/(\d*)?\..*/);
    if (results && results.length == 2) {
        if (/^\d*$/.test(results[1])) {
            return parseInt(results[1]);
        }
    }
    return null;
}

/**
 * Load a JavaScript file asynchronous.
 * @param src location of the file (relative or absolute)
 * @author Urs Wolfer
 */
function loadJsDom(src) {
    var scriptElm = document.createElement("script");
    var hdElm = document.getElementsByTagName("head").item(0);
    scriptElm.setAttribute('type', 'text/javascript');
    scriptElm.setAttribute('src', src);
    hdElm.appendChild(scriptElm);
}






// ************************* logging / debugging *************************


/**
 * Logs anything to to 'best' available logging tool in a usable form.
 * NOTE: This function gets overridden in nice2.util.Logger (if used) and the API changes!
 * @param anything Anything you want to log.
 * @param forceAlert Set true if you want to force a window.alert if no other debug tool is available.
 * @author Urs Wolfer
 */
function log(anything, forceAlert) {
    if (typeof console != 'undefined') {
        console.log(anything);
    } else if (typeof Ext != 'undefined' && typeof Ext.log == 'function') {
        Ext.log(dump(anything));
    } else {
        if (forceAlert)
            alert(dump(anything));
    }
}


/**
 * Brings a variable into human readable form, ready for printing somewhere, as in PHP.
 *
 * <pre>
 * Example usage:
 *   var obj = {foo:'bar', greeting':{hello:'world'}}
 *   alert(dump(obj));
 * </pre>
 * 
 * <p>
 * <b>Warning:</b> This method does not detect cyclic loops.
 *
 * @author public domain, andrej, urs
 * @param {Mixed} arr (the data - array,hash(associative array),object)
 * @param {Boolean} includeFunctions (optional, if object methods shall be printed too, default is false)
 * @param {int} _level (used internally only)
 * @return String (The textual representation of the variable)
 */
function dump(arr, includeFunctions, _level) {
    var collector = [];
    _dump_recursive(collector, arr, includeFunctions, _level);
    return collector.join('');
}

function _dump_recursive(col, arr, includeFunctions, _level) {
    if (_level > 20) return; // prevent infinite recursion -uwolfer

    if (!_level) _level = 0;
    if (typeof(includeFunctions)=='undefined') includeFunctions = false;

    //The padding given at the beginning of the line.
    var level_padding = '';
    for(var j=0;j<_level;j++) level_padding += '    ';

    if (typeof(arr) == 'object') { //Array/Hashes/Objects
        if (!_dump_handleSpecial(arr, col, level_padding, null)) {
            for(var item in arr) {
                var value = arr[item];

                if (typeof(value) == 'object') { //If it is an array,
                    if (!_dump_handleSpecial(value, col, level_padding, item)) {
                        var objType = Ext.isArray(value) ? 'array' : 'object';
                        col[col.length] = level_padding + "'" + item + "' ("+objType+") ...\n";
                        _dump_recursive(col, value, includeFunctions, _level+1);
                    }
                } else {
                    if (!includeFunctions && (typeof(value) == 'function')) {
                        continue;
                    }
                    col[col.length] = level_padding + "'" + item + "' => \"" + value + "\" (" + typeof(value) + ")\n";
                }
            }
        }
    } else { //Stings/Chars/Numbers etc.
        col[col.length] = level_padding + "===>"+arr+"<===("+typeof(arr)+")";
    }
}

function _dump_handleSpecial(val, col, level_padding, item) {
    var txt;
    if (val === null) {
        txt = "(null)\n";
    } else if (val === undefined) {
        txt = "(undefined)\n";
    } else if ((typeof(val['_ordinal']) != 'undefined') && (typeof(val['_name']) != 'undefined')) {
        txt = val.toString()+" (enum)\n";
    } else {
        return false;
    }
    if (item) col[col.length] = level_padding + "'"+item + "' => ";
    col[col.length] = txt;
    return true;
}

/**
 * Warning: helper/debugging method only, delivers unreliable results!
 * @param func
 * @return the function name, or empty string (not in a func)
 */
function getFunctionName(func) {
  if ( typeof func == "function" || typeof func == "object" )
  var fName = (""+func).match(
    /function\s*([\w\$]*)\s*\(/
  ); if ( fName !== null ) return fName[1];
    return "";
}

function getUrlParameter(name)
{
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regexS = "[\\?&]" + name + "=([^&#]*)";
    var regex = new RegExp(regexS);
    var results = regex.exec(window.location.href);
    if (results == null)
        return "";
    else
        return results[1];
}


loadJs('/js/_logConfig.js', 'debug');

