	// ***** Require External Dependencies *****
requireNamespace("Phoenix.Widgets", "Phoenix.Widgets.SelectionMap");


/*
	Phoenix.Widgets.SelectionMap()
	
*/

Phoenix.Widgets.SelectionMap = function(container, insertionmethod, mapname, areasobj)
{
	this.nsIdentifier = this.nsIdentifier || "Phoenix.Widgets.SelectionMap";														// What sort of widget are we?

	// ***** Require External Dependencies *****
	requireNamespace("Phoenix.Util.Event", this.neIdentifier);
	requireNamespace("Phoenix.DOM", this.neIdentifier);
	
	// ***** Sanity-Check Passed Parameters *****
	
	if(!container) throw new Phoenix.Exception("Phoenix.Widgets.SelectionMap.Constructor: First parameter must be a valid HTML element reference.");	// Can't survive without an element to bed ourselves down in
	insertionmethod = (insertionmethod == null || insertionmethod > 3 || insertionmethod < 0) ? 3: insertionmethod;			// If insertionmethod not specified or invalid, assume "unrender fallback elements and insert ourselves *before* them in the div"
	mapname = mapname || Phoenix.DOM.generateUniqueId("PWSMap");		// If no mapname specified, generate one guaranteed unique within the page, with the specified prefix for recognisability


	// ***** State Variables *****
	
	// Set up identifying properties of this widget
	this.globalWidgetIndex = Phoenix.Widgets.registerThisWidget(this.nsIdentifier, this);	// Grab a reference to the index of this particular instance of the control in the Phoenix.Widgets._InstantiatedControls array (in case we ever need to refer to ourself in the global scope)
	
	this.renderMethod = insertionmethod;	// How are we to render ourselves?  See Render() for explanation of values.
	this.rendered = false;								// Has this control been rendered yet?
	this.animationDelay = 10;							// time between animation frames (milliseconds)
	this.animationStep = 0.1;								// how many pixels to move per animation step (1=smoothest/slowest)
	this.animationMax = 1;
	this.animationMin = 0;
	this.useIEAsynchContextHack = false;	// IE doesn't properly support asynchronous
	
	// ** "Child HTML Element" references **
	// Root element of this control:
	this.rootElement = container;						// Reference to the root element that this widget inhabits
	this.rootElement.owningControl = this;

	// "Transparent Image Overlay" element (transparent image to attach actual imagemap to):
	this.image = document.createElement("img");	// Reference to the map tag which defines the imagemap
	this.image.src = "/_gfx/selectionmap/mapoverlay.gif";
	this.image.alt = this.image.title = "";
	this.image.useMap = "#"+mapname;
	this.image.className = "mapoverlay";
	this.image.owningControl = this;
	
	// "Imagemap" element (defines imagemap to apply to image overlay):
	this.imageMap = document.createElement("map");	// Reference to the map tag which defines the imagemap
	this.imageMap.id = this.imageMap.name = mapname;
	this.imageMap.owningControl = this;

	// "Map Layer" elements (this.mapLayersRoot points to containing div.  Children are div elements which will be stacked to form eventual composite map image):
	this.mapLayersRoot = document.createElement("div");	// Reference to the div which contains the layer divs
	this.mapLayersRoot.className = "maplayersroot";
	this.mapLayersRoot.owningControl = this;
	this.mapLayersRoot.savedImagesList = new Array();	// Array of image URLs to apply to map layers once they're rendered (style object is apprently reinitialised when the element is added to the page)
	this.mapLayersRoot.movementDirectionList = new Array();
	this.mapLayersRoot.movementTimers = new Array();

	// Set up event-handler callbacks (these are events that the slider control exposes *to other scripts*.  Its constituent elements will have their own (private) event handlers below that call any function attached to these properties after they do their jobs).
	this.onclick = null;							// Fired when the mouse button is raised 
	this.onselectarea = null;							// Fired when mouseup after the thumb slider has changed position
	this.ondeselectarea = null;


	// ***** Do Initial "Pre-Rendering" Setup *****

	// Mark out this element's root container as a mapwidget (for styling purposes)
	Phoenix.DOM.addCSSClass(this.rootElement, "mapwidget");
	
	// If appropriate, hide or unrender any DHTML fallback elements in the div
	// We do this here so that it'll be done while the DOM is still loading, rather than having them flicker by waiting for the entire page to load (exposing the fallback elements the whole time) and then blanking them out after a short delay
	if(this.renderMethod == 2 || this.renderMethod == 3)	// If hide/undisplay all fallback elements
	{
		if(container.hasChildNodes())
		{
			var removemethod = (this.renderMethod == 2) ? "hidden": "unrendered";	// Remove (display:none) the control or just hide (visibility:hidden) it?
	
			// Hide all "fallback" HTML elements (anything inside the div this control has been passed as its container)
			//var children = container.getElementsByTagName("*");
			var children = Phoenix.DOM.getElementsByClassName("dhtmlfallback", container);
			for(var i=0; i<children.length; i++)	// remove/hide old HTML elements
				Phoenix.DOM.addCSSClass(children[i], removemethod);
		}
	}

	// Initialise areas and map layers from the passed-in areasobj (if any)
	for(var i=0; i<areasobj.length; i++)
	{
		// Set up map overlay for this area
		var newlayerelement = document.createElement("div");
		newlayerelement.className = "maplayer";
		newlayerelement.owningControl = this;
		this.mapLayersRoot.appendChild(newlayerelement);

		// Save the style info for attachment on Render()
		this.mapLayersRoot.savedImagesList.push(areasobj[i].mapoverlay);	// Style info gets wiped when element is added to page on Render(), so instead save it and attach the style info in the Render() function

		// set up area tag
		var newareaelement = document.createElement("area");
		newareaelement.className = "maparea";
		newareaelement.owningControl = this;
		newareaelement.href = (areasobj[i].href || ("#" + i));	// href or index, depending on what the config object specifies
		newareaelement.alt = areasobj[i].alt || areasobj[i].title || "";		// If either alt or title not supplied, fill in with the other
		newareaelement.title = areasobj[i].title || areasobj[i].alt || "";
		newareaelement.shape = areasobj[i].shape;
		newareaelement.coords = areasobj[i].coords;
		newareaelement.selected = false;
		this.imageMap.appendChild(newareaelement);
	}
	
	// IE Asynchronous Function Parameters Hack
	// Check to see if we're running under some version of IE with a broken setTimeout/setInterval implementation that doesn't support additional parameters to the callback function.  If non-broken implementation, the asynch function will set the useIEAsynchContextHack to false.  Otherwise (for broken implementations) it'll stay true.
	this.useIEAsynchContextHack = true;
	setTimeout(function(context) { if(context) context.useIEAsynchContextHack = false; }, 0, this);
	

	// ***** Internal Functions *****
	
	this._render = function()
	{
		/*
			Add new controls to parent rootElement in one of four ways:
			1a. Leave previous contents alone and append to end of div (this.renderMethod == 0)
			1b. Leave all previous content alone and prepend to beginning of div (this.renderMethod == 1)
			2a. Set all previous contents of the div to visibility:hidden and prepend to beginning of the div (this.renderMethod == 2)
			2b. Set all previous contents of the div to display:none and prepend to beginning of the div (this.renderMethod == 3)
		*/
		
		var IEversion = navigator.userAgent.substr(navigator.userAgent.indexOf("MSIE")+5, 1);	// Browser-version sniffing is hideous, but it's the only way to target IE6 (but not IE7 or later) for the alpha-PNG hack below	
		var referenceelement;
		if(this.renderMethod == 0)		// Add the new elements to the end of the div
			referenceelement = null;		// parent.insertBefore(element, null) appends to the end of the parent
		else													// Add the new elements to the beginning of the div (insertionmethod == 1-3)
			referenceelement = container.firstChild;
		
		// Add "map layer" divs
		container.insertBefore(this.mapLayersRoot, referenceelement);
		// Add image overlay
		container.insertBefore(this.image, referenceelement);
		// Add imagemap
		container.insertBefore(this.imageMap, referenceelement);
		
		if(IEversion == 6)
		{
			// Check to see if they've set the background image to a PNG file in IE6, and do the alpha-PNG hack if so:
			var bgimage = this.mapLayersRoot.currentStyle.backgroundImage.toString();
			var pngpattern = /url\("?(.*\.png)"?\);?/i;	// IE6 reformats the CSS fields and does things like inserting quotes around the url in url() values, but don't trust it

			var matches = bgimage.match(pngpattern);
			if(typeof(this.mapLayersRoot.style.filter) != "undefined" && matches && matches[1])								// Has filters, is IE version 6 and backgroundImage filename ends with .png (only mainstream format which supports alpha-channel transparency).
			{
				this.mapLayersRoot.style.backgroundImage = "url("+this.image.src+")";											// Set old background to a transparent overlay image (for efficiency, just reuse the transparent map overlay we already use for the imagemap)
				Phoenix.DOM.addFilter(this.mapLayersRoot, " progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+matches[1]+"', sizingMethod='scale')");
			}
			
		}
		
		// Set styles of "map layer" divs (styles are undefined until the element is inserted into the document, and may be wiped on insertion).
		var maplayers = Phoenix.DOM.getElementsByClassName("maplayer", this.mapLayersRoot);
		var areas = this.imageMap.getElementsByTagName("area");

		for(var i=0; i<maplayers.length; i++)
		{
			maplayers[i].style.backgroundImage = "url(" + this.mapLayersRoot.savedImagesList[i] + ")";	// Set background image in CSS

			// We *love* browsers which support alpha-blended PNG and CSS opacity: by default.
			maplayers[i].style.opacity = (areas[i].selected? "1":"0");				// Set overlays to be visible if they've been programmatically selected pre-render.

			if(typeof(maplayers[i].style.filter) != "undefined")	// And then, of course, there are the various flavours of IE.  Sigh.
			{
				if(IEversion == 6)						// IE Alpha-PNG Hack:
				{
					maplayers[i].style.backgroundImage = "url("+this.image.src+")";											// Set old background to a transparent overlay image (for efficiency, just reuse the transparent map overlay we already use for the imagemap)
					Phoenix.DOM.addFilter(maplayers[i], " progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+this.mapLayersRoot.savedImagesList[i]+"', sizingMethod='scale')");
				}
				
				// And a hack for IE7, which still doesn't appear to support opacity: as a CSS property.  Sigh.
				Phoenix.DOM.addFilter(maplayers[i], " progid:DXImageTransform.Microsoft.Alpha(opacity="+(areas[i].selected? "1":"0")+")");
			}
			
			this.mapLayersRoot.movementDirectionList[i] = 0;	// And initialise all layers to be static (ie, not in the process of animating up or down)
		}

		// Add event handlers to area tags in imagemap (similar to styles, event handlers are undefined before insertion into the document)
		var areas = this.imageMap.areas;
		for(var i=0; i<areas.length; i++)
		{	// Set up internal event handlers.
			Phoenix.Util.Event.addEventHandler("click", areas[i], this.toggleSelectArea);
		}

		// And finally, remember that we've been rendered
		this.rendered = true;
	};

	// Internal functions:

	// Step through the "selected/deselected" animation
	this.animateLayer = function (index, context, globalwidgetindex)
	{
		// EITHER: the function was called synchronously (with the index value passed to it)
		// OR it was called as Phoenix.Widgets._InstantiatedControls[globalwidgetindex] by our "IE6/7's broken setTimeout" workaround
		// EITHER WAY: "this" points to the correct location already, so just copy it into "context"
		if(index != null && context == null && this != window)
			context = this;
		else if(index === undefined)	// God knows what's happened here - someone called it synchronously without knowing anything about the function, or tried to call it asynchronously through setTimeout in IE without using our setTimeout hack?
			throw new Phoenix.Exception("State lost during asynchronous call to Phoenix.Widgets.SelectionMap.animateLayer()");
		// ELSE Firefox, called asynchronously, and index/context are already passed-in correctly
		
		var thelayer = Phoenix.DOM.getElementsByClassName("maplayer", context.mapLayersRoot, "div")[index];
		var currentopacity = Phoenix.DOM.getElementOpacity(thelayer) || 0;	// On initial start, no in-line styles defined (but layers default to being invisibile, so if no defined inline style then opacity == 0)
		var thearea = context.imageMap.areas[index];
		
		if(context.mapLayersRoot.movementDirectionList[index] > 0 && currentopacity < context.animationMax)		// Going up, and not hit max height yet...
		{
			currentopacity += context.animationStep;
			Phoenix.DOM.setElementOpacity(thelayer, currentopacity);																					// ...so increase opacity a notch
			
			if(context.useIEAsynchContextHack)
				context.mapLayersRoot.movementTimers[index] = window.setTimeout("Phoenix.Widgets._InstantiatedControls["+globalwidgetindex+"].animateLayer("+index+", null, "+globalwidgetindex+");", context.animationDelay);
			else
				context.mapLayersRoot.movementTimers[index] = window.setTimeout(function(index, context) { context.animateLayer(index, context); }, context.animationDelay, index, context);

		}
		else if(context.mapLayersRoot.movementDirectionList[index] < 0 && currentopacity > context.animationMin)	// Going down, and not hit minumum height yet and this area isn't selected...
		{
			currentopacity -= context.animationStep;
			Phoenix.DOM.setElementOpacity(thelayer, currentopacity);																						// ...so move opacity down a notch

			if(context.useIEAsynchContextHack)
				context.mapLayersRoot.movementTimers[index] = window.setTimeout("Phoenix.Widgets._InstantiatedControls["+globalwidgetindex+"].animateLayer("+index+", null, "+globalwidgetindex+");", context.animationDelay);
			else
				context.mapLayersRoot.movementTimers[index] = window.setTimeout(function(index, context) { context.animateLayer(index, context); }, context.animationDelay, index, context);
		}
		else		// Not going anywhere...
		{
			context.mapLayersRoot.movementTimers[index] = null;	// ...so blank the timer so it's obvious we aren't moving
		}
	};


	// ** Internal Event Handlers **

	// toggleselectarea - highlights/unhighlights an area by locating and opacifying/deopacifying the corresponding mapLayer div
	// May be called as an event-handler (by specifying event and leaving the other parameters null) or procedurally (by setting event to null and passing in index and the other optional parameters)
	this.toggleSelectArea = function(event, index, forcestate, animate, fireevents)
	{
//		var temp = event ? event.srcElement: event;
//		alert(temp + ", " + index + ", " + forcestate + ", " + animate + ", " + fireevents);
		if(!isNaN(parseInt(index)))				// Can't pass extra parameters when a function is called as an event handler, so if index is set we know it's been called procedurally, and hence the event field is irrelevant and *should* be passed as null
		{
			// Set sensible defaults for procedural call
			event = null;											// event is irrelevent when called procedurally
			forcestate = (forcestate === true || forcestate === false) ? forcestate : null;	// toggle the selection state (null), force it to selected (true) or force it to unselected (false)
			animate = animate || false;				// animate the selection/deselection, or snap to the desired state?
			fireevents = fireevents || true;	// just change map's internal selection state, or fire events so external event handlers are notified of change of state as well?
		
			var areas = this.imageMap.getElementsByTagName("area");
			if(index < 0 || index > areas.length)
				return(false);
			var context = this;
			var thearea = areas[index];
		}
		else		// function called as an event handler, so animate & fire events as normal
		{
			event = event || window.event;
			index = 0;
			animate = true;
			fireevents = true;
			forcestate = null;
			
			var thearea = Phoenix.Util.Event.getTarget(event);
			var context = thearea.owningControl;	// Grab a handle to this Phoenix.Widgets.SelectionMap object (for brevity in following lines)
			var areas = context.imageMap.getElementsByTagName("area");
			while(index < areas.length)
			{
				if(areas[index] == thearea)
					break;
				index++;
			}
			if(index >= areas.length)
				return(null);
		}
		
		if(forcestate === true)
			thearea.selected = true;	// force selected
		else if(forcestate === false)
			thearea.selected = false;	// force unselected
		else
			thearea.selected = !thearea.selected;	// toggle selected/unselected

		if(context.rendered == false)	// Called procedurally before widget is rendered, so now we've changed the 
			return(false);

		var thelayer = Phoenix.DOM.getElementsByClassName("maplayer", context.mapLayersRoot, "div")[index];
		
		if(animate)
		{
			if(thearea.selected)
				context.mapLayersRoot.movementDirectionList[index] = 1;	// when selected, no matter what stage of raising/lowering the layer's at, just change the direction to "up"
			else
				context.mapLayersRoot.movementDirectionList[index] = -1;	// when selected, no matter what stage of raising/lowering the layer's at, just change the direction to "up"

			if(context.useIEAsynchContextHack)
				context.mapLayersRoot.movementTimers[index] = window.setTimeout("Phoenix.Widgets._InstantiatedControls["+context.globalWidgetIndex+"].animateLayer("+index+", null, "+context.globalWidgetIndex+");", context.animationDelay);
			else
				context.mapLayersRoot.movementTimers[index] = window.setTimeout(function(index, context) { context.animateLayer(index, context); }, context.animationDelay, index, context);
		}
		else
		{
			if(thearea.selected)
				Phoenix.DOM.setElementOpacity(thelayer, 1);
			else
				Phoenix.DOM.setElementOpacity(thelayer, 0);
		}	

		if(fireevents)	// If we want to notify external event handlers
		{
			if(event === null)	// If called procedurally, construct a blank event to pass to the external event handlers
			{
				if(document.createEvent)
				{
					event = document.createEvent("HTMLEvents");
					event.initEvent("change", true, true);
					thearea.dispatchEvent(event);
				}
				else if(document.createEventObject)
				{
					event = document.createEventObject();
					thearea.fireEvent('onselectstart', event);	// not actually an onselectstart, but we need to fire the "fake" event to fill in event.srcElement for when it's passed to the external event handlers below, and (1) IEv<=7 doesn't support firing onchange events from arbitrary elements, and (2) IE crashes if you try to fire an IE-specific but vaguely valid event like onpropertychange.  Sigh.
				}
			}
			
			// Pass through events to external event-handler hooks (if defined)
			if(context.onclick)
				context.onclick(event);
			
			if(thearea.selected && context.onselectarea)
				context.onselectarea(event);
			else if(!thearea.selected && context.ondeselectarea)
				context.ondeselectarea(event);
			
			// Prevent any default behaviour from the event which fired this event handler (eg, if this is fired from a link-click)
			if(event.preventDefault)
				event.preventDefault();
			event.returnValue = false;	// and support IE
		}
		
		return(false);
	};


	this.coordsListToPointsArray = function (valuelist)
	{
		var onedarray = valuelist.split(",");
		var twodarray = new Array();
		var tempxcoord = null;
		
		for(i=0; i<onedarray.length; i++)
		{
			if(i % 2 == 0)	// If even element, save it (don't add until we have a x,y value pair, in case someone does something stupid like passing an *odd* number of integers)
				tempxcoord = onedarray[i];
			else						// If odd element, add this and the previous one to the array
				twodarray.push({x:parseInt(tempxcoord), y:parseInt(onedarray[i])});
		}
		return(twodarray);
	};

	this.pointsArrayToCoordsList = function (twodarray)
	{
		var onedarray = new Array();
		
		for(i=0; i<twodarray.length; i++)
		{
				onedarray.push(parseInt(twodarray[i].x));
				onedarray.push(parseInt(twodarray[i].y));
		}
		
		return(onedarray);
	};

};