/**
 * General Constants
 */
if(!window.Const) Const = {};
Object.extend(	Const,	
				{	UNSET_STRING: "-UNSET-STRING-"
				}
			 );

/**
 * ElementsGroup - a group of HTML elements distincted in the program by a provided 
 * name that may be different from thier Html-Element ID.
 * The elements are gathered on the this.elements collection, while the element IDs 
 * are in this.elementIDs.
 * 
 * It features:
 *  - managment of elements by logical names
 *  - show and hide elements 
 *  - onload events stack executed on window.onload
 */
ElementsGroup = Class.create();
/**
 * The constructor expects a dictionary, pointing every element's programatic name 
 * to its ID on the screen.
 * The constructor scheduels a task for the window's load event, and once the HTML 
 * document is loaded, it parses the provided dictionary, and obtains references to 
 * all specified elements.
 * Missing elements are reported in the log in WARN level.
 * 
 * @param {string}	p_screenElementsDictionary
 *  dictionary of program-names as keys, and HTML element-ids as values.
 *
 * @param {string(optional)}  sInstanceName
 *  The name of the ClientMgr instance (usually a singletone utilities class)
 *  used for the Log4Js.Logger and for on-the-fly creation of an onload function
 *  - Optional: when not provided, a random unique identifier is generated for internal use.
 */
ElementsGroup.prototype.initialize = function(p_elementsDictionary, sInstanceName)
{
	//default sInstanceName - for "anonymous"
	if(!sInstanceName) sInstanceName = "ElementsGroup" + ((new Date()).getTime() + "_" + Math.random()).replace(".","_");
	
	ElementsGroup.all[ElementsGroup.all.length] = this;
	if(parseInt(sInstanceName) != Number(sInstanceName))
	{
		 ElementsGroup.all[sInstanceName] = this;
	}

	this.log = new Log4Js.Logger(sInstanceName);
	this.instanceName = sInstanceName;
	this.elements = {};
	this.elementIDs = {};
	
	//prepare to and attach its onload
	this.prv_constructorTmpDictionary = p_elementsDictionary;
	this.onLoadHandlers = [ function() 
							{
								var oConstrDictionary = this.prv_constructorTmpDictionary;
								delete this.prv_constructorTmpDictionary;

								var anythingInDictionary;
								for(anythingInDictionary in oConstrDictionary) break;		
								if(anythingInDictionary)
								{	
									this.setElementIDs(oConstrDictionary);	
								}
							}
						  ];
}
/**
 * @private
 * A dictionary of all element-groups, named by thier provided instance name
 */
ElementsGroup.all = [];
/**
 * adds new or overrides an existing element to the 
 * this.elements and this.elementIDs dictionaries
 *
 * @param {string} sElementName
 *  Name of the element in the program
 *
 * @param {string} sElementID
 *  projectal HTML element ID of the element
 */	
ElementsGroup.prototype.setElementID = function(sElementName, sElementID)
{
	this.elementIDs[sElementName] = sElementID;
	this.elements[sElementName] = $(sElementID);
} 
/**
 * adds or overrides managed elements names and thier HTML ids in the elementIDs dictionary
 *
 * @param {object} p_screenElementsDictionary
 *  a dictionary of the managed element names as keys, and thier HTML element-ids as values.
 */
ElementsGroup.prototype.setElementIDs = function(p_elementsDictionary)
{
	//if the preload has not occuded yet - add it to the constructor dictionary
	if(this.prv_constructorTmpDictionary)
	{
		Object.extend(this.prv_constructorTmpDictionary, p_elementsDictionary);
		return;
	}
	var element;
	for(element in p_elementsDictionary)
	{
		this.setElementID(element, p_elementsDictionary[element]);
	}
}
/**
 * @private
 *
 * @param {string} sElementName
 *	the name of the element in the program
 *
 * @param {string} sCallerInstance
 * the caller function (for logging}
 * - optional : Default value: "[ClientMgr].getMyElement(...)"
 */
