/* Copyright Stuff */

/**
 * @requires
 */

/**
 * Namespace: OGIS
 * The OGIS object provides a namespace for all things OGIS.
 */
var OGIS = {};

/**
 * Constructor: OGIS.Class
 * Base class used to construct all other classes. Includes support for multiple inheritance.
 * To create a new OGIS-style class, use the following syntax:
 * > var MyClass = OGIS.Class(prototype);
 * To create a new OGIS-style class with multiple inheritance, use the following syntax:
 * > var MyClass = OGIS.Class(Class1, Class2, prototype);
 */
OGIS.Class = function() {
    var Class = function() {
        this.initialize.apply(this, arguments);
    };
    var extended = {};
    var parent;
    for(var i=0; i<arguments.length; ++i) {
        if(typeof arguments[i] == "function") {
            // get the prototype of the superclass
            parent = arguments[i].prototype;
        } else {
            // in this case we're extending with the prototype
            parent = arguments[i];
        }
        OGIS.Util.extend(extended, parent);
    }
    Class.prototype = extended;
    return Class;
};

/**
 * Namespace: Utils
 */
OGIS.Util = {};
/**
 * APIFunction: extend
 * Copy all properties of a source object to a destination object.  Modifies
 *     the passed in destination object.  Any properties on the source object
 *     that are set to undefined will not be (re)set on the destination object.
 *
 * Parameters:
 * destination - {Object} The object that will be modified
 * source - {Object} The object with properties to be set on the destination
 *
 * Returns:
 * {Object} The destination object.
 */
OGIS.Util.extend = function(destination, source) {
	if(destination && source) {
		for(var property in source) {
			value = source[property];
			if(value !== undefined) {
				destination[property] = value;
			}
		}
		/**
		 * IE doesn't include the toString property when iterating over an object's
		 * properties with the for(property in object) syntax.  Explicitly check if
		 * the source has its own toString property.
		 */
		if(source.hasOwnProperty && source.hasOwnProperty('toString')) {
			destination.toString = source.toString;
		}
	}
	return destination;
};

/**
 * APIFunction: getWindowDimensions
 * For getting the window height and width independant of platform
 *
 * Parameters:
 *
 * Returns:
 * {Object} Object that contains w for window width and h for window height.
 */
OGIS.Util.getWindowDimensions = function(){
	var dim = {};
	dim.w = (window.innerWidth || document.body.clientWidth);
	dim.h = (window.innerHeight || document.body.clientHeight);
	return dim;
};

/**
 * APIFunction: GUIDGen
 * Generates a unique GUID
 * Parameters:
 *
 * Returns:
 * {String} String of the unique GUID
 */
OGIS.Util.GUIDGen = function(){
	var i, j, result = '';
	for(j=0; j<32; j++){
		if( j == 8 || j == 12|| j == 16|| j == 20){
			result = result + '-';
		}
		i = Math.floor(Math.random()*16).toString(16).toUpperCase();
		result = result + i;
	}
	return result;
};

/**
 * APIFunction: msgAlert
 * Pops up the alert message
 * Parameters:
 *
 * Returns:
 * {Message} Message box with the alert message
 */
OGIS.msgAlert = function(header,message){
    if (Ext != null){
       if (header == "") header = "Message";
       // Show a dialog using config options:
       Ext.Msg.show({
                title:header,
                msg: message,
                buttons: Ext.Msg.OK,
                icon: Ext.MessageBox.WARNING
       });
    }
    else {
         alert(message);
    }
}

/**
 * APIFunction: getElement
 * Parameters:
 * Doesn't take parameters per se, but can take arguments.
 * Arguments can consist of the one element's ID {String} you are looking for, or a list of element IDs.
 * Returns:
 * {DOMElement or Array} A DOM element with the ID sent, or an array of DOM elements
 */