ElementsGroup.prototype.getMyElement = function(sElementName, sCallerInstance)
{	//default sCallerInstance
	if(!sCallerInstance) sCallerInstance = "[ElementsGroup].getMyElement(...)";
	
	//first - check the this.elements collection
	if(this.elements[sElementName]){
		return this.elements[sElementName];
	}
	this.log.info(sCallerInstance + " doesn't have " + sCallerInstance + ".elements." + sElementName);
	
	//get managed element ID
	var sElementID = this.elementIDs[sElementName];
	if(sElementID == null){ 		
		this.log.warn(sCallerInstance + " was asked to show an unknown element: " + sElementName);
		return;
	}
	//get element
	var oElement = $(sElementID);
	if(oElement == null){
		this.log.warn(sCallerInstance + " was asked to show an element that does not exist: " + sElementID);
		return;
	}
	
	return oElement;
}
/**
 * shows a managed element by its logical name in the program.
 *
 * @param {string} sElementName
 *  the name of the element in the program
 */
ElementsGroup.prototype.showElement = function(sElementName,displayVal)
{
	
	var e = this.getMyElement(sElementName, "showElement");
	if(!e) return;
	if(displayVal==null)
		displayVal="block";
	e.style.display = displayVal;
	
}
/**
 * hides a managed element by its logical name in the program.
 *
 * @param {string} sElementName
 *  the name of the element in the program
 */
ElementsGroup.prototype.hideElement = function(sElementName)
{
	var e = this.getMyElement(sElementName, "hideElement");
	if(!e) return;
	e.style.display = "none";
}
/**
 * API to add handlers to the onload events
 *
 * @param {function} fHandler
 * A handler to dispatch from window.onload
 */
ElementsGroup.prototype.addOnLoadHandler = function(fHandler)
{
	Claim.isFunction(fHandler,"[ElementsGroup].addOnloadHandler(fHandler) - fHandler must be a function");
	this.onLoadHandlers[this.onLoadHandlers.length] = fHandler;
}
/**
 * @private
 * Used in on-the-fly generated functions, attached to the window.onload event.
 * The goal of this function is to allow sub-classes to have window.onload event 
 * listenerss of thier own, and assure the execution sequence of the event handlers
 * between events of the parent and events of the subclass.
 * The first listener that is registered in the constructor is 
 * ElementsGroup.prv_onLoad_handler_ref
 */
ElementsGroup.prototype.prv_onPageLoad = function()
{
	var i;
	for(i = 0; i < this.onLoadHandlers.length; i++){
		this.onLoadHandlers[i].call(this);
	}
}
/**
 * @private
 * static behavior, called from a function attached to Window.onload
 * dispatches onPageLoad to all instances
 */
ElementsGroup.prv_observeWindowLoad_callback = function()
{
	for(var i = 0; i <  this.all.length; i++)
	{
		this.all[i].prv_onPageLoad();
	}
}

// Call onPageLoad on all instances created untill page-load event
Event.observe(	window
			 ,  "load" 	
			 , function(){
				  ElementsGroup.prv_observeWindowLoad_callback();
			   }
			 );


//============================================================
/**
 * LayersGroup is an ElementGroup that all its layers share the same screen space, 
 * hence, a single layer is visible at a time, or non at all.
 */
LayersGroup = Class.create( "ElementsGroup" );
/**
 *
 */
LayersGroup.prototype.initialize = function(p_screenElementsDictionary, sInstanceName)
{
	this.layersIDs = this.elementsIDs;
	this.layers = this.elements;
}
/**
 *
 */
LayersGroup.prototype.hideAll = function()
{
	var each;
	for(each in this.elementIDs){
		this.hideElement(each);
	}
}
/**
 *
 */
LayersGroup.prototype.showLayer = function(sLayerName)
{
	this.hideAll();
	this.showElement(sLayerName);
}
//==============================================
/**
 * ClientMgr - class for managing client-side UI.
 * Features utilities for:
 *  - post/get/redirections
 *  - show/hide HTML elements
 *
 * @param {string}  sInstanceName
 *  The name of the ClientMgr instance (usually a singletone utilities class)
 *  used for the Log4Js.Logger and for on-the-fly creation of an onload function
 * 
 * @param {string}	p_screenElementsDictionary
 *  dictionary of program-names as keys, and HTML element-ids as values.
 */