OGIS.Util.getElement = function() {
	var elements = [];

	for (var i = 0; i < arguments.length; i++) {
		var element = arguments[i];
		if (typeof element == 'string') {
			element = document.getElementById(element);
		}
		if (arguments.length == 1) {
			return element;
		}
		elements.push(element);
	}
	return elements;
};

// maintains prototype functionality by passing through to OGIS.Util.getElement
if ($ == null) {
    var $ = OGIS.Util.getElement;
}

/*********************
 * Namespace: String
 *********************/
OGIS.String = {};
/**
 * APIFunction: supplant
 * Supplant() does variable substitution on the string.
 * It scans through the string looking for expressions enclosed in { } braces.
 * If an expression is found, use it as a key on the object, and if the key has a string value
 * or number value, it is substituted for the bracket expression and it repeats.
 * This is useful for automatically fixing URLs.
 * So,
 * 		var txt = {domain: 'valvion.com', media: 'http://media.valvion.com/'};
 *		var o = "{media}logo.gif";
 *		OGIS.String.supplant(txt, o);
 * produces a url containing "http://media.valvion.com/logo.gif".
 *
 * Parameters:
 * txt - {String} The string that will be modified
 * dict - {Object} The JSON object with properties to be set on the destination
 *
 * Returns:
 * {String} The modified input string.
 */
OGIS.String.supplant = function(txt, dict) {
	return txt.replace(/{([^{}]*)}/g,
		function (a, b) {
			var r = dict[b];
			return typeof r === 'string' || typeof r === 'number' || typeof r === 'boolean' ? r : a;
		}
	);
};

/* APIFunction: entityify
 * Entityify() produces a string in which '<', '>', and '&' are replaced with their HTML entity equivalents.
 * This is essential for placing arbitrary strings into HTML texts.
 * So,
 * 		var txt = "if (a < b && b > c) {";
 *		OGIS.String.entityify(txt);
 * produces "if (a &lt; b &amp;&amp; b &gt; c) {".
 *
 * Parameters:
 * txt - {String} The string to process.
 *
 * Returns:
 * {String} The modified input string.
 */
OGIS.String.entityify = function(txt) {
	return txt.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
};

/* APIFunction: quote
 * Quote() produces a quoted string.
 * This method returns a string that is like the original string except that it is wrapped in quotes
 * and all quote and backslash characters are preceded with backslash.
 * So,
 * 		var txt = 'I said "foo" instead of "bar"';
 *		OGIS.String.quote(txt);
 * produces "I said \"foo\" instead of \"bar\"".
 *
 * Parameters:
 * txt - {String} The string to process.
 *
 * Returns:
 * {String} The modified input string.
 */
OGIS.String.quote = function (txt) {
	var c, i, l = txt.length, o = '"';
	for (i = 0; i < l; i += 1) {
		c = txt.charAt(i);
		if (c >= ' ') {
			if (c === '\\' || c === '"') {
				o += '\\';
			}
			o += c;
		} else {
			switch (c) {
			case '\b':
				o += '\\b';
				break;
			case '\f':
				o += '\\f';
				break;
			case '\n':
				o += '\\n';
				break;
			case '\r':
				o += '\\r';
				break;
			case '\t':
				o += '\\t';
				break;
			default:
				c = c.charCodeAt();
				o += '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
			}
		}
	}
	return o + '"';
};