ClientMgr = Class.create("ElementsGroup");
ClientMgr.prototype.initialize = function(p_globalElementsDictionary, sInstanceName)
{
	//init the current URL
	var sUrl = location.href;
	//PATCH: - remove #anchor - overcome a bug of the infrastructure
	i =	sUrl.lastIndexOf("#");
	if(   i > -1 && i > sUrl.lastIndexOf("?") && i > sUrl.lastIndexOf("=") )
	{
		sUrl = sUrl.substr(0,i);
	}		
	
	this.Url = Url.parse(sUrl);
	
	//windows collection
	this.windows = {};
	this.templates = {};
	//infra for close all windows in unload
	this.onPageUnloadHandlers = [ ClientMgr.onPageUnload_handler_ref ];
	Event.observe(	window
				 ,	"unload"
				 ,	new Function( "try{\n\t" 
								+	 this.instanceName + ".onPageUnload();\n"
								+ "}catch(ex){\n\t" 
								+	"(new Log4Js.Logger('" + this.instanceName + "')).error('Error or Exception in " + this.instanceName + ".onPageUnload(): ' + (ex.description)?ex.description:ex );\n" 
								+ "}"
								)
				 );
}
 //===============================================
//---- PAGE NAVIGATION AND WINDOWS MANAGMENT ----
/**
 * @private
 * This is a dictionary for name in the program as key, and page-settings dictionary as values
 * for windows the managed page uses.
 * each window settings can contain URL and winSettings in case its used in a window.
 * This collection is managed by the API: 
 *	- ClientMgr.prototype.addManagedPage(...) 
 */
ClientMgr.prototype.pages = {};
/**
 * Adds deffinition for a page in the managed window-deffinitions collection.
 *
 * @param {string} sName
 *  the name of the page in the program
 *
 * @param {string} sUrl
 *  the Url of the page
 *
 * @param {string(optional)} sWinSettings
 * - optional: when not provided - the page does not open in a window, 
 * but replaces the current page
  */
ClientMgr.prototype.addManagedPage = function(sName, sUrl, sWinSettings)
{
	this.pages[sName] = { URL: sUrl
						, winSettings: sWinSettings
						};
}
/**
 * Usefull for:
 * - send the page to _top without conflicting IFrames security.
 * - Submit a form to a provided URL with the provided parameters and form settings.
 *
 * @param {object|string} oFormAttributes
 *  a key-value dictionary of form HTML attributes to be used to add or override to the default settings.
 *  When the parameter is provided as string - it is assumed to be the 
 *  'action' attribute, and the rest of the settings are taken from the default settings.
 *  Default settings: 
 *			- target = "_top"
 *			- method = if oAdditionalParams is provided - "POST", otherwise - "GET"
 *			- action = when oFormAttributes is provided as string
 * 
 * @param {object} oAdditionalParams
 *	a key-value dictionary of required query-string parametes.
 *  when an argument exists both in oParameters and in the sUrl 
 *  as query-string parameter - the parameter in sUrl overrides.
 *  - optional : functionality ignored when not provided.
 *  when this parameter is provided, the default form-method is "POST"
 *  otherwise - "GET".
 *
 */
ClientMgr.prototype.submitToPage = 	function go(oFormAttributes, oAdditionalParams)
{
	if(typeof(oFormAttributes) == 'string'){
		this.log.debug("ClientMgr.submitToPage got 'oFormAttributes' as string, assumed to be the oFormAttributes.action");
		oFormAttributes = {action: oFormAttributes };
	}
	else
	{
		if(oFormAttributes.URL && !oFormAttributes.action)
			oFormAttributes.action = oFormAttributes.URL;
		Claim.isString(	oFormAttributes.action, "..submitToPage(oFormAttributes.action)");
	}
	
	//PATCH: - remove #anchor - overcome a bug of the infrastructure
	var sUrl = oFormAttributes.action;
	i =	sUrl.lastIndexOf("#");
	if(   i > -1 && i > sUrl.lastIndexOf("?") && i > sUrl.lastIndexOf("=") )
	{
		sUrl = sUrl.substr(0,i);
	}
	
	var oUrl = Url.parse(sUrl);
	oFormAttributes.action = oUrl.base;
	// use default settings for all settings not in oFormAttributes
	if(null == oFormAttributes.target) oFormAttributes.target = "_top";
	if(null == oFormAttributes.method) oFormAttributes.method = (oAdditionalParams != null)? "POST" : "GET";

	//copy all settings to a newly created <FORM>
	var f = Object.extend(	document.createElement("FORM")
						 ,	oFormAttributes
						 );
	//copy all parameters found in action URL to oAdditionalParams
	if(oAdditionalParams == null) oAdditionalParams = {};
	var oParams = Object.extend(oAdditionalParams, oUrl.params)

	//populate <FORM> with all fields found
	var e, paramName;
	for(paramName in oParams){
		if(paramName == "") continue;
		
		e =	Object.extend(	document.createElement("INPUT")
						 ,  {  type : "hidden"
							,  name : paramName
							,  value: oParams[paramName]
							} 
						 );
		f.appendChild(e);
	}

	//append and submit
	document.body.appendChild(f)
	f.submit();
}