/********************
* Namespace: Number
********************/
OGIS.Number = {};
    /**
     * APIProperty: decimalSeparator
     * Decimal separator to use when formatting numbers.
     */
	OGIS.Number.decimalSeparator = ".";

    /**
     * APIProperty: thousandsSeparator
     * Thousands separator to use when formatting numbers.
     */
	OGIS.Number.thousandsSeparator = ",";

    /**
     * APIFunction: limitSigDigs
     * Limit the number of significant digits on a float.
     *
     * Parameters:
     * num - {Float}
     * sig - {Integer}
     *
     * Returns:
     * {Float} The number, rounded to the specified number of significant
     *     digits.
     */
	OGIS.Number.limitSigDigs = function(num, sig) {
        var fig = 0;
        if (sig > 0) {
            fig = parseFloat(num.toPrecision(sig));
        }
        return fig;
    };

    /**
     * APIFunction: format
     * Formats a number for output.
     *
     * Parameters:
     * num  - {Float}
     * dec  - {Integer} Number of decimal places to round to.
     *        Defaults to 0. Set to null to leave decimal places unchanged.
     * tsep - {String} Thousands separator.
     *        Default is ",".
     * dsep - {String} Decimal separator.
     *        Default is ".".
     *
     * Returns:
     * {String} A string representing the formatted number.
     */
	OGIS.Number.format = function(num, dec, tsep, dsep) {
        dec = (typeof dec != "undefined") ? dec : 0;
        tsep = (typeof tsep != "undefined") ? tsep :
            OGIS.Number.thousandsSeparator;
        dsep = (typeof dsep != "undefined") ? dsep :
            OGIS.Number.decimalSeparator;

        if (dec != null) {
            num = parseFloat(num.toFixed(dec));
        }

        var parts = num.toString().split(".");
        if (parts.length == 1 && dec == null) {
            // integer where we do not want to touch the decimals
            dec = 0;
        }

        var integer = parts[0];
        if (tsep) {
            var thousands = /(-?[0-9]+)([0-9]{3})/;
            while(thousands.test(integer)) {
                integer = integer.replace(thousands, "$1" + tsep + "$2");
            }
        }

        var str;
        if (dec == 0) {
            str = integer;
        } else {
            var rem = parts.length > 1 ? parts[1] : "0";
            if (dec != null) {
                rem = rem + new Array(dec - rem.length + 1).join("0");
            }
            str = integer + dsep + rem;
        }
        return str;
	};
/*********************
* Namespace: Function
*********************/
OGIS.Function = {};
/**
 * APIFunction: bind
 * Bind a function to an object.  Method to easily create closures with 'this' altered.
 *
 * Parameters:
 * func - {Function} Input function.
 * object - {Object} The object to bind to the input function (as this).
 *
 * Returns:
 * {Function} A closure with 'this' set to the passed in object.
 */
OGIS.Function.bind = function(func, object) {
	// create a reference to all arguments past the second one
	var args = Array.prototype.slice.apply(arguments, [2]);
	return function() {
		// Push on any additional arguments from the actual function call.
		// These will come after those sent to the bind call.
		var newArgs = args.concat(
			Array.prototype.slice.apply(arguments, [0])
		);
		return func.apply(object, newArgs);
	};
};

/**
 * APIFunction: bindAsEventListener
 * Bind a function to an object, and configure it to receive the event
 *     object as first parameter when called.
 *
 * Parameters:
 * func - {Function} Input function to serve as an event listener.
 * object - {Object} A reference to this.
 *
 * Returns:
 * {Function}
 */
OGIS.Function.bindAsEventListener = function(func, object) {
	return function(event) {
		return func.call(object, event || window.event);
	};
};


/********************
* Namespace: Array
********************/
OGIS.Array = {};

/********************
* Namespace: Misc
********************/
OGIS.Misc = {};
/**
 * APIFunction: typeOf
 * A more comprehensive test for teh standard typeof.
 * This function catches .... (TODO)
 *
 * Parameters:
 * value - {Object} Input object to test.
 *
 * Returns:
 * {String} String description of type of object.
 */
OGIS.Misc.typeOf = function (value) {
	var s = typeof value;
	if (s === 'object') {
		if (value) {
			if (typeof value.length === 'number' &&
					!(value.propertyIsEnumerable('length')) &&
					typeof value.splice === 'function') {
				s = 'array';
			}
		} else {
			s = 'null';
		}
	}
	return s;
};