/**
 * @private
 *  parses window settings string (as passed to window.open)
 *
 * @param {string} sSettings
 */
ClientMgr.prototype.prv_parseWindowSettingsString = function(sSettings){
    if(!sSettings) return;
    Claim.isString(sSettings, "prv_parseWindowSettingsString(sSettings)");
    
    sSettings = sSettings.split(",");
    var oProps = {};
    for(var i = 0 ; i < sSettings.length; i++){
        try{
            sSettings[i] = sSettings[i].split("=");
            oProps[sSettings[i][0].toLowerCase()] = sSettings[i][1];
        }catch(ex){}
    }
    return oProps;
}
/**
 * Opens a managed window
 * - centalizes all windows to the center of the screen
 * - focuses every opened window
 * - allow supply window name from managed windows or directly a URL
 * - allows page names and windows reuse, and allows anonymous windows
 * - hold a reference to all used windows, so that when the page unloads, all windows can be closed
 *
 * @param {string} sWindowPage
 *  The URL to populate the window with. The Url can contain Query-String parameters
 * 
 * @oParams {object} oQSParams
 *  Parameters for to add to the URL Query-String.
 *  When a parameter appears in the QS of the URL and in the oParams, 
 *  the value in oParams overrides the one in the QS.
 * 
 * 
 */
ClientMgr.prototype.openWindow = function(sWindowPage, oQSParams, sWinID, sDefaultWinSettings)
{
    Claim.isString(sWindowPage, this.instanceName + ".openWindow(sWindowPage) - sWindowPage is expected to be a URL in a string");
    
    if(!oQSParams) oQSParams = {};
    
    if(!sWinID) sWinID = sWindowPage;
    
    //TODO:
    var sUrl;
    if(this.pages[sWindowPage]){
		sUrl = this.pages[sWindowPage].URL;
    }else{
		sUrl = sWindowPage;
		sWindowPage = sWindowPage.replace(/[^A-Z^a-z^0-9]*/g,"_");
    }
    
	//PATCH: - remove #anchor - overcome a bug of the infrastructure
	i =	sUrl.lastIndexOf("#");
	if(   i > -1 && i > sUrl.lastIndexOf("?") && i > sUrl.lastIndexOf("=") )
	{
		sUrl = sUrl.substr(0,i);
	}
    
    sUrl = Url.parse(sUrl);
    oQSParams = Object.extend(sUrl.params, oQSParams);    
    delete oQSParams[""];// work around 
    sUrl = Url.appendParams(sUrl.base, oQSParams);

    var sWinSettings;
    if(sDefaultWinSettings){
		sWinSettings = sDefaultWinSettings;
    }else if(this.pages[sWindowPage]) {
		sWinSettings = this.pages[sWindowPage].winSettings;
    }//	else - do nothing...

	if(sWinSettings){
		var oProps = this.prv_parseWindowSettingsString(sWinSettings);
		if(oProps.width && oProps.height){
			var h = parseInt((screen.height - oProps.height) / 2);
			var w = parseInt((screen.width - oProps.width) / 2);
			sWinSettings += ",top=" + h + ",left=" + w;
		}
	}
	    
    if(this.windows[sWinID] && !this.windows[sWinID].closed)
        this.windows[sWinID].close();
    this.windows[sWinID] = window.open(sUrl, sWindowPage, sWinSettings);
}
/**
 * 
 */
ClientMgr.prototype.onPageUnload = function()
{
	var i;
	for(i = 0; i < this.onPageUnloadHandlers.length; i++)
	{
		this.onPageUnloadHandlers[i].call(this);
	}
}
/**
 * @private
 * this is a STATIC member - a handler referece that is attached 
 * dynamicly later to each instance
 */
ClientMgr.onPageUnload_handler_ref = function()
{
	var sWinID;
	for(sWinID in this.windows)
	{
		try{
			this.windows[sWinID].close();
		}catch(e){}
	}
}
//=========================================
//---- DATA_SOURCE-TO-SCREEN UTILITIES ----
ClientMgr.prototype.populateElement = function(sElementID, varValue)
{
	var oElement = $(sElementID);
	if(!oElement){
		this.log.warn("populateElement(..) sElementID specifies and element which does not exist:" + sElementID);
		return;
	}	
	Element.setText(oElement, varValue);
}
/**
 * populates the HTML elements whos IDs are the keys in the parameter oElementsDictionary
 * while thier correlating values are the property-names on the parameter oDataSource.
 *
 * @param {object} oElementsDictionary
 *  dictionary with HTML IDs as keys, and names of properties on oDataSource as values.
 *
 * @param {object} oDataSource
 *  dictionary with property names as keys, and thier fetched data as values.
 */
ClientMgr.prototype.populateElements = function(oElementsDictionary, oDataSource)
{
	Claim.isObject(oElementsDictionary, "[ClientMgr].populateElements(oElementsDictionary)");
	Claim.isObject(oDataSource , "[ClientMgr].populateElements(oDataSource)");
	var each, oElement, value;
	for(each in oElementsDictionary){
		oElement = $(each);
		if(!oElement){
			this.log.warn("populateElements(..) properties-dictionary specifies and element which does not exist:" + each);
			continue;
		}
		value = oDataSource[oElementsDictionary[each]];
		if(null == value){
			this.log.warn("populateElements properties-dictionary askes for a property which does not exist. Element: " + each + ", Property: " + oElementsDictionary[each]);
			continue;
		}
		Element.setText(oElement, value);
	}
}
/**
 * Populates the provided string-template by replacing the placeholders in the sTemplate 
 * with values from the oDataSource. 
 * The searched placeholders and the correlating values are matched according to the 
 * provided oPlaceHoldersAttributesDictionary.
 *
 * @param {string} sTemplate
 *  The template to populate. can be the template istelf as a string, or a template logical name
 *
 * @param {object} oPlaceHoldersAttributes
 *  Dictionary that correlates place-holders in sTemplate as keys, and properties of oDataSource as values.
 *
 * @param {object} oDataSource
 *  Data-Source object
 */
ClientMgr.prototype.populateStringTemplate = function(sTemplate, oPlaceHoldersAttributes, oDataSource)
{
	Claim.isString(sTemplate  , "[ClientMgr].populateStringTemplace(sTemplate,...)");
	Claim.isObject(oPlaceHoldersAttributes, "[ClientMgr].populateStringTemplace(..,oPlaceHoldersAttributes,..)");
	Claim.isObject(oDataSource , "[ClientMgr].populateStringTemplace(..,oDataSource)");


	//The template can be a string, or a template logical name
	if(this.templates[sTemplate])
		sTemplate = this.templates[sTemplate];

	var placehoder, attribute, replacment;
	
	for(placehoder in oPlaceHoldersAttributes)
	{
		attribute = oPlaceHoldersAttributes[placehoder];
		if(null == attribute){
			this.log.warn("in populateStringTemplace(...) oPlaceHoldersAttributes dictionary specifies a placeholder without providing its data-attribute name: " + placehoder);
			continue;
		}
		replacement = oDataSource[attribute];
		if(null == replacement){
			this.log.warn("in populateStringTemplace(...) oPlaceHoldersAttributes dictionary specifies a property that does not exist on the provided oDataSource. Placehoder: " + placehoder +", attribute: " + attribute);
			continue;
		}
		//replacment = replacment.replace(/\$/g,"");
		sTemplate = sTemplate.replace(new RegExp(placehoder,"g"), replacement);
	}
	return sTemplate;
}
ClientMgr.openLink = function (link){
    var wn = window.open(link,'GameCenterMainWindow');
    wn.focus();
}