/**
 * APIFunction: isEmpty
 * isEmpty(v) returns true if v is an object containing no enumerable members.
 * This function catches .... (TODO)
 *
 * Parameters:
 * value - {Object} Input object to test.
 *
 * Returns:
 * {Boolean}
 */
OGIS.Misc.isEmpty = function (o) {
	var i, v;
	if (OGIS.Misc.typeOf(o) === 'object') {
		for (i in o) {
			v = o[i];
			if (v !== undefined && OGIS.Misc.typeOf(v) !== 'function') {
				return false;
			}
		}
	}
	return true;
};

/********************
* Namespace: XSS.XSSRepository
********************/
OGIS.XSSRepository = {};
/********************
* Namespace: XSS.XSSRepository.Responses
* Holds the responses from an XSS request until the calling function comes to get it and then removes it.
********************/
OGIS.XSSRepository.Responses = {};
/**
 * APIFunction: ProcessJSON
 * This function is *always* called once the JSON object is received from the server.
 * It then takes the response and adds it to the OGIS.XSS.Responses array at location GUID.
 *
 * Parameters:
 * respJSON - {Object} JSON object as answer from XSS request
 */
OGIS.XSSRepository.ProcessJSON = function(respJSON){
	this.Responses[respJSON.GUID] = respJSON;
};

/**
 * Class: OGIS.XSSBroker
 */
OGIS.XSSBroker = OGIS.Class({
    /**
    * Property: XSSObjects
    * {Object} Private property that is a collection of the JSON XSS transport objects indexed by GUID.
    */
	XSSObjects : {},
	/**
    * Property: timerIDs
    * {Object} Private property that holds the id of the timers that have been called indexed by GUID.
    */
	timerIDs : {},
	/**
    * Property: callbacks
    * {Object} Private property that holds the callback functions in the callingClasses to call on completion indexed by GUID.
    */
	callbacks: {},
	/**
    * Property: callingClasses
    * {Object} Private property that holds the handles back to the caller indexed by GUID.
    */
	callingClasses: {},
	/**
    * APIProperty: checkInterval
    * {Integer} Public property of the time between checks for a response from the server.
    */
	checkInterval: 250,
	/**
     * Constructor: OGIS.XSSBroker
     * Handles Asyncronous communication via the XSS script hack methodology
     */
    initialize: function(options) {
		OGIS.Util.extend(this, options);
	},
	/**
    * Method: responseCheck
    * Checks to see if the response is back yet. If so, it will pass the response on. If not, it will call itself again.
    *
    * Parameters:
    * JSONGUID - {String} GUID
	*/
	responseCheck : function(JSONGUID) {
		this.clearTimer(JSONGUID);
		var respJSON = OGIS.XSSRepository.Responses[JSONGUID];

		if (!respJSON){
			this.timerIDs[JSONGUID] = window.setTimeout(OGIS.Function.bind(this.responseCheck, this, JSONGUID), this.checkInterval);
		} else {
			this.processCallback(JSONGUID, [respJSON]);
		}
	},
	/**
    * Method: processCallback
    * Does the work of calling the callback function and cleaning up aeverything that was created in the process.
    *
    * Parameters:
    * JSONGUID - {String} GUID
	* args - {Array} The response string from the server
	*/
	processCallback: function (JSONGUID, args) {
        if (JSONGUID && this.callbacks[JSONGUID]) {
            this.callbacks[JSONGUID].apply(this.callingClasses[JSONGUID], args);
			//clean up all the request objects
			this.callbacks[JSONGUID] = null;
			this.callingClasses[JSONGUID] = null;
			this.XSSObjects[JSONGUID].removeScriptTag();
			delete this.callbacks[JSONGUID];
			delete this.callingClasses[JSONGUID];
			delete OGIS.XSSRepository.Responses[JSONGUID];
        }
    },

	/**
    * Method: clearTimer
    * Clears the timerID.
    *
    * Parameters:
    * JSONGUID - {String} GUID
	*/
	clearTimer : function(JSONGUID) {
		if (this.timerIDs[JSONGUID] != null) {
			window.clearTimeout(this.timerIDs[JSONGUID]);
			this.timerIDs[JSONGUID] = null;
		}
	},

	/**
    * APIFunction: sendRequest
    * Does the sending of a request by creating the various objects necessary.
    *
    * Parameters:
    * callingClass - {Object} Normally Should be *this* from caller unless a different object contains the callback function
	* callbackFn - {Function} Function to call with response object. TODO: should be and array of callbacks?
	* inURL - {String} URL to communicate with
	*/
	sendRequest: function(callingClass, callbackFn, inURL){
		//TODO: have function that knows if user didn't wait for response before clicking again and clean up!
		var JSONGUID = OGIS.Util.GUIDGen();
		this.callingClasses[JSONGUID] = callingClass;
		this.callbacks[JSONGUID] = callbackFn;

		var outURL = inURL + "&output=json&callback=OGIS.XSSRepository.ProcessJSON&GUID=" + JSONGUID;

		this.XSSObjects[JSONGUID] = new OGIS.JSONscriptRequest(outURL, JSONGUID);
		this.XSSObjects[JSONGUID].buildScriptTag();
		this.XSSObjects[JSONGUID].addScriptTag();

		this.timerIDs[JSONGUID] = window.setTimeout(OGIS.Function.bind(this.responseCheck, this, JSONGUID), this.checkInterval); //if need to pass args -- , 1
	},

    CLASS_NAME: "OGIS.XSS"
});

/**
 * Class: OGIS.JSONscriptRequest
 */
OGIS.JSONscriptRequest = OGIS.Class({
	/**
    * Property: fullUrl
    * {String} URL to send request to.
    */
	fullUrl: null,
	/**
    * Property: scriptCounter
    * {Integer} Static script ID counter
    */
	scriptCounter : 1,
	/**
    * Property: noCacheIE
    * {String} Keep IE from caching requests
    */
    noCacheIE : null,
	/**
    * Property: headLoc
    * {DOMElement} Get the DOM location to put the script tag
    */
    headLoc : null,
	/**
    * Property: scriptId
    * {String} Generate a unique script tag id
    */
    scriptId : null,
	/**
     * Constructor: OGIS.JSONscriptRequest
     * Creates and destroys the script element in the header of a page. This is the backbone of the XSS hack.
	 * Must provide URL and JSONGUID (from OGIS.Util.GUIDGen).
	 * Parameters:
     * fullUrl - {String} URL to send to.
	 * JSONGUID - {String} GUID to keep script elements unique.
     */
	initialize: function(fullUrl, JSONGUID) { //must provide URL and JSONGUID from OGIS.Util.GUIDGen
		this.fullUrl = fullUrl;
		this.noCacheIE = '&noCacheIE=' + (new Date()).getTime();
		this.headLoc = document.getElementsByTagName("head").item(0);
		this.scriptId = 'XSSId-' + JSONGUID;
	},

	/**
    * Method: buildScriptTag
    * Build the script tag
	*/
	buildScriptTag : function () {
		this.scriptObj = document.createElement("script");
		// Add script object attributes
		this.scriptObj.setAttribute("type", "text/javascript");
		this.scriptObj.setAttribute("charset", "utf-8");
		this.scriptObj.setAttribute("src", this.fullUrl + this.noCacheIE);
		this.scriptObj.setAttribute("id", this.scriptId);
	},

	/**
    * Method: removeScriptTag
    * Remove the script tag from the page
	*/
	removeScriptTag : function () {
		this.headLoc.removeChild(this.scriptObj);
	},

	/**
    * Method: addScriptTag
    * Add the script tag to the page
	*/
	addScriptTag : function () {
		this.headLoc.appendChild(this.scriptObj);
	},

    CLASS_NAME: "OGIS.JSONscriptRequest"
});
