/**
 * class WJWindow
 *
 * The base window class
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindow = Class.create({
	/**
	 * Properties of Window
	 *
	 * string _title
	 * string _type
	 * DOMElement _content
	 * mixed _contenttype
	 * Function _callbackFunction
	 * boolean _visible
	 * integer _x
	 * integer _y
	 * integer _z
	 * integer _w
	 * integer _h
	 **/
	DEFAULT_PARENT: document.body,

	/**
	 * initialize
	 *
	 * Creates a new WJWindow
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param Function callback
	 * @param DOMElement parent (default: document.body)
	 * @return WJWindow
	 **/
	initialize: function(callback, parent, translate) {
		this._loading = false;
		this._basetitle = this._title = "";
		this._theme = "default";
		this._parent = parent || WJWindow.DEFAULT_PARENT || document.body;
		this._listeners = new Hash();
		this.translate = translate || this.translate;

		this._createWindow();
		this._addDefaultListeners();

		this._addCloseButton();

		this.setCallback(callback);
		this.setBaseTitle(WJGuiSettings.windowBaseTitle);
	},

	/**
	 * _createWindow
	 *
	 * Creates a new window DOMElement
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return void
	 **/
	_createWindow: function() {
		var classname = this._getBaseClassname();
		this._windowElement = new Element("div");
		this._windowElement.addClassName(classname);
		this._windowElement.setStyle({"display": "none"});
		this._createWindowRows(["title", "main", "buttons", "bottom"], classname);
		this._windowElementId = this._windowElement.identify();
		this._parent.insert(this._windowElement);
		this._absolutizeTopLeft();
		this.hide();
		this._outerElement = this._windowElement;
		this.setTheme();
	},

	/**
	 * insertWindowRowBefore
	 *
	 * Inserts a new row before given rowname with name newrowname
	 *
	 * @since Tue Sep 23 2008
	 * @access public
	 * @param string rowname
	 * @param string newrowname
	 * @return DOMElement
	 **/
	insertWindowRowBefore: function(rowname, newrowname) {
		if (rowname === "title") {
			return;
		}
		var classname = this._getBaseClassname();
		var row = this._windowElement.select("." + classname + "_" + rowname)
		row = row.first();
		var newrowhtml = this._createRow(newrowname, classname, " " + classname + "_body");
		var div = new Element("div");
		div.update(newrowhtml);
		var toprow = row.parentNode;
		var newrow = toprow.insertBefore(div.firstChild, row );
		newrow = Element.extend(newrow);
		newrow = newrow.select("." + classname + "_content");
		this._contentElements[newrowname] = newrow.first();
		return newrow;
	},

	/**
	 * replaceWindowRow
	 *
	 * Replaces windowrow old with new, keeps references to old alive (they'll return the new rows)
	 * Returns the removed row
	 *
	 * @since Thu Feb 12 2009
	 * @access public
	 * @param string oldrow
	 * @param string newrow
	 * @return htmlelement
	 **/
	replaceWindowRow: function(oldrow, newrow) {
		var inserted = this.insertWindowRowBefore(oldrow, newrow);
		var toremove = this.getContentElement(oldrow);
		this._contentElements[oldrow] = this._contentElements[newrow];
		return toremove.remove();
	},

	/**
	 * removeWindowRow
	 *
	 * Removes windowrow
	 * Returns the removed row
	 *
	 * @since Tue Dec 8 2009
	 * @access public
	 * @param string oldrow
	 * @return htmlelement
	 **/
	removeWindowRow: function(oldrow) {
		var toremove = this.getContentElement(oldrow);
		delete(this._contentElements[oldrow]);
		return toremove.remove();
	},

	/**
	 * _addCloseButton
	 *
	 * Adds a button to close the window
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @return void
	 **/
	_addCloseButton: function() {
		var title = this.getContentElement("title");
		var titlediv = new Element("div", {"onclick": "this.parentNode.getWJWindowObject().fireClose(this)", "title": this.translate("CLOSE_WINDOW") } );
		titlediv.addClassName(this._getBaseClassname() + "_closebutton");
 		title.insert(titlediv);
	},

	/**
	 * fireClose
	 *
	 * Fires the close event from the given element
	 *
	 * @since Thu Oct 16 2008
	 * @access public
	 * @param Element element
	 * @return void
	 **/
	fireClose: function(element) {
		element = $(element);

		/* Observe once function */
		var func = function() {
			this.destroy();
			Event.stopObserving(document, "wjgui:close", arguments.callee.observerFunction);
		}
		var bound = func.bindAsEventListener(this);
		func.observerFunction = bound;
		/* End observe once function */

		Event.observe(document, "wjgui:close", bound);
		element.fire("wjgui:close");
		Event.stopObserving.defer(document, "wjgui:close", bound);
	},

	/**
	 * _addDefaultListeners
	 *
	 * Adds custom event listeners to the window
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @return void
	 **/
	_addDefaultListeners: function(element) {
		this.addListener("true", this.windowResult.bindAsEventListener(this, true) );
		this.addListener("false", this.windowResult.bindAsEventListener(this, false) );
		this.addListener("close", this.windowResult.bindAsEventListener(this, false) );
		this.addListener("save", this.windowResult.bindAsEventListener(this) );
		this.addListener("delete", this.windowResult.bindAsEventListener(this) );
		this.addListener("cancel", this.windowResult.bindAsEventListener(this) );
		this._addDefaultKeyListener();
	},

	/**
	 * _addDefaultKeyListener
	 *
	 * Adds a listener for key's like return and esc
	 *
	 * @since Fri Sep 5 2008
	 * @access protected
	 * @return void
	 **/
	_addDefaultKeyListener: function() {
		// here for extending purposes only
		var element = element || this._windowElement;
		Event.observe(element, "keydown", this.keyHandle.bindAsEventListener(this) );
	},

	/**
	 * keyHandle
	 *
	 * Handles pressing enter or esc
	 *
	 * @since Fri Sep 5 2008
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	keyHandle: function(event) {
		var element = event.element();
		if (Object.isElement(element.up(".wjgui_window") ) ) {
			switch (event.keyCode) {
				case Event.KEY_RETURN:
					if (this.isVisible() ) {
						element.fire("wjgui:true");
					}
					break;
				case Event.KEY_ESC:
					if (this.isVisible() ) {
						element.fire("wjgui:close");
					}
					break;
				default:
					return;
			}
		}
	},

	/**
	 * addListener
	 *
	 * Adds a listener for a custom event that calls the given callback or the default callback of this window
	 *
	 * @since Tue Aug 12 2008
	 * @access
	 * @param
	 * @return WJWindow
	 **/
	addListener: function(eventName, callback, element) {
		WJDebugger.log(WJDebugger.INFO, "Adding listener in WJWindow", eventName, callback);
		var callback = callback || this.windowResult.bindAsEventListener(this);
		var element = element || this._windowElement;

		Event.observe(element, "wjgui:" + eventName, callback);
		this._setListener(eventName, {"element": element, "callback": callback} );
		return this;
	},

	/**
	 * _setListener
	 *
	 * Registers a listener function
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @param string key
	 * @param Object elementAndCallback
	 * @return void
	 **/
	_setListener: function(key, elementAndCallback) {
		this._listeners.set(key, elementAndCallback);
	},

	/**
	 * removeListener
	 *
	 * Removes the listener set for key
	 *
	 * @since Tue Aug 12 2008
	 * @access
	 * @param
	 * @return WJWindow
	 **/
	removeListener: function(key) {
		var listener = this.getListener(key);
		Event.stopObserving(listener.element, "wjgui:" + key, listener.callback);
		this._listeners.unset(key);
		return this;
	},

	/**
	 * removeListeners
	 *
	 * Removes all listeners
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @return WJWindow
	 **/
	removeListeners: function() {
		this._listeners.each(function(info) {
			this.removeListener(info.key);
		}.bind(this) );
		return this;
	},

	/**
	 * windowResult
	 *
	 * Handles the window result event
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	windowResult: function(event) {
		this._callback.apply(this, arguments);
	},

	/**
	 * getListeners
	 *
	 * Returns the listeners hash
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return Hash
	 **/
	getListeners: function() {
		return this._listeners;
	},

	/**
	 * getListener
	 *
	 * Returns the listener info set for key
	 *
	 * @since Tue Aug 12 2008
	 * @access
	 * @param
	 * @return
	 **/
	getListener: function(key) {
		return this._listeners.get(key);
	},

	/**
	 * _getBaseClassname
	 *
	 * Returns the base classname used for windows
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return string
	 **/
	_getBaseClassname: function() {
		return "wjgui_window";
	},

	/**
	 * _getWindowRowTemplate
	 *
	 * Returns a template that can be used to create rows in windows
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return Template
	 **/
	_getWindowRowTemplate: function() {
		return new Template("<div class='#{classprefix}_#{rowname} #{classprefix}_row#{body}'><div class='#{classprefix}_left #{classprefix}_column'><div class='#{classprefix}_right #{classprefix}_column'><div class='#{classprefix}_center #{classprefix}_column'><div class='#{classprefix}_content'>&#160;</div></div></div></div></div>");
	},

	/**
	 * _createWindowRows
	 *
	 * Creates rows with names in the rows argument, appends them to windowElement and prefixes all classes with classprefix
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @param Array rows
	 * @param string classprefix
	 * @param Element windowElement
	 * @return array
	 **/
	_createWindowRows: function(rows, classprefix, windowElement) {
		var windowElement = windowElement || this._windowElement;
		rows.each(function(windowElement, classprefix, rowname, index) {
			var body = " " + classprefix + "_body";
			if (index == 0 || index == (rows.length - 1) ) {
				body = "";
			}
			windowElement.innerHTML += this._createRow(rowname, classprefix, body);
		}.bind(this, windowElement, classprefix));
		this._saveRows(rows, classprefix, windowElement);
		this._addWindowObjectGetters();
	},

	/**
	 * _addWindowObjectGetters
	 *
	 * Adds a getWJWindowObject getter to all content elements and the main window element
	 *
	 * @since Thu Oct 16 2008
	 * @access protected
	 * @return Array
	 **/
	_addWindowObjectGetters: function() {
		var test = [$H(this._contentElements).values(), this._windowElement].flatten();
		test.each(function(el) {
			el.getWJWindowObject = function() { return this; }.bind(this);
		}, this);

		return test;
	},

	/**
	 * _saveRows
	 *
	 * Saves all rows in this._contentElements
	 *
	 * @since Tue Sep 23 2008
	 * @access protected
	 * @param Array rows
	 * @param string classprefix
	 * @param Element windowElement
	 * @return WJWindow
	 **/
	_saveRows: function(rows, classprefix, windowElement) {
		var windowElement = windowElement || this._windowElement;
		this._contentElements = {};
		rows.each(function(windowElements, rowname, index) {
			this._contentElements[rowname] = windowElements[index];
		}.bind(this, windowElement.select("." + classprefix + "_content") ) );
		return this;
	},

	/**
	 * _createRow
	 *
	 * Creates the HTML of a row
	 *
	 * @since Tue Sep 23 2008
	 * @access protected
	 * @param string rowname
	 * @param string classprefix
	 * @param string body
	 * @return string
	 **/
	_createRow: function(rowname, classprefix, body) {
		var row = this._getWindowRowTemplate();
		return row.evaluate({"rowname": rowname, "classprefix": classprefix, "body": body});
	},

	/**
	 * getContentElement
	 *
	 * Returns the content element identified by rowname
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param string rowname
	 * @return DOMElement
	 **/
	getContentElement: function(rowname) {
		return this._contentElements[rowname];
	},

	/**
	 * evalContentElement
	 *
	 * Evaluates the script parts in the content element identified by rowname
	 *
	 * @since Wed Jul 30 2008
	 * @access public
	 * @param string rowname
	 * @return WJWindow
	 **/
	evalContentElement: function(rowname) {
		var element = this.getContentElement(rowname);
		element.innerHTML.evalScripts();
		return this;
	},

	/**
	 * _absolutizeTopLeft
	 *
	 * Puts the window in the top left corner of the viewport
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @param Element element
	 * @return void
	 **/
	_absolutizeTopLeft: function(element) {
		var element = element || this._windowElement;
		element.absolutize();
		element.setStyle({height: "", width: ""});
		this.setX(0, element);
		this.setY(0, element);
	},

	/**
	 * _checkMaxHeight
	 *
	 * Checks the window wo be inside the viewport
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @param DOMElement element
	 * @return void
	 **/
	_checkMaxHeight: function(element) {
		var element = element || this.getContentElement("main");
		if (this.getY() + this.getHeight() > document.viewport.getHeight() ) {
			this.setHeight(document.viewport.getHeight() - this.getY(), element, false);
		}
		else {
			element.setStyle({"maxHeight": ""});
		}
	},

	/**
	 * show
	 *
	 * Shows the window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return WJWindow
	 **/
	show: function(element) {
		var element = element || this._outerElement || this._windowElement;
		element.style.display = "block";
		try { element.focus() } catch(e) {} // TODO think of something better to get focus in browsers and a window in IE6
		return this;
	},

	/**
	 * hide
	 *
	 * Hides the window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return WJWindow
	 **/
	hide: function(element) {
		var element = element || this._outerElement || this._windowElement;
		element.style.display = "none";
		return this;
	},

	/**
	 * destroy
	 *
	 * Destroys the window
	 *
	 * @since Mon Jul 28 2008
	 * @access public
	 * @return WJWindow
	 **/
	destroy: function(element) {
		var element = element || this._outerElement || this._windowElement;
		if (element.parentNode) {
			element.remove();
		}
		return this;
	},

	/**
	 * _callback
	 *
	 * Does the callback that this window should do
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return mixed
	 **/
	_callback: function() {
		if (Object.isFunction(this._callbackFunction) ) {
			var args = $A(arguments);
			args.unshift(this);
			return this._callbackFunction.apply(this._callbackFunction,  args);
		}
	},

	/**
	 * _close
	 *
	 * Closes the window
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return mixed
	 **/
	_close: function(event) {
		this.hide();
	},

	/**
	 * setBaseTitle
	 *
	 * Changes the base title
	 *
	 * @since Wed Sep 10 2008
	 * @access public
	 * @param string title
	 * @return WJWindow
	 **/
	setBaseTitle: function(title) {
		this._basetitle = title;
		this.setTitle(this.getTitle() );
		return this;
	},

	/**
	 * setTitle
	 *
	 * Changes the title
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return WJWindow
	 **/
	setTitle: function(title) {
		this._title = title;
		var headers = this.getContentElement("title").getElementsByTagName("h1");
		if (headers.length < 1) {
			this.getContentElement("title").innerHTML = "<h1>&#160;</h1>" + this.getContentElement("title").innerHTML;
			return this.setTitle(this._title);
		}
		headers[0].innerHTML = this._getComposedTitle();
		return this;
	},

	/**
	 * _getComposedTitle
	 *
	 * Creates a nice looking title
	 *
	 * @since Wed Sep 10 2008
	 * @access protected
	 * @return string
	 **/
	_getComposedTitle: function() {
		return this._title + ( (this._basetitle != "" &&  this._basetitle != this._title) ? ( (this._title != "") ? " - " : "") + this._basetitle : "");
	},

	/**
	 * setContent
	 *
	 * Changes the content
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param mixed content
	 * @return WJWindow
	 **/
	setContent: function(content) {
		if (Object.isString(content) ) {
			this.getContentElement("main").innerHTML = content;
		}
		if (Object.isElement(content) ) {
			this.getContentElement("main").appendChild(content);
		}
		this._content = content;
		return this;
	},

	/**
	 * addButton
	 *
	 * Adds a button to the window
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @param string caption
	 * @param mixed callback
	 * @param boolean defaultButton
	 * @return DOMElement
	 **/
	addButton: function(caption, eventHandler, defaultButton) {
		WJDebugger.log(WJDebugger.INFO, "Adding button to window", caption, eventHandler, this);
		var button = WJButton.create(caption, eventHandler, defaultButton, this.getContentElement("buttons") );
		this._checkMaxHeight(); // this function is likely to change the height of the bottom row
		return button;
	},

	/**
	 * addStatusbar
	 *
	 * Adds a statusbar to the window
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @return void
	 **/
	addStatusbar: function() {
		WJDebugger.log(WJDebugger.INFO, "Adding statusbar to window", this);
		this._statusbar = new Element("div");
		this._statusbar.addClassName("wjgui_statusbar");
		this.getContentElement("buttons").insert(this._statusbar);
		this._checkMaxHeight();
	},

	/**
	 * setStatusbar
	 *
	 * Sets the content of the statusbar
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @param string content
	 * @param integer fade
	 * @return void
	 **/
	setStatusbar: function(content, fade) {
		var fade = fade || -1;
		if (this._statusbar) {
			this._statusbar.update(content);
			if (fade > 0) {
				this.hideStatusbar.bind(this).delay(fade);
			}
		}
	},

	/**
	 * hideStatusbar
	 *
	 * Hides the contents of the statusbar, and then clears its contents
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @return void
	 **/
	hideStatusbar: function() {
		Effect.Fade(this._statusbar, {duration: 3.0});
		this.clearAndShowStatusbar.bind(this).delay(3.5);
	},

	/**
	 * clearAndShowStatusbar
	 *
	 * Clears and shows the statusbar
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @return void
	 **/
	clearAndShowStatusbar: function() {
		this._statusbar.update("");
		this._statusbar.show();
	},

	/**
	 * setCallback
	 *
	 * Changes the callback function
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param Function callback
	 * @return WJWindow
	 **/
	setCallback: function(callback) {
		if (Object.isFunction(callback) ) {
			this._callbackFunction = callback;
		}
		return this;
	},

	/**
	 * setX
	 *
	 * Changes the x position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer x
	 * @return WJWindow
	 **/
	setX: function(x, element) {
		this._x = x || 0;
		var element = element || this._windowElement;
		element.style.left = x + "px";
		return this;
	},

	/**
	 * setY
	 *
	 * Changes the y position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer y
	 * @return WJWindow
	 **/
	setY: function(y, element) {
		this._y = y || 0;
		var element = element || this._windowElement;
		element.style.top = y + "px";
		this._checkMaxHeight(element);
		return this;
	},

	/**
	 * setZ
	 *
	 * Changes the z-index of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer z
	 * @return WJWindow
	 **/
	setZ: function(z, element) {
		this._z = z || 10000;
		var element = element || this._windowElement;
		element.style.zIndex = z;
		return this;
	},

	/**
	 * setWidth
	 *
	 * Changes the width of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer width
	 * @return WJWindow
	 **/
	setWidth: function(width, element) {
		this._width = width;
		var element = element || this._windowElement;
		element.setStyle({"width": width + "px"});
		element.fire("wjgui:resize");
		return this;
	},

	/**
	 * setHeight
	 *
	 * Changes the height of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer height
	 * @return WJWindow
	 **/
	setHeight: function(height, element, checkHeight) {
		this._height = height;
		var element = element || this.getContentElement("main");

		var wasVisible = this.isVisible();
		var origX = this.getX();
		var origY = this.getY();
		if (!wasVisible) {
			this.setX(-10000);
			this.setY(-10000);
			this.show();
		}

		var otherRowsHeight = 0;
		for (var key in this._contentElements) {
			if (key != "main") {
				otherRowsHeight += this._contentElements[key].getHeight();
			}
		}

		var height = (Object.isNumber(height) == false) ? height : (height - otherRowsHeight) + "px";
		element.setStyle({"height": height});

		if (!wasVisible) {
			this.hide();
			this.setX(origX);
			this.setY(origY);
		}

		if (checkHeight != false) {
			this._checkMaxHeight(element);
		}
		if (!checkHeight) {
			element.fire("wjgui:resize");
		}
		return this;
	},

	/**
	 * getBaseTitle
	 *
	 * Returns the title of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return string
	 **/
	getBaseTitle: function() {
		return this._basetitle;
	},

	/**
	 * getTitle
	 *
	 * Returns the title of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return string
	 **/
	getTitle: function() {
		return this._title;
	},

	/**
	 * getType
	 *
	 * Tells what window type this window is
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return string
	 **/
	getType: function() {
		return this._type;
	},

	/**
	 * getContent
	 *
	 * Returns the content of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return mixed
	 **/
	getContent: function() {
		return this._content;
	},

	/**
	 * getContenttype
	 *
	 * Returns the type of the content
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return mixed
	 **/
	getContenttype: function() {

	},

	/**
	 * getCallback
	 *
	 * Returns the callback function
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return Function
	 **/
	getCallback: function() {
		return this._callbackFunction;
	},

	/**
	 * isVisible
	 *
	 * Tells if this window can be seen
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return boolean
	 **/
	isVisible: function() {
		var xInLow = (this.getX() < 0 && (this.getX() + this.getWidth() ) > 0);
		var xInHigh = (this.getX() >= 0 && this.getX() < document.viewport.getWidth() );
		var yInLow = (this.getY() < 0 && (this.getY() + this.getHeight() ) > 0);
		var yInHigh = (this.getY() >= 0 && this.getY() < document.viewport.getHeight() );
		if ( (xInLow || xInHigh) && (yInLow || yInHigh) && this._windowElement.visible() ) {
			return true;
		}
		return false;
	},

	/**
	 * getX
	 *
	 * Tells the x position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getX: function(element) {
		var element = element || this._windowElement;
		return parseInt(element.style.left);
	},

	/**
	 * getY
	 *
	 * Tells the y position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getY: function(element) {
		var element = element || this._windowElement;
		return parseInt(element.style.top);
	},

	/**
	 * getZ
	 *
	 * Tells the zIndex of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getZ: function() {
		var element = element || this._windowElement;
		return element.style.zIndex;
	},

	/**
	 * getWidth
	 *
	 * Tells the width of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getWidth: function(element) {
		var element = element || this._windowElement;
		return element.getWidth();
	},

	/**
	 * getHeight
	 *
	 * Tells the height of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getHeight: function(element) {
		var element = element || this._windowElement;
		return element.getHeight();
	},

	/**
	 * getContentHeight
	 *
	 * Tells what's the height of the content element
	 *
	 * @since Tue Sep 16 2008
	 * @access public
	 * @return integer
	 **/
	getContentHeight: function() {
		return this.getContentElement("main").getHeight();
	},

	/**
	 * getContentWidth
	 *
	 * Tells what's the width of the content element
	 *
	 * @since Tue Sep 16 2008
	 * @access public
	 * @return integer
	 **/
	getContentWidth: function() {
		return this.getContentElement("main").getWidth();
	},

	/**
	 * getWindowElement
	 *
	 * Returns the windowElement
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return DOMElement
	 **/
	getWindowElement: function() {
		return this._windowElement;
	},

	/**
	 * center
	 *
	 * centers the given element
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param DOMElement element
	 * @return WJWindow
	 **/
	center: function(element) {
		var element = element || this._windowElement;
		var remainsX = document.viewport.getWidth() - this.getWidth(element);
		var remainsY = document.viewport.getHeight() - this.getHeight(element);

		this.setX(remainsX / 2, element).setY(remainsY / 2, element);
		return this;
	},

	/**
	 * keepCentered
	 *
	 * Makes sure the window stays centered
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param DOMElement element
	 * @return void
	 **/
	keepCentered: function(element) {
		if (!this._centerObserver) {
			var element = element || this._windowElement;
			this._centerObserver = this.center.bind(this, element);
			Event.observe(window, "resize", this._centerObserver);
		}
	},

	/**
	 * stopCentered
	 *
	 * Stops centering the window
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return void
	 **/
	stopCentered: function() {
		Event.stopObserving(window, "resize", this._centerObserver);
		this._centerObserver = null;
	},

	/**
	 * maximize
	 *
	 * Maximizes the window
	 *
	 * @since Fri Aug 8 2008
	 * @access public
	 * @return WJWindow
	 **/
	maximize: function(paddingTop, paddingRight, paddingBottom, paddingLeft, noScroll) {
		var paddingTop = paddingTop || 0;
		var paddingRight = paddingRight || paddingTop;
		var paddingBottom = paddingBottom || paddingTop;
		var paddingLeft = paddingLeft || paddingTop;
		var noScroll = noScroll || false;

		this.setX(paddingLeft).setY(paddingTop);

		if (noScroll) {
			this.setWidth(0).setHeight(0);
		}

		this.setWidth(document.viewport.getWidth() - (paddingLeft + paddingRight) ).setHeight(document.viewport.getHeight() - (paddingTop + paddingBottom) );
		return this;
	},

	/**
	 * keepMaximized
	 *
	 * Makes sure the window stays maximized
	 *
	 * @since Tue Sep 9 2008
	 * @access public
	 * @return void
	 **/
	keepMaximized: function() {
		if (!this._maximizedObserver) {
			var paddingTop = this.getY();
			var paddingLeft = this.getX();
			var paddingRight = document.viewport.getWidth() - this.getWidth() - paddingLeft;
			var paddingBottom = document.viewport.getHeight() - this.getHeight() - paddingTop;

			this._maximizedObserver = this.maximize.bind(this, paddingTop, paddingRight, paddingBottom, paddingLeft, true);
			Event.observe(window, "resize", this._maximizedObserver);
		}
	},

	/**
	 * stopMaximized
	 *
	 * Stops maximizing the window
	 *
	 * @since Tue Sep 9 2008
	 * @access public
	 * @return void
	 **/
	stopMaximized: function() {
		Event.stopObserving(window, "resize", this._maximizedObserver);
		this._maximizedObserver = null;
	},

	/**
	 * setLoading
	 *
	 * Mark this window as loading (or not)
	 *
	 * @since Wed Sep 3 2008
	 * @access public
	 * @param boolean loading
	 * @param function loadCallback;
	 * @return WJWindow
	 **/
	setLoading: function(loading, loadCallback) {
		var loadCallback = loadCallback || false;
		if (loading && !this.getLoading() ) {
			this.getWindowElement().addClassName("wjgui_window_loading");
			if (!loadCallback) {
				this.getContentElement("main").setStyle({"visibility": "hidden"});
			}
			else {
				loadCallback(this, loading);
			}
		}
		else if (!loading && this.getLoading() ) {
			this.getWindowElement().removeClassName("wjgui_window_loading");
			if (!loadCallback) {
				this.getContentElement("main").setStyle({"visibility": "visible"});
			}
			else {
				loadCallback(this, loading);
			}
		}
		this._loading = loading;
		return this;
	},

	/**
	 * getLoading
	 *
	 * Tells if this window is marked as loading
	 *
	 * @since Wed Sep 3 2008
	 * @access public
	 * @return boolean
	 **/
	getLoading: function() {
		return this._loading;
	},

	/**
	 * setTheme
	 *
	 * Sets the theme for the window
	 *
	 * @since Fri Dec 5 2008
	 * @access public
	 * @param string theme
	 * @return void
	 **/
	setTheme: function(theme) {
		if (theme == this.getTheme() ) {
			return;
		}
		this._removeOldTheme();
		this._setTheme(theme);
		this._addNewTheme();
	},

	/**
	 * _setTheme
	 *
	 * Sets the value of the _theme property
	 *
	 * @since Fri Dec 5 2008
	 * @access protected
	 * @param string theme
	 * @return void
	 **/
	_setTheme: function(theme) {
		this._theme = theme || "default";
	},

	/**
	 * _removeOldTheme
	 *
	 * Removes the previously set theme class from the window
	 *
	 * @since Fri Dec 5 2008
	 * @access
	 * @param
	 * @return
	 **/
	_removeOldTheme: function() {
		this.getWindowElement().removeClassName(this._getBaseClassname() + "theme_" + this.getTheme() );
	},

	/**
	 * _addNewTheme
	 *
	 * Adds the new theme class to the window
	 *
	 * @since Fri Dec 5 2008
	 * @access protected
	 * @return void
	 **/
	_addNewTheme: function() {
		this.getWindowElement().addClassName(this._getBaseClassname() + "theme_" + this.getTheme() );
	},

	/**
	 * getTheme
	 *
	 * Returns the value of the _theme property
	 *
	 * @since Fri Dec 5 2008
	 * @access public
	 * @return string
	 **/
	getTheme: function() {
		return this._theme;
	},

	/**
	 * translate
	 *
	 * Translates strings
	 *
	 * @since Thu Jun 18 2009
	 * @access public
	 * @param string key
	 * @return string
	 **/
	translate: function(key) {
		if (this._translations[key] ) {
			return this._translations[key];
		}
		return key.charAt(0).toUpperCase() + key.replace("_", " ").substr(1).toLowerCase();
	},

	/**
	 * The translations table
	 *
	 * @since Thu Jun 18 2009
	 * @access protected
	 * @var Object
	 **/
	_translations: {OK: "Ok", CANCEL: "Annuleren", YES: "Ja", NO: "Nee", CLOSE_WINDOW: "Venster sluiten"}
});


WJWindow._messagedialog = function(type, message, callback, show, translate, allowHTML) {
	var win = new WJWindow(callback, null, translate);
	var mwin = new window[type](win);
	mwin.setMessage(message, allowHTML);
	if (show) {
		mwin.show();
	}
	return mwin;
};
WJWindow.alert = function(message, callback, translate, allowHTML) {
	return WJWindow._messagedialog("WJWindowAlert", message, callback, true, translate, allowHTML);
};
WJWindow.notice = function(message, callback, translate, allowHTML) {
	return WJWindow._messagedialog("WJWindowNotice", message, callback, true, translate, allowHTML);
};
WJWindow.confirm = function(message, callback, translate, allowHTML) {
	return WJWindow._messagedialog("WJWindowConfirm", message, callback, true, translate, allowHTML);
};
WJWindow.booleanConfirm = function(message, callback, translate, allowHTML) {
	return WJWindow._messagedialog("WJWindowBooleanConfirm", message, callback, true, translate, allowHTML);
};
WJWindow.prompt = function(message, callback, translate, allowHTML) {
	return WJWindow._messagedialog("WJWindowPrompt", message, callback, true, translate, allowHTML);
};
/**
 * WJWindowGooglemaps
 *
 * A class handling google maps info windows
 *
 * @since Fri Jan 30 2009
 * @revision $Revision$
 * @author Reyo Stallenberg
 * @package Windmill.Javascript.WJGui
 **/
var WJWindowGooglemaps = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new instanceof WJWindowModal
	 *
	 * @since Fri Jul 4 2008
	 * @access public
	 * @param WJWindow wjwindow
	 * @return WJWindowModal
	 **/
	initialize: function(toDecorate, kmlname, sitename) {
		this._decorate(toDecorate);
		this._addClassNames(kmlname, sitename);
		if (typeof(kmlname) != "undefined") {
			this.setTheme(kmlname);
		}
		this._addPushpinWindowConnector();
		this._saveStyleSettings();
		this.removeListener("true");
	},

	/**
	 * addPushpinWindowConnector
	 *
	 * Adds a wrapper for the connector between the pushpin and the infowindow
	 *
	 * @since Mon Feb 2 2009
	 * @access protected
	 * @return void
	 **/
	_addPushpinWindowConnector: function() {
		this._pushpinwindowconnector = new Element("div", {"style": "position: absolute;"});
		this._pushpinwindowconnector.addClassName("pushpinwindowconnector");
		this.getWindowElement().insert(new Element("div", {"style": "position: relative; overflow: visible; height: 0px; width: 0px;"} ).insert(this._pushpinwindowconnector ) );
	},

	/**
	 * getPushpinWindowConnectorElement
	 *
	 * Gets the pushpinwindowconnector element
	 *
	 * @since Wed Feb 18 2009
	 * @access public
	 * @return DomNode
	 **/
	getPushpinWindowConnectorElement: function() {
		return this._pushpinwindowconnector;
	},

	/**
	 * _saveStyleSettings
	 *
	 * Saves some settings from the style
	 *
	 * @since Wed Feb 18 2009
	 * @access protected
	 * @return void
	 **/
	_saveStyleSettings: function() {
		var wasVisible = this.isVisible();
		var origX = this.getX();
		var origY = this.getY();
		if (!wasVisible) {
			this.setX(-10000);
			this.setY(-10000);
			this.show();
		}

		this._stylesettings = new Hash();
		var top = (this._pushpinwindowconnector.getStyle("top") != null) ? parseInt(this._pushpinwindowconnector.getStyle("top") ) : 0;
		this._stylesettings.set("pushpinwindowconnector-top", top);
		this._stylesettings.set("pushpinwindowconnector-left", parseInt(this._pushpinwindowconnector.getStyle("left") ) );
		this._stylesettings.set("pushpinwindowconnector-height", this._pushpinwindowconnector.getHeight() );

		if (!wasVisible) {
			this.hide();
			this.setX(origX);
			this.setY(origY);
		}
	},

	/**
	 * getStyleSetting
	 *
	 * Returns a saved style setting
	 *
	 * @since Wed Feb 18 2009
	 * @access
	 * @param
	 * @return mixed
	 **/
	getStyleSetting: function(setting) {
		return this._stylesettings.get(setting);
	},

	/**
	 * _addClassNames
	 *
	 * Adds classnames to the window element
	 *
	 * @since Mon Feb 02 2009
	 * @access protected
	 * @param string kmlname
	 * @param string sitename
	 * @return void
	 **/
	_addClassNames: function(kmlname, sitename) {
		var classname = this._getBaseClassname() + "_googlemaps";
		var element = this.getWindowElement()
		element.addClassName(classname);
		if (typeof(sitename) != "undefined") {
			element.addClassName(classname + "_" + sitename);
		}
	},

	/**
	 * _decorate
	 *
	 * Decorates the given object
	 *
	 * @since Fri Jan 30 2009
	 * @access protected
	 * @param WJWindow toDecorate
	 * @return void
	 **/
	_decorate: function(toDecorate) {
		this._decorated = toDecorate;

		for (property in this._decorated) {
			// Add all methods not defined in this
			if (!Object.isFunction(this[property]) ) {
				this[property] = this._decorated[property];
			}
		}
	},

	/**
	 * fireClose
	 *
	 * Fires the close event from the given element
	 *
	 * @since Fri Jan 30 2009
	 * @access public
	 * @param Element element
	 * @return void
	 **/
	fireClose: function(element) {
		this.hide();
	},

	/**
	 * getHeight
	 *
	 * Tells the height of this window, including the pushpinwindowconnector
	 *
	 * @since Fri Jan 30 2009
	 * @access public
	 * @return integer
	 **/
	getFullHeight: function() {
		return this.getHeight() + (this._stylesettings.get("pushpinwindowconnector-height") + this._stylesettings.get("pushpinwindowconnector-top"));
	}
});
/**
 * WJCBGooglemaps
 *
 * WJCBGooglemaps handles a google map CB
 *
 * @since Tue Dec 30 2008
 * @revision $Revision$
 * @author Reyo Stallenberg
 * @package Windmill.Javascript.Contentblock
 **/
var WJCBGooglemaps = Class.create({
	/**
	 * Properties of WJCBGooglemaps
	 *
	 * Hash _overlays
	 * Hash _forms
	 **/
	_overlays: new Hash(),
	_forms: new Hash(),
	_filterlists: new Hash(),
	_selectobservers: new Hash(),
	_loader: null,

	/**
	 * initialize
	 *
	 * Creates a new WJCBGooglemaps
	 *
	 * @since Tue Dec 30 2008
	 * @access public
	 * @param string id
	 * @return new WJCBGooglemaps
	 **/
	initialize: function(elementid, latitude, longitude, zoom, sitename) {
		this._mapelement = $(elementid);
		this._mapelement.style.backgroundImage = "";

		this._center = new GLatLng(latitude, longitude);
		this._zoom = zoom;
		this._sitename = sitename;

		this._zoomtobounds = false;
		this._bounds = new GLatLngBounds();

		this._addpositioncontrol = true;
		this._positioncontrol = (this._mapelement.getHeight() < 300) ? new GSmallMapControl : new GLargeMapControl();

		this._map = new GMap2(this._mapelement);

		this._map.addMapType(G_PHYSICAL_MAP);
		var hierarchy = new GHierarchicalMapTypeControl();
		hierarchy.addRelationship(G_SATELLITE_MAP, G_HYBRID_MAP, null, true);

		this._addtypecontrol = true;
		this._typecontrol = hierarchy;

		this._enabledoubleclickzoom = true;
		this._enablecontinuouszoom = true;

		this.currentrequests = 0;
		this.requests = new Array();
	},

	/**
	 * setLoader
	 *
	 * Gives possibility to add a loader
	 *
	 * @since Tue Apr 7 2009
	 * @access public
	 * @param element
	 * @return void
	 **/
	setLoader: function(element) {
		this._loader = element;
	},


	/**
	 * getLoader
	 *
	 * Get the loader
	 *
	 * @since Tue Apr 7 2009
	 * @access public
	 * @return element
	 **/
	getLoader: function() {
		return this._loader;
	},

	/**
	 * loaderShowHide
	 *
	 * Checks if it should show or hide the loader (only adds classname)
	 *
	 * @since Tue Apr 7 2009
	 * @access public
	 * @return void
	 **/
	 loaderShowHide: function() {
		if (this.getLoader() && Object.isElement(this.getLoader() ) ) {
			if (this.currentrequests > 0) {
				this.getLoader().addClassName("maploadershow");
			}
			else {
				this.getLoader().removeClassName.bind(this.getLoader() ).defer("maploadershow");
			}
		}
	 },

	/**
	 * changeSettings
	 *
	 * Gives possibility to overwrite some settings
	 *
	 * @since Tue Feb 10 2009
	 * @access
	 * @param
	 * @return
	 **/
	changeSettings: function(settings) {
		for (key in settings) {
			this[key] = settings[key];
		}
	},

	/**
	 * applySettings
	 *
	 * Applies the settings for the map
	 *
	 * @since Tue Feb 10 2009
	 * @access public
	 * @param
	 * @return
	 **/
	applySettings: function() {
		this._map.setCenter(this._center, this._zoom);

		if (this._enabledoubleclickzoom) {
			this._map.enableDoubleClickZoom();
		}
		if (this._enablecontinuouszoom) {
			this._map.enableContinuousZoom();
		}
		if (this._addpositioncontrol) {
			this._map.addControl(this._positioncontrol);
		}
		if (this._addtypecontrol) {
			this._map.addControl(this._typecontrol);
		}

		this._addZoomListener();
	},

	/**
	 * addZoomListener
	 *
	 * Adds a listener to be able to reposition the custom infowindows
	 *
	 * @since Tue Feb 10 2009
	 * @access protected
	 * @return void
	 **/
	_addZoomListener: function() {
		GEvent.addListener(this._map, "zoomend", function() {
			this._overlays.findAll(function(overlay) {
				return (typeof(overlay.value.infowindow) != "undefined" && overlay.value.infowindow.isVisible() );
			} ).each(function(overlay) {
				this.positionInfowindowRelativeToMarker(overlay.value);
			}.bind(this) );
		}.bind(this) );
	},

	/**
	 * pushOverlay
	 *
	 * Stacks the settings of an overlay in this._overlays
	 *
	 * @since Tue Dec 30 2008
	 * @access public
	 * @param String key
	 * @param String overlay
	 * @param Boolean active
	 * @param Boolean filterable
	 * @param Boolean checkable
	 * @return void
	 **/
	pushOverlay: function(key, overlay, active, parser, filterable, checkable) {
		var active = (active == 1) ? true : false;
		this.setOverlay(key, {
			"baseurl": overlay,
			"url": overlay,
			"active": active,
			"parser": parser,
			"filterable": filterable,
			"checkable": checkable
		});
	},

	/**
	 * setOverlay
	 *
	 * Adds an item to this._overlays
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string key
	 * @param object value
	 * @return void
	 **/
	setOverlay: function(key, value) {
		this._overlays.set(key, value);
	},

	/**
	 * getOverlay
	 *
	 * Gets an item from this._overlays
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string key
	 * @return object
	 **/
	getOverlay: function(key) {
		return this._overlays.get(key);
	},

	/**
	 * addOverlays
	 *
	 * Executes addOverlay for the active overlays on this map
	 *
	 * @since Tue Dec 30 2008
	 * @access public
	 * @param boolean readform
	 * @return void
	 **/
	addOverlays: function(readform) {
		var readform = (readform !== false);
		if (readform) {
			this._fillFormsValues();
		}
		this._overlays.each(function(overlay) {
			if (typeof(overlay.value.geoxml) == "undefined") {
				this.getNewGeoXml(overlay);
			}
			else if (overlay.value.active) {
				this.addOverlay(overlay)
			}
		}.bind(this) );
	},

	/**
	 * addOverlay
	 *
	 * Adds the parsed overlay to the map
	 *
	 * @since Fri Jan 30 2009
	 * @access public
	 * @param Hash overlay
	 * @return void
	 **/
	addOverlay: function(overlay) {
		this._map.addOverlay(overlay.value.geoxml);
		overlay.value.lasturl = overlay.value.url;
	},

	/**
	 * getNewGeoXml
	 *
	 * Adds the overlay with the correct parser
	 *
	 * @since Thu Jan 29 2009
	 * @access public
	 * @param Hash overlay
	 * @return void
	 **/
	getNewGeoXml: function(overlay) {
		if (overlay.value.active) {
			if (overlay.value.parser == "GGeoXml") {
				overlay.value.geoxml = new GGeoXml(overlay.value.url);
				this.addOverlay(overlay);
			}
			else {
				this.parseOverlay(overlay);
				var handle = GEvent.addListener(overlay.value.geoxml, "parsed", function (overlay) {
					if (overlay.value.active) {
						this.addOverlay(overlay);
					}
					this.currentrequests--;
					this.loaderShowHide();
					this.zoomToBounds();
					GEvent.removeListener(handle);
				}.bind(this, overlay) );
			}
		}
	},

	/**
	 * hideInfoWindows
	 *
	 * Hides all visible infowindows
	 *
	 * @since Mon Feb 9 2009
	 * @access public
	 * @return void
	 **/
	hideInfoWindows: function() {
		this._overlays.findAll(function(overlay) {
			return (typeof(overlay.value.infowindow) != "undefined");
		} ).each(function(overlay) {
			overlay.value.infowindow.hide();
		});
	},

	/**
	 * createInfoWindow
	 *
	 * Creates an infowindow for the overlay on map
	 *
	 * @since Mon Feb 2 2009
	 * @access public
	 * @return WJWindowGooglemaps
	 **/
	createInfoWindow: function(overlay, map) {
		var callback = function(wjwindow, event, answer) {
			wjwindow.hide();
			event.stop();
		};

		var infowindow = new WJWindowGooglemaps(new WJWindow(callback, $(map.getPane(G_MAP_FLOAT_PANE) ) ), overlay.key, this._sitename);

		var infowindowelement = infowindow.getWindowElement();

		var stopInfowindowEvents = function(event, evtype) {
			var eventelem = Event.findElement(event);
			if (evtype == "mousedown") {
				switch (eventelem.tagName.toLowerCase() ) {
					case "input":
						eventelem.focus();
					case "a":
						break;
					default:
						Event.stop(event);
						break;
				}
			}
			else {
				Event.stop(event);
			}
		}
		GEvent.bindDom(infowindowelement, "mousedown", this, stopInfowindowEvents.bindAsEventListener(infowindowelement, "mousedown") );
		GEvent.bindDom(infowindowelement, "dblclick", this, stopInfowindowEvents.bindAsEventListener(infowindowelement, "dblclick") );
		return infowindow;
	},

	/**
	 * removeOverlays
	 *
	 * Executes removeOverlay for the non active overlays on this map
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @return void
	 **/
	removeOverlays: function() {
		this._overlays.each(function(overlay) {
			if (typeof(overlay.value.geoxml) != "undefined" && overlay.value.filterable) {
				if (typeof(overlay.value.infowindow) != "undefined") {
					overlay.value.infowindow.hide();
				}
				this._map.removeOverlay(overlay.value.geoxml);
			}
			if (overlay.value.lasturl != overlay.value.url && overlay.value.filterable) {
				this.getNewGeoXml(overlay);
			}
		}.bind(this) );
	},

	/**
	 * removeOverlay
	 *
	 * Removes a single overlay
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string key
	 * @return void
	 **/
	removeOverlay: function(key) {
		var overlay = this._overlays.get(key);
		if (overlay.active) {
			overlay.active = false;
			this._map.removeOverlay(overlay.geoxml);
		}
	},

	/**
	 * zoomToBounds
	 *
	 * Resets the zoomlevel and lat lon depending on the content of the map
	 *
	 * @since Tue Jun 23 2009
	 * @access public
	 * @return void
	 **/
	zoomToBounds: function() {
		if (this._zoomtobounds && !(this.currentrequests > 0) ) {
			this._bounds = new GLatLngBounds();
			this._overlays.each(function(overlay) {
				if (overlay.value.active == true) {
					if (typeof(overlay.value.data.gmarkers) != "undefined") {
						overlay.value.data.gmarkers.each(function(gmarker) {
							this._bounds.extend(gmarker.getLatLng() );
						}.bind(this) );
					}
					if (typeof(overlay.value.data.gpolygons) != "undefined") {
						overlay.value.data.gpolygons.each(function(gpolygon) {
							var bounds = gpolygon.getBounds();
							this._bounds.extend(bounds.getNorthEast() );
							this._bounds.extend(bounds.getSouthWest() );
						}.bind(this) );
					}
				}
			}.bind(this) );
			if (!this._bounds.isEmpty() ) {
				this._map.setZoom(this._map.getBoundsZoomLevel(this._bounds) );
				this._map.setCenter(this._bounds.getCenter() );
			}
		}
	},

	/**
	 * addMainFormObserver
	 *
	 * Adds a form observer for the element retrieved by elementid
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string elementid
	 * @return void
	 **/
	addMainFormObserver: function(elementid) {
		var mainform = $(elementid);
		mainform.getInputs().each(function(element) {
			$(element).observe("click", function(event) {
				this._handleMainFormChange(event);
			}.bind(this) );
		}.bind(this) );
		this._mainform = mainform;
	},

	/**
	 * addFormObserver
	 *
	 * Adds a form observer for the given form identified by name
	 *
	 * @since Tue Jan 13 2009
	 * @access public
	 * @param string formname
	 * @return void
	 **/
	addFormObserver: function(formname) {
		var additionalform = $(document.forms[formname] );
		if (Object.isElement(additionalform) ) {
			additionalform.getElements().each(function(element) {
				if (element.tagName.toLowerCase() == "select") {
					this._selectobservers.set(element.name, Form.Element.getValue(element) );
				}
				$(element).observe("click", function(event) {
					var eventelement = Event.findElement(event, "select");
					if (eventelement != document && eventelement) {
						var value =  Form.Element.getValue(eventelement);
						if (value != this._selectobservers.get(eventelement.name) ) {
							this._selectobservers.set(eventelement.name, value);
							this._handleChange(event);
						}
					}
					else {
						this._handleChange(event);
					}
				}.bind(this) );
			}.bind(this) );
			Event.observe(additionalform, "submit", this._handleSubmit.bind(this) );
			this.setForm(additionalform.name, additionalform);
		}
	},

	/**
	 * addAddFilterObserver
	 *
	 * Adds an observer for each <a> that is an "add filter" link
	 *
	 * @since Fri Jul 24 2009
	 * @access public
	 * @param string filterlist
	 * @return void
	 **/
	addFilterlistObservers: function(filterlistwrappersid, id, module) {
		var filterwrapper = $(filterlistwrappersid + "_filters");
		if (filterwrapper) {
			filterwrapper.observe("click", this._handleFilterChange.bindAsEventListener(this) );
		}
		var activefilterwrapper = $(filterlistwrappersid + "_activefilters");
		if (activefilterwrapper) {
			activefilterwrapper.observe("click", this._handleFilterChange.bindAsEventListener(this) );
		}

		this._filterlists.set(id, new Hash({filterwrapper: filterwrapper, activefilterwrapper: activefilterwrapper, module: module} ) );
	},

	/**
	 * _handleFilterChange
	 *
	 * Handles the event where a filter link is clicked
	 *
	 * @since Fri Jul 24 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 * @fires filter:changed
	 **/
	_handleFilterChange: function(event) {
		if (this.currentrequests > 0) {
			// Delay if there are requests pending, otherwise some functionality will break
			this._handleFilterChange.bind(this, event).delay(1);
			event.stop();
		}
		else {
		var link = event.findElement("a");
		if (link) {
			var list = [link.up("ul.filter_filter"), link.up("ul.filter_activefilters")].compact().first();
			if (list) {
				var filter = Object.toQueryString(link.href.toQueryParams() );
				this._applyFilter(filter);
				this.redrawOverlays(false);
				this._updateFilterLists(filter);
				event.stop();
				link.fire("filter:changed", {cbgooglemaps: this});
			}
		}
		}
	},

	/**
	 * _updateFilterLists
	 *
	 * Retrieves the new filterlist's using WJSpin
	 *
	 * @since Fri Jul 24 2009
	 * @access protected
	 * @param Hash filter
	 * @return void
	 **/
	_updateFilterLists: function(filter) {
		this._filterlists.values().each(function(filter, filterinfo) {
			var query = Object.extend({ct: "wmdynamic", dt: "filter", contenttype: "xml", "__type": "xml", module: filterinfo.get("module"), "wmtrigger[]": "requestufts", savecriteria: "true", "count": 1}, filter.toQueryParams() );

			new WJSpin().content(new WJUrl(query, "/index.php"), [this._updateFilterListElements.bind(this, filterinfo) ] );
		}.curry(filter), this);
	},

	/**
	 * _updateFilterListElements
	 *
	 * Updates the divs containing the filter lists
	 *
	 * @since Fri Jul 24 2009
	 * @access protected
	 * @param Hash filterinfo
	 * @param Document response
	 * @return void
	 **/
	_updateFilterListElements: function(filterinfo, response) {
		filterinfo.get("activefilterwrapper").update("");
		filterinfo.get("filterwrapper").update("");

		$A(response.documentElement.childNodes).each(function(listwrapper) {
			if (listwrapper.getAttribute("id") == "activefilters") {
				if (listwrapper.childNodes[0] ) {
					$A(listwrapper.childNodes[0].childNodes).each(function(filtercontent) {
						if (filterinfo.get("activefilterwrapper").ownerDocument.importNode) {
							filtercontent = filterinfo.get("activefilterwrapper").ownerDocument.importNode(filtercontent, true);
							filterinfo.get("activefilterwrapper").appendChild(filtercontent);
						}
						else if (filtercontent.xml) {
							filterinfo.get("activefilterwrapper").update(filterinfo.get("activefilterwrapper").innerHTML + filtercontent.xml);
						}
						else {
							filterinfo.get("activefilterwrapper").appendChild(filtercontent);
						}
					} );
				}
			}
			else if (listwrapper.getAttribute("id") == "filters") {
				if (listwrapper.childNodes[0] ) {
					$A(listwrapper.childNodes[0].childNodes).each(function(filtercontent) {
						if (filterinfo.get("filterwrapper").ownerDocument.importNode) {
							filtercontent = filterinfo.get("filterwrapper").ownerDocument.importNode(filtercontent, true);
							filterinfo.get("filterwrapper").appendChild(filtercontent);
						}
						else if (filtercontent.xml) {
							filterinfo.get("filterwrapper").update(filterinfo.get("filterwrapper").innerHTML + filtercontent.xml);
						}
						else {
							filterinfo.get("filterwrapper").appendChild(filtercontent);
						}
					} );
				}
			}
		} );

		filterinfo.get("filterwrapper").innerHTML = filterinfo.get("filterwrapper").innerHTML;
		filterinfo.get("activefilterwrapper").innerHTML = filterinfo.get("activefilterwrapper").innerHTML;
	},

	/**
	 * _handleSubmit
	 *
	 * Handles a form submit
	 *
	 * @since Thu Jan 15 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	_handleSubmit: function(event) {
		this._handleChange(event);
		Event.stop(event);
	},

	/**
	 * _handleChange
	 *
	 * Handles the change to the form
	 *
	 * @since Fri Jan 2 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	_handleChange: function(event) {
		if (this.currentrequests > 0) {
			// Delay if there are requests pending, otherwise some functionality will break
			this._handleChange.bind(this).delay(1, event);
		}
		else {
			this._fillFormsValues();
			this.redrawOverlays();
		}
	},

	/**
	 * _fillFormsValues
	 *
	 * Changes the urls of the overlays depending on the this._forms values
	 *
	 * @since Wed Jan 21 2009
	 * @access protected
	 * @return void
	 **/
	_fillFormsValues: function() {
		var filter = this._forms.values().invoke("serialize").join("&");
		this._applyFilter(filter);
	},

	/**
	 * _applyFilter
	 *
	 * Applies the given filter to all filterable overlays
	 *
	 * @since Fri Jul 24 2009
	 * @access protected
	 * @param string filter
	 * @return void
	 **/
	_applyFilter: function(filter) {
		this._overlays.values().each(function(filter, overlay) {
			if (overlay.filterable && filter != "") {
				overlay.url = overlay.baseurl + ( (overlay.baseurl.indexOf("?") == -1) ? "?" : "&") + filter;
			}
			else {
				overlay.url = overlay.baseurl;
			}
		}.bind(this, filter) );
	},

	/**
	 * redrawOverlays
	 *
	 * Removes and adds all overlays
	 *
	 * @since Thu Jan 15 2009
	 * @access public
	 * @param boolean readform
	 * @return void
	 **/
	redrawOverlays: function(readform) {
		this.removeOverlays();
		this.addOverlays(readform);
	},

	/**
	 * _handleMainFormChange
	 *
	 * Handles the change to the main form
	 *
	 * @since Thu Jan 15 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	_handleMainFormChange: function(event) {
		var checkbox = Event.element(event);
		this.hideInfoWindows();

		this._overlays.each(function(checkbox, overlay) {
			if (checkbox.name == overlay.key) {
				if (checkbox.checked) {
					overlay.value.active = true;
					if (typeof(overlay.value.geoxml) == "undefined") {
						this.getNewGeoXml(overlay);
					}
					else if ( (overlay.value.url != overlay.value.lasturl) && overlay.value.filterable) {
						this.removeOverlay(checkbox.name);
						this.getNewGeoXml(overlay);
					}
					else {
						this.addOverlay(overlay);
					}
				}
				else {
					this.removeOverlay(checkbox.name);
					overlay.value.active = false;
				}
			}
		}.bind(this, checkbox) );
	},

	/**
	 * setForm
	 *
	 * Adds an item to this._forms
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string key
	 * @param object value
	 * @return void
	 **/
	setForm: function(key, value) {
		this._forms.set(key, value);
	},

	/**
	 * getForm
	 *
	 * Gets an item from this._forms
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string key
	 * @return element
	 **/
	getForm: function(key) {
		return this._forms.get(key);
	},

	/**
	 * parseOverlay
	 *
	 * Creates a new GOverlay and downloads kml
	 *
	 * @since Mon Feb 9 2009
	 * @access public
	 * @param Hash overlay
	 * @return GOverlay
	 **/
	parseOverlay: function(overlay) {
		overlay.value.data = {};
		overlay.value.styles = {};
		overlay.value.geoxml = {};
		overlay.value.geoxml.prototype = new GOverlay();
		overlay.value.geoxml.initialize = this.addOverlayToMap.bind(this, overlay);
		overlay.value.geoxml.remove = this.removeOverlayFromMap.bind(this, overlay);
		overlay.value.geoxml.redraw = this.redrawOverlayOnMap.bind(this, overlay);
		overlay.value.geoxml.copy = this.copyOverlayOfMap.bind(this, overlay);

		this.currentrequests++;
		this.loaderShowHide();
		this.zoomToBounds();

		this.stackRequest(overlay);
		if (this.currentrequests == 1) {
			this.downloadKML();
		}
	},

	/**
	 * stackRequest
	 *
	 * Adds the overlay to the stack of to do requests
	 *
	 * @since Fri Jun 26 2009
	 * @access public
	 * @param Hash overlay
	 * @return void
	 **/
	stackRequest: function(overlay) {
		this.requests.push(overlay);
	},

	/**
	 * downloadKML
	 *
	 * Download the first added overlay of the requests stack
	 *
	 * @since Fri Jun 26 2009
	 * @access public
	 * @return void
	 **/
	downloadKML: function() {
		var overlay = this.requests.shift();
		if (typeof(overlay) != "undefined") {
			GDownloadUrl(overlay.value.url, this.handleKMLRequest.bind(this, overlay) );
		}
	},

	/**
	 * showInfowindow
	 *
	 * Handles the showing of a custom infowindow
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param GMarker m
	 * @param Object overlayvalue
	 * @param String name
	 * @param Element desc
	 * @param String url
	 * @return void
	 **/
	showInfowindow: function(m, overlayvalue, name, desc, url) {
		overlayvalue.infowindow.setContent(desc);
		if (m.chkmlClassName != "") {
			overlayvalue.infowindow.setTheme(m.chkmlClassName);
		}
		overlayvalue.lastmarker = m;
		if (m.chkmlDirections != false) {
		this.handleDirections(m, overlayvalue);
		}
		if (typeof(url) != "undefined") {
			var spincomplete = function(overlayvalue, content) {
				overlayvalue.infowindow.setContent(content);
				overlayvalue.infowindow.setLoading(false);
				overlayvalue.infowindow.setHeight(450);
				var point = this.positionInfowindowRelativeToMarker(overlayvalue);
				this._map.panTo(this._map.fromDivPixelToLatLng(point) );
			};
			var spin = new WJSpin();
			overlayvalue.infowindow.setLoading(true);
			spin.content(url, [spincomplete.bind(this, overlayvalue)]);
		}
		overlayvalue.infowindow.setHeight("auto");
		overlayvalue.infowindow.show();
		var point = this.positionInfowindowRelativeToMarker(overlayvalue);
		if (typeof(url) == "undefined") {
			this._map.panTo(this._map.fromDivPixelToLatLng(point) );
		}
	},

	/**
	 * handleDirections
	 *
	 * If a marker has "directions" this method will handle it
	 *
	 * @since Tue Dec 8 2009
	 * @access public
	 * @param GMarker m
	 * @param Object overlayvalue
	 * @return void
	 **/
	handleDirections: function(m, overlayvalue) {
		if (!this._directions) {
			this.createDirections();
		}

		if (typeof(overlayvalue.infowindow.getContentElement("directions") ) != "undefined") {
			overlayvalue.infowindow.removeWindowRow("directions");
		}
		if (m.chkmlDirections != false) {
			var directionsrow = overlayvalue.infowindow.insertWindowRowBefore("bottom", "directions");
			var directionsrowcontent = overlayvalue.infowindow.getContentElement("directions");
			directionsrowcontent.update(m.chkmlDirections.getHtml() );
			var formelement = directionsrowcontent.down("form");
			if (m.chkmlDirections.addTo === false && m.chkmlDirections.addFrom === true) {
				this._directionsdirection = "from";
			}
			else {
				this._directionsdirection = "to";
			}
			formelement.addClassName("infowindowdirection_" + this._directionsdirection);
			formelement.addClassName("infowindowdirection_hidden");
			formelement.observe("submit", function(event, formelement, overlayvalue) {
				var formvalues = formelement.serialize(true);
				var latlng = overlayvalue.lastmarker.getLatLng().toUrlValue();
				if (this._directionsdirection == "to") {
					var searchstring = "from: " + formvalues.address + " to: " + latlng;
				}
				else {
					var searchstring = "from: " + latlng + " to: " + formvalues.address;
				}
				this._directions.load(searchstring, this._mapdirections);
				Event.stop(event);
			}.bindAsEventListener(this, formelement, overlayvalue) );
			formelement.select("a").each(function(formelement, element, key) {
				element.observe("click", function(event, formelement) {
					var eventelem = Event.element(event);
					if (eventelem.hasClassName("back") ) {
						formelement.addClassName("infowindowdirection_hidden");
						var point = this.positionInfowindowRelativeToMarker(overlayvalue);
						this._map.panTo(this._map.fromDivPixelToLatLng(point) );
					}
					else if (eventelem.hasClassName("link_to") ) {
						this._directionsdirection = "to";
						formelement.removeClassName("infowindowdirection_from");
						formelement.addClassName("infowindowdirection_to");
					}
					else if (eventelem.hasClassName("link_from") ) {
						this._directionsdirection = "from";
						formelement.removeClassName("infowindowdirection_to");
						formelement.addClassName("infowindowdirection_from");
					}
					else if (eventelem.hasClassName("directionsactivator") ) {
						formelement.removeClassName("infowindowdirection_hidden");
						var point = this.positionInfowindowRelativeToMarker(overlayvalue);
						this._map.panTo(this._map.fromDivPixelToLatLng(point) );
					}
				}.bindAsEventListener(this, formelement) );
			}.bind(this, formelement) );
		}
	},

	/**
	 * createDirections
	 *
	 * Creates a direction object
	 *
	 * @since Tue Dec 8 2009
	 * @access public
	 * @return void
	 **/
	createDirections: function() {
		this._mapdirections = this._mapelement.up().appendChild(new Element("div", {"class": "googlemapsdirections"} ) ).hide();
		if (Object.isElement($("googlemapsdirectionserror") ) ) {
			this._directionerrors = $("googlemapsdirectionserror");
		}
		else {
			this._directionerrors = this._mapelement.up().appendChild(new Element("div", {"class": "googlemapsdirectionserror"} ) );
		}
		this._directionerrors.hide();
		this._directions = new GDirections(this._map, this._mapdirections);
		GEvent.addListener(this._directions, "error", this._displayDirectionsError.bindAsEventListener(this) );
		GEvent.addListener(this._directions, "load", this._displayDirections.bindAsEventListener(this) );
	},

	/**
	 * _displayDirections
	 *
	 * Displays the directions
	 *
	 * @since Thu Oct 8 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	_displayDirections: function(event) {
		if (Object.isElement($("googlemapsdirectionserror") ) ) {
			$("googlemapsdirectionserror").hide();
		}
		else {
			this._directionerrors.update("");
		}
		this.hideInfoWindows();
		this._mapdirections.show();
	},

	/**
	 * _displayDirectionsError
	 *
	 * Displays an error for directions
	 *
	 * @since Tue Dec 8 2009
	 * @access protected
	 * @return void
	 **/
	_displayDirectionsError: function() {
		if (Object.isElement($("googlemapsdirectionserror") ) ) {
			$("googlemapsdirectionserror").show();
		}
		else {
			this._directionerrors.update("Route could not be found").show();
		}
		this._mapdirections.hide();
	},

	/**
	 * addOverlayToMap
	 *
	 * Creates items to put on the map
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Hash overlay
	 * @param GMap2 map
	 * @return void
	 **/
	addOverlayToMap: function(overlay, map) {
		WJDebugger.log(WJDebugger.NOTICE, "Adding overlay " + overlay[0], overlay[1]);
		var data = overlay.value.data;

		if (typeof(overlay.value.infowindow) == "undefined") {
			overlay.value.infowindow = this.createInfoWindow(overlay, map);
		}

		// This shouldn't be needed, check why the overlays get duplicated
		this.removeOverlayFromMap(overlay);

		data.gpolygons = [];
		data.gpolylines = [];
		data.gmarkers = [];

		if (typeof(data.polylines) != "undefined") {
			data.polylines.each(function(map, overlay, polyline, key) {
				this.createPolyline(polyline, overlay);
			}.bind(this, map, overlay) );
		}

		if (typeof(data.polygons) != "undefined") {
			data.polygons.each(function(map, overlay, polygon, key) {
				this.createPolygon(polygon, overlay);
			}.bind(this, map, overlay) );
		}

		if (typeof(data.markers) != "undefined") {
			data.markers.each(function(map, overlay, marker, key) {
				this.createMarker(marker, overlay);
			}.bind(this, map, overlay) );
		}
	},

	/**
	 * removeOverlayFromMap
	 *
	 * Removes items of overlay from the map
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Hash overlay
	 * @return void
	 **/
	removeOverlayFromMap: function(overlay) {
		var data = overlay.value.data;

		if (typeof(data.gpolylines) != "undefined") {
			data.gpolylines.each(function(polyline, key){
				this._map.removeOverlay(polyline);
			}.bind(this) );
		}

		if (typeof(data.gpolygons) != "undefined") {
			data.gpolygons.each(function(polygon, key){
				this._map.removeOverlay(polygon);
			}.bind(this) );
		}

		if (typeof(data.gmarkers) != "undefined") {
			data.gmarkers.each(function(marker, key){
				this._map.removeOverlay(marker);
			}.bind(this) );
		}
	},

	/**
	 * redrawOverlayOnMap
	 *
	 * Redraws the overlay on the map (not implemented, but required in GOverlay, so therefore here)
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Hash overlay
	 * @param Boolean force
	 * @return void
	 * @see WJCBGooglemaps.parseOverlay
	 **/
	redrawOverlayOnMap: function(overlay, force) {
	},

	/**
	 * copyOverlayOfMap
	 *
	 * Copies an empty version of the overlay (not implemented, but required in GOverlay, so therefore here)
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @return void
	 * @see WJCBGooglemaps.parseOverlay
	 **/
	copyOverlayOfMap: function () {
	},

	/**
	 * createMarker
	 *
	 * Creates a single marker
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Object data
	 * @param Hash overlay
	 * @return void
	 **/
	createMarker: function(data, overlay) {
		var overlayvalue = overlay.value;
		var point = data.point;
		var name = data.name;
		var desc = data.desc;
		var style = data.styleInfo;
		var url;
		if (data.spin != "") {
			url = new WJUrl(data.spin.toQueryParams() , "/index.php");
		}

		var icon = overlayvalue.styles[style];

		var marker = new GMarker(point, icon);

		marker.chkmlClassName = data.chkmlClassName;
		marker.chkmlDirections = data.chkmlDirections;

		if (name != "" && desc != "") {
			GEvent.addListener(marker, "click", function(m, overlayvalue, name, desc, url) {
				if (m == overlayvalue.lastmarker) {
					overlayvalue.infowindow.show();
					var point = this.positionInfowindowRelativeToMarker(overlayvalue);
					this._map.panTo(this._map.fromDivPixelToLatLng(point) );
				}
				else {
					this.hideInfoWindows();
					overlayvalue.infowindow.setBaseTitle("");
					overlayvalue.infowindow.setContent("");

					overlayvalue.infowindow.setBaseTitle(name);

					var wrapper = new Element("div").update(desc);
					var contentimages = wrapper.select("img");
					overlayvalue.infowindow.contentimagescounter = contentimages.size();
					if (overlayvalue.infowindow.contentimagescounter > 0) {
						var countdown = function(event, m, html, iwoptions, name, desc, url) {
							overlayvalue.infowindow.contentimagescounter--;
							if (overlayvalue.infowindow.contentimagescounter == 0) {
								this.showInfowindow(m, overlayvalue, name, wrapper, url);
							}
						}.bindAsEventListener(this, m, overlayvalue, name, wrapper, url);

						contentimages.invoke("observe", "load", countdown).invoke("observe", "error", countdown);
					}
					else {
						this.showInfowindow(m, overlayvalue, name, desc, url);
					}
				}
			}.bind(this, marker, overlayvalue, name, desc, url) );
		}
		this._map.addOverlay(marker);
		overlayvalue.data.gmarkers.push(marker);
	},

	/**
	 * createPolyline
	 *
	 * Creates a single polyline
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Object data
	 * @param Hash overlay
	 * @return void
	 **/
	createPolyline: function(data, overlay) {
		var overlayvalue = overlay.value;
		var points = data.points;
		var color = data.color;
		var width = data.width;
		var opacity = data.opacity;
		var pbounds = data.pbounds;
		var name = data.name;
		var desc = data.desc;
		var p = new GPolyline(points, color, width, opacity);
		this._map.addOverlay(p);
		overlayvalue.data.gpolylines.push(p);
	},

	/**
	 * createPolygon
	 *
	 * Creates a single polygon
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Object data
	 * @param Hash overlay
	 * @return void
	 **/
	createPolygon: function(data, overlay) {
		var overlayvalue = overlay.value;
		var pbounds = data.pbounds;
		var name = data.name;
		var desc = data.desc;

		var p = new GPolygon(data.points, data.color, data.width, data.opacity, data.fillcolor, data.fillopacity);

		if (name != "" && desc != "") {
			GEvent.addListener(p, "click", function(p, pbounds, overlayvalue, name, desc, clicked) {
				this.hideInfoWindows();
				overlayvalue.infowindow.setBaseTitle("");
				overlayvalue.infowindow.setContent("");

				overlayvalue.infowindow.setBaseTitle(name);

				var wrapper = new Element("div").update(desc);
				var contentimages = wrapper.select("img");
				overlayvalue.infowindow.contentimagescounter = contentimages.size();
				if (overlayvalue.infowindow.contentimagescounter > 0) {
					var countdown = function(event, p, pbounds, overlayvalue, name, desc) {
						overlayvalue.infowindow.contentimagescounter--;
						if (overlayvalue.infowindow.contentimagescounter == 0) {
							this.positionInfowindowRelativeToPoint(overlayvalue, clicked);
							overlayvalue.infowindow.setContent(wrapper);
							overlayvalue.infowindow.show();
							this._map.panTo(clicked);
						}
					}.bindAsEventListener(this, p, pbounds, overlayvalue, name, wrapper);

					contentimages.invoke("observe", "load", countdown).invoke("observe", "error", countdown);
				}
				else {
					this.positionInfowindowRelativeToPoint(overlayvalue, clicked);
					overlayvalue.infowindow.setContent(wrapper);
					overlayvalue.infowindow.show();
					this._map.panTo(clicked);
				}
			}.bind(this, p, pbounds, overlayvalue, name, desc) );
		}

		overlayvalue.data.gpolygons.push(p);
		this._map.addOverlay(p);
	},

	/**
	 * handleKMLRequest
	 *
	 * Handles the response of GDownloadUrl
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Hash overlay
	 * @param String doc
	 * @param Integer responseCode
	 * @return void
	 **/
	handleKMLRequest: function(overlay, doc, responseCode) {
		if(responseCode == 200) {
			this.processKML(GXml.parse(doc), overlay);
		} else if(responseCode == -1) {
			WJDebugger.log(WJDebugger.ERROR, "Data request timed out. Please try later.");
		} else {
			WJDebugger.log(WJDebugger.ERROR, "Request resulted in error. Check XML file is retrievable.");
		}
		this.downloadKML();
	},

	/**
	 * processKML
	 *
	 * Processes the downloaded kml
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Document xmlDoc
	 * @param Hash overlay
	 * @return void
	 **/
	processKML: function(xmlDoc, overlay) {
		if (xmlDoc.text != "") {
			WJDebugger.log(WJDebugger.NOTICE, "Processing " + overlay[0] + " overlay");
			overlay.value.styles = this.readStyles(xmlDoc.documentElement.getElementsByTagName("Style") );
			overlay.value.data = this.readData(xmlDoc.documentElement.getElementsByTagName("Placemark"), overlay.value.styles);
			overlay.value.groundoverlays = this.readGroundOverlays(xmlDoc.documentElement.getElementsByTagName("GroundOverlay") );
			GEvent.trigger(overlay.value.geoxml, "parsed");
			WJDebugger.log(WJDebugger.NOTICE, "Processed " + overlay[0] + " overlay");
		}
	},

	/**
	 * readData
	 *
	 * Reads out placemarks (polygons, polylines and markers)
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param DomNode placemarks
	 * @param Object styles
	 * @return Object
	 * @todo Refactoring and extraction
	 **/
	readData: function(placemarks, styles) {
		var data = {};
		data.polylines = [];
		data.polygons = [];
		data.markers = [];

		// Read through the Placemarks
		for (var i = 0; i < placemarks.length; i++) {
			var chkmlClassName = "";
			var name = this.readKMLValue(placemarks[i].getElementsByTagName("name")[0] );
			var desc = this.readKMLValue(placemarks[i].getElementsByTagName("description")[0] );
			if (typeof(placemarks[i].getElementsByTagNameNS ) != "undefined") {
				var spin = this.readKMLValue(placemarks[i].getElementsByTagNameNS("http://www.connectholland.nl/kml/", "spin")[0] );
			}
			else {
				var spin = this.readKMLValue(placemarks[i].getElementsByTagName("chkml:spin").item(0) );
			}
			var style = this.readKMLValue(placemarks[i].getElementsByTagName("styleUrl")[0] );
			var coords = GXml.value(placemarks[i].getElementsByTagName("coordinates")[0]);
			coords = coords.replace(/\s+/g," "); // tidy the whitespace
			coords = coords.replace(/^ /,"");    // remove possible leading whitespace
			coords = coords.replace(/ $/,"");    // remove possible trailing whitespace
			coords = coords.replace(/, /,",");   // tidy the commas

			var path = coords.split(" ");
			// Is this a polyline/polygon?
			if (path.length > 1) {
				// Build the list of points
				var points = [];
				var pbounds = new GLatLngBounds();
				for (var p = 0; p < path.length - 1; p++) {
					var bits = path[p].split(",");
					if (bits[1] != "") {
						var point = new GLatLng(parseFloat(bits[1] ), parseFloat(bits[0] ) );
						points.push(point);
						this._bounds.extend(point);
						pbounds.extend(point);
					}
				}

				var linestring = placemarks[i].getElementsByTagName("LineString");
				if (linestring.length) {
					// it's a polyline grab the info from the style
					if (!!styles[style]) {
						var chkmlClassName = styles[style].chkmlClassName;
						var width = styles[style].width;
						var color = styles[style].color;
						var opacity = styles[style].opacity;
					} else {
						var width = 5;
						var color = "#0000ff";
						var opacity = 0.45;
					}
					data.polylines.push( {
						points: points,
						width: width,
						color: color,
						opacity: opacity,
						pbounds: pbounds,
						name: name,
	 					desc: desc,
						chkmlClassName: chkmlClassName
					} );
				}

				var polygons = placemarks[i].getElementsByTagName("Polygon");
				if (polygons.length) {
					// it's a polygon grab the info from the style
					if (!!styles[style]) {
						var chkmlClassName = styles[style].chkmlClassName;
						var width = styles[style].width;
						var color = styles[style].color;
						var opacity = styles[style].opacity;
						var fillopacity = styles[style].fillopacity;
						var fillcolor = styles[style].fillcolor;
					} else {
						var width = 5;
						var color = "#0000ff";
						var opacity = 0.45;
						var fillopacity = 0.25;
						var fillcolor = "#0055ff";
					}

					data.polygons.push( {
						points: points,
						color: color,
						width: width,
						opacity: opacity,
						fillcolor: fillcolor,
						fillopacity: fillopacity,
						pbounds: pbounds,
						name: name,
						desc: desc,
						chkmlClassName: chkmlClassName
					} );
				}
			} else {
				// It's not a poly, so I guess it must be a marker
				var bits = path[0].split(",");
				if (!!styles[style] ) {
					var chkmlClassName = styles[style].chkmlClassName;
				}

				var chkmlDirections = this.readDirections(placemarks[i] );
				if (bits[1] != "" && typeof(bits[1]) != "undefined") {
					var point = new GLatLng(parseFloat(bits[1]),parseFloat(bits[0]));
					this._bounds.extend(point);
					data.markers.push( {
						point: point,
						name: name,
						desc: desc,
						styleInfo: style,
						spin: spin,
						chkmlClassName: chkmlClassName,
						chkmlDirections: chkmlDirections
					} );
				}
			}
		}
		return data;
	},

	/**
	 * readDirections
	 *
	 *
	 *
	 * @since Tue Dec 8 2009
	 * @access public
	 * @param DomNode placemark
	 * @return Object
	 **/
	readDirections: function(placemark) {
		if (typeof(placemark.getElementsByTagNameNS ) != "undefined") {
			var directionsnode = placemark.getElementsByTagNameNS("http://www.connectholland.nl/kml/", "directions")[0];
		}
		else {
			var directionsnode = placemark.getElementsByTagName("chkml:directions").item(0);
		}
		if (!directionsnode) {
			return false;
		}
		var html = this.readKMLValue(directionsnode);
		var addTo = (directionsnode.getAttribute("to") == "true");
		var addFrom = (directionsnode.getAttribute("from") == "true");
		return {
			getHtml: function() {
				return html;
			},
			addTo: addTo,
			addFrom: addFrom
		}
	},

	/**
	 * readData
	 *
	 * Reads out groundoverlays and adds them to the map
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param DomNode grounds
	 * @return Array
	 * @todo Refactoring
	 **/
	readGroundOverlays: function(grounds) {
		var parsedGroundOverlays = [];
		// Scan through the Ground Overlays
		for (var i = 0; i < grounds.length; i++) {
			var url = this.readKMLValue(grounds[i].getElementsByTagName("href")[0] );
			var north = parseFloat(this.readKMLValue(grounds[i].getElementsByTagName("north")[0] ) );
			var south = parseFloat(this.readKMLValue(grounds[i].getElementsByTagName("south")[0] ) );
			var east = parseFloat(this.readKMLValue(grounds[i].getElementsByTagName("east")[0] ) );
			var west = parseFloat(this.readKMLValue(grounds[i].getElementsByTagName("west")[0] ) );
			var sw = new GLatLng(south, west);
			var ne = new GLatLng(north, east);
			var ground = new GGroundOverlay(url, new GLatLngBounds(sw, ne) );
			this._bounds.extend(sw);
			this._bounds.extend(ne);
			parsedGroundOverlays.push(ground);
		}
		return parsedGroundOverlays;
	},

	/**
	 * readStyles
	 *
	 * Reads out styles
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param DomNode styles
	 * @return Object
	 * @todo Refactoring and extraction
	 **/
	readStyles: function(styles) {
		var parsedStyles = {};
		for (var i = 0; i <styles.length; i++) {
			var styleID = styles[i].getAttribute("id");
			var iconstyle = styles[i].getElementsByTagName("IconStyle");
			var iconsizeX = 32;
			var iconsizeY = 32;
			var shadowWidth = 59;
			var scale = 1;

			if (iconstyle.length > 0) {
				var scalestyle = iconstyle[0].getElementsByTagName("scale");
				if (scalestyle.length > 0) {
					var scale = this.readKMLValue(scalestyle[0] );
					var iconsizeX = iconsizeX * scale;
					var iconsizeY = iconsizeY * scale;
					var shadowWidth = shadowWidth * scale;
				}

				var anchorX = iconsizeX * 0.5;
				var anchorY = iconsizeY;
				var iwanchorX = iconsizeX * 0.5;
				var iwanchorY = 0;

				var hotspot = iconstyle[0].getElementsByTagName("hotSpot");
				if (hotspot.length > 0) {
					var hotspotpos = this.calculateHotSpot(hotspot[0], iconsizeX, iconsizeY, anchorX, anchorY);
					anchorX = hotspotpos.x;
					anchorY = hotspotpos.y;
				}

				if (typeof(iconstyle[0].getElementsByTagNameNS ) != "undefined") {
					var iwhotspot = iconstyle[0].getElementsByTagNameNS("http://www.connectholland.nl/kml/", "infowindowHotSpot");
					var chkmlClassName = this.readKMLValue(iconstyle[0].getElementsByTagNameNS("http://www.connectholland.nl/kml/", "infowindowClassName")[0] );
					var imagemapnode = iconstyle[0].getElementsByTagNameNS("http://www.connectholland.nl/kml/", "area");
				}
				else {
					var iwhotspot = iconstyle[0].getElementsByTagName("chkml:infowindowHotSpot");
					var chkmlClassName = this.readKMLValue(iconstyle[0].getElementsByTagName("chkml:infowindowClassName").item(0) );
					//image map is not working in that program
				}

				if (iwhotspot.length > 0) {
					var iwhotspotpos = this.calculateHotSpot(iwhotspot[0], iconsizeX, iconsizeY, iwanchorX, iwanchorY);
					iwanchorX = iwhotspotpos.x;
					iwanchorY = iwhotspotpos.y;
				}

				if ( (typeof(imagemapnode) != "undefined") && (imagemapnode.length > 0) ) {
					var imageMapType = imagemapnode.item(0).getAttribute("shape");
					var imageMap = imagemapnode.item(0).getAttribute("coords").split(",");
				}
			}

			var icons = styles[i].getElementsByTagName("Icon");
			// This might not be an icon style
			if (icons.length > 0) {
				var href = this.readKMLValue(icons[0].getElementsByTagName("href")[0]);
				if (!!href) {
					var basehref = href.replace(/\.(png|gif)$/i, "");

					parsedStyles["#" + styleID] = new GIcon(G_DEFAULT_ICON, href);
					parsedStyles["#" + styleID].iconSize = new GSize(iconsizeX, iconsizeY);
					parsedStyles["#" + styleID].shadowSize = new GSize(shadowWidth, iconsizeY);
					parsedStyles["#" + styleID].iconAnchor = new GPoint(anchorX,anchorY);
					parsedStyles["#" + styleID].dragCrossAnchor = new GPoint(2,8);
					parsedStyles["#" + styleID].infoWindowAnchor = new GPoint(iwanchorX,iwanchorY);
					parsedStyles["#" + styleID].shadow = basehref + "-shadow.png";
// 					parsedStyles["#" + styleID].shadow = "";
					parsedStyles["#" + styleID].printImage = basehref + ".gif";
					parsedStyles["#" + styleID].mozPrintImage = basehref + ".gif";
					if (typeof(imagemapnode) != "undefined") {
						parsedStyles["#" + styleID].imageMapType = imageMapType;
						parsedStyles["#" + styleID].imageMap = imageMap;
					}
				}
			}

			// is it a LineStyle ?
			var linestyles = styles[i].getElementsByTagName("LineStyle");
			if (linestyles.length > 0) {
				var width = parseInt(this.readKMLValue(linestyles[0].getElementsByTagName("width")[0] ) );
				if (width < 1) {
					width = 5;
				}
				var color = this.readKMLValue(linestyles[0].getElementsByTagName("color")[0] );
				var opacity = parseInt(color.substr(0,2), 16) / 256;
				color = "#" + color.substr(6,2) + color.substr(4,2) + color.substr(2,2);
				if (!parsedStyles["#" + styleID]) {
					parsedStyles["#" + styleID] = {};
				}
				parsedStyles["#" + styleID].color = color;
				parsedStyles["#" + styleID].width = width;
				parsedStyles["#" + styleID].opacity = opacity;
			}

			// is it a PolyStyle ?
			var polystyles=styles[i].getElementsByTagName("PolyStyle");
			if (polystyles.length > 0) {
				if (!parsedStyles["#" + styleID]) {
					parsedStyles["#" + styleID] = {};
				}

				var fill = parseInt(GXml.value(polystyles[0].getElementsByTagName("fill")[0] ) );
				if (polystyles[0].getElementsByTagName("fill").length == 0) {
					fill = 1;
				}

				var outline = parseInt(GXml.value(polystyles[0].getElementsByTagName("outline")[0] ) );
				if (polystyles[0].getElementsByTagName("outline").length == 0) {
					outline = 1;
				}

				var color = this.readKMLValue(polystyles[0].getElementsByTagName("color")[0] );
				var opacity = parseInt(color.substr(0,2), 16 ) / 256;
				color = "#" + color.substr(6,2) + color.substr(4,2) + color.substr(2,2);

				parsedStyles["#" + styleID].fillcolor = color;
				parsedStyles["#" + styleID].fillopacity = opacity;
				if (!fill) {
					parsedStyles["#" + styleID].fillopacity = 0;
				}
				if (!outline) {
					parsedStyles["#" + styleID].opacity = 0;
				}
			}
			parsedStyles["#" + styleID].chkmlClassName = chkmlClassName;
		}
		return parsedStyles;
	},

	/**
	 * calculateHotSpot
	 *
	 * Calculates the x and y of a hotspot
	 *
	 * @since Mon Feb 9 2009
	 * @access public
	 * @param DomNode hotspot
	 * @param Integer iconsizeX
	 * @param Integer iconsizeY
	 * @param Integer anchorX
	 * @param Integer iconsizeY
	 * @return Object
	 **/
	calculateHotSpot: function(hotspot, iconsizeX, iconsizeY, anchorX, anchorY) {
		var xunits = hotspot.getAttribute("xunits");
		var yunits = hotspot.getAttribute("yunits");
		var xval = hotspot.getAttribute("x");
		var yval = hotspot.getAttribute("y");

		var anchorX = (xunits == "fraction") ? (iconsizeX * xval) : anchorX;
		var anchorX = (xunits == "pixels") ? xval : anchorX;
		var anchorX = (xunits == "insetPixels") ? (iconsizeX - xval) : anchorX;

		var anchorY = (yunits == "fraction") ? (iconsizeY * yval) : anchorY;
		var anchorY = (yunits == "pixels") ? yval : anchorY;
		var anchorY = (yunits == "insetPixels") ? (iconsizeY - yval) : anchorY;
		return {x: anchorX, y: anchorY};
	},

	/**
	 * readKMLValue
	 *
	 * Reads string value of node (uses GXml.value, then removes leading and trailing whitespace)
	 *
	 * @since Mon Feb 9 2009
	 * @access public
	 * @param DomNode node
	 * @return String
	 **/
	readKMLValue: function(node) {
		return GXml.value(node).replace(/^\s*/,"").replace(/\s*$/,"");
	},

	/**
	 * positionInfowindowRelativeToMarker
	 *
	 * Positions the window relative to the last clicked marker
	 *
	 * @since Mon Feb 2 2009
	 * @access public
	 * @param Object overlayvalue
	 * @return GPoint
	 **/
	positionInfowindowRelativeToMarker: function(overlayvalue) {
		if (typeof(overlayvalue.lastmarker) == "undefined") {
			return;
		}
		var fullheight = overlayvalue.infowindow.getFullHeight();
		if (fullheight > this._map.getContainer().getHeight() ) {
			var newheight = overlayvalue.infowindow.getContentHeight() - (fullheight - this._map.getContainer().getHeight() );
			fullheight = overlayvalue.infowindow.setHeight(newheight, null, false).getFullHeight();
		}

		var p = this._map.fromLatLngToDivPixel(overlayvalue.lastmarker.getLatLng() );
		var icon = overlayvalue.lastmarker.getIcon();
		var halfwidth = (overlayvalue.infowindow.getWidth() / 2);
		var offsety = (icon.iconAnchor.y - icon.infoWindowAnchor.y);

		p.y -= offsety;
		p.x -= (icon.iconAnchor.x - icon.infoWindowAnchor.x);
		p.y -= fullheight;
		p.x -= halfwidth;
		overlayvalue.infowindow.setX(p.x);
		overlayvalue.infowindow.setY(p.y);

		overlayvalue.infowindow.getPushpinWindowConnectorElement().setStyle( {"left": ( (halfwidth + overlayvalue.infowindow.getStyleSetting("pushpinwindowconnector-left") ) - offsety) + "px" } );

		var newy = p.y + ( (overlayvalue.infowindow.getFullHeight() + icon.iconSize.height) / 2);
		var newx = p.x + halfwidth;
		return new GPoint(newx, newy);
	},

	/**
	 * positionInfowindowRelativeToPoint
	 *
	 * Positions the window relative to the given point
	 *
	 * @since Mon Feb 2 2009
	 * @access public
	 * @param Object overlayvalue
	 * @return GPoint
	 **/
	positionInfowindowRelativeToPoint: function(overlayvalue, point) {
		var p = this._map.fromLatLngToDivPixel(point);
		var halfwidth = (overlayvalue.infowindow.getWidth() / 2);
		p.y -= overlayvalue.infowindow.getFullHeight();
		p.x -= halfwidth;
		overlayvalue.infowindow.setX(p.x);
		overlayvalue.infowindow.setY(p.y);

		overlayvalue.infowindow.getPushpinWindowConnectorElement().setStyle( {"left": (halfwidth + overlayvalue.infowindow.getStyleSetting("pushpinwindowconnector-left") ) + "px" } );

		var newy = p.y;
		var newx = p.x + halfwidth;
		return new GPoint(newx, newy);
	}
});/**
 * SWFLoader is the javascript loader class for flash embedding
 * Launches the expressinstall when the user doesn't have the required flash version installed
 *
 * This class also handles communication between the Flash movie and the JavaScript class
 *
 * Changelog
 * ---------
 *
 * Niels Nijens - Mon Nov 10 2008
 * --------------------------------
 * - Added Debug interface for the new Debug class
 *
 * Niels Nijens - Thu Nov 06 2008
 * --------------------------------
 * - Added deeplinking
 *
 * Niels Nijens - Mon May 19 2008
 * --------------------------------
 * - Fixed version retrieval for Flash Player 10 beta
 *
 * Niels Nijens - Tue Apr 01 2008
 * --------------------------------
 * - Changed required version from 9.0.47 to 9.0.115 due to some flash functionalities not working
 *
 * Niels Nijens - Mon Mar 10 2008
 * --------------------------------
 * - Changed required version from 9.0.28 to 9.0.47 due to components bug
 *
 * Niels Nijens - Fri Nov 16 2007
 * --------------------------------
 * - Changed SWFCall(); so it will run registered callback functions
 *
 * Niels Nijens - Mon Oct 22 2007
 * --------------------------------
 * - Added unload function
 * - Added timeout function to unload SWFObjects
 *
 * Niels Nijens - Mon Oct 22 2007
 * --------------------------------
 * - Made load() and loadSWFObject() the same, loadSWFObject() missed the expressinstall functionality
 * - Fixed bgcolor error
 *
 * Niels Nijens - Mon Oct 15 2007
 * --------------------------------
 * - Made SWFError(); able to call from Flash
 * - Added arguments to function calls from Flash (thanks to Giso)
 *
 * Niels Nijens - Fri Sep 21 2007
 * --------------------------------
 * - Added addFlashConfigVars(); for WMFlashConfig
 *
 * Niels Nijens - Mon Sep 17 2007
 * --------------------------------
 * - Added size check for the expressinstall
 * - Added addAlternateContentCallback();
 *
 * Niels Nijens - Fri Sep 14 2007
 * --------------------------------
 * - Added workaround (onunload) for IE video streaming bug in Flash Player
 * - Added default alternate content
 *
 * To do
 * ---------
 * -
 *
 * @since Thu Sep 13 2007
 * @author Niels Nijens (niels@connectholland.nl)
 **/
var SWFLoader = Class.create();
SWFLoader.prototype = {

	/**
	 * initialize
	 *
	 * Initialize a new SWFLoader
	 *
	 * @since initial
	 * @return void
	 **/
	initialize: function() {
		this.swfobjects = {};
		this.deeplinkListener = null;
		this.requiredVersion = {"major" : 9, "minor" : 0, "rev" : 115};
		this.expressInstallVersion = {"major" : 6, "minor" : 0, "rev" : 65};
		this.expressInstallSize = {"width" : 215, "height" : 138};
		window.SWFCall = this.SWFCall.bind(this);
		window.SWFError = this.SWFError.bind(this);
		window.SWFLogStart = this.SWFLogStart.bind(this);
		window.SWFLogEnd = this.SWFLogEnd.bind(this);
		window.SWFDebug = this.SWFDebug.bind(this);
		window.SWFDeeplink = this.SWFDeeplink.bind(this);
		this.initUnload();
	},

	/**
	 * load
	 *
	 * Adds a SWF file to the document when flash is available
	 * Otherwise adds alternate content to the element
	 *
	 * @since initial
	 * @param string element
	 * @param string swfname
	 * @param string swffile
	 * @param integer width
	 * @param integer height
	 * @param string bgcolor - transparent also works
	 * @param boolean wmode
	 * @param object swfvars
	 * @return void
	 **/
	load: function(element, swfname, swffile, width, height, bgcolor, swfvars) {
		swfobject = this.getSWFObject(element, swfname, swffile, width, height, bgcolor, swfvars);
		this.loadSWFObject(element, swfobject);
	},

	/**
	 * loadSWFObject
	 *
	 * Adds a SWF file to the document when flash is available
	 * Otherwise adds alternate content to the element
	 * (This function should be used for extends of SWFObject, for example MPlayerObject)
	 *
	 * @since initial
	 * @param string element
	 * @param object swfobject
	 * @return void
	 **/
	loadSWFObject: function(element, swfobject) {
		swfName = swfobject.getAttribute("swfname");
		swfWidth = swfobject.getAttribute("width");
		swfHeight = swfobject.getAttribute("height");
		
		this.checkDeeplinking(swfobject);
		
		if (this.checkPlayerVersion() ) {
			this.addSWFObject.bind(this).defer(element, swfobject);
		}
		else if (this.checkExpressInstallVersion() && this.checkExpressInstallSize(swfWidth, swfHeight) ) {
			installObject = this.getSWFObject(element, swfName, "/lib/swfloader/expressinstall.swf", swfWidth, swfHeight, "transparent", {"SWFContainer" : element, "MMredirectURL" : escape(window.location), "MMdoctitle" : document.title});
			this.addSWFObject.bind(this).defer(element, installObject);
		}
		else {
			this.addAlternateContent(element, swfWidth, swfHeight);
		}
	},

	/**
	 * checkPlayerVersion
	 *
	 * Returns true if the required flash version is available
	 *
	 * @since initial
	 * @return boolean
	 **/
	checkPlayerVersion: function() {
		installedVersion = this.getPlayerVersion();
		if (this.getVersionInt(installedVersion) >= this.getVersionInt(this.requiredVersion) ) {
			return true;
		}
		return false;
	},

	/**
	 * checkPlayerVersion
	 *
	 * Returns true if the flash version required for the express install is available
	 *
	 * @since initial
	 * @return boolean
	 **/
	checkExpressInstallVersion: function() {
		installedVersion = this.getPlayerVersion();
		if (this.getVersionInt(installedVersion) >= this.getVersionInt(this.expressInstallVersion) ) {
			return true;
		}
		return false;
	},

	/**
	 * checkExpressInstallSize
	 *
	 * Returns true if width and height are equal or greater than the required size for the express install
	 *
	 * @since initial
	 * @param integer width
	 * @param integer height
	 * @return boolean
	 **/
	checkExpressInstallSize: function(width, height) {
		if (width >= this.expressInstallSize.width && height >= this.expressInstallSize.height) {
			return true;
		}
		return false;
	},

	/**
	 * getPlayerVersion
	 *
	 * Returns the installed version of flash
	 *
	 * @since initial
	 * @return object
	 **/
	getPlayerVersion: function() {
		if (!this.installedVersion) {
			this.installedVersion = {"major" : 0, "minor" : 0, "rev" : 0};
			if(navigator.plugins && navigator.mimeTypes.length) {
				version = this.getPlayerVersionFF();
			} else {
				version = this.getPlayerVersionIE();
			}
			if (version) {
				var i = 0;
				for (property in this.installedVersion) {
					this.installedVersion[property] = version[i] != null ? parseInt(version[i]) : 0;
					i++;
				}
			}
		}
		return this.installedVersion;
	},

	/**
	 * getVersionInt
	 *
	 * Returns an Integer of the version
	 *
	 * @since Fri Sep 14 2007
	 * @param object version
	 * @return integer
	 **/
	getVersionInt: function(version) {
		return (version.major * Math.pow(1000, 2) ) + (version.minor * 1000) + version.rev;
	},

	/**
	 * getPlayerVersionFF
	 *
	 * Returns the Flash version for FireFox and other browsers
	 * Returns false if Flash isn't available
	 *
	 * @since initial
	 * @return mixed
	 **/
	getPlayerVersionFF: function() {
		var flashplayer = navigator.plugins["Shockwave Flash"];
		if(flashplayer && flashplayer.description) {
			var version = flashplayer.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split(".");
			if (version[2] == "") {
				version[2] = "0";
			}
			
			return version;
		}
		return false;
	},

	/**
	 * getPlayerVersionIE
	 *
	 * Returns the Flash version for Internet Exploder browsers
	 * Returns false if Flash isn't available
	 *
	 * @since initial
	 * @return mixed
	 **/
	getPlayerVersionIE: function() {
		try {
			flashplayer = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
			flashplayer.AllowScriptAccess = "always";
			return flashplayer.GetVariable("$version").split(" ")[1].split(",");
		}
		catch(e) {
			return false;
		}
	},

	/**
	 * getSWFObject
	 *
	 * Adds a SWF to the swfobjects list and writes the SWFObject to the document
	 *
	 * @since Mon Oct 22 2007 (previously known as addSWF() )
	 * @param string element
	 * @param string swfname
	 * @param string swffile
	 * @param integer width
	 * @param integer height
	 * @param string bgcolor
	 * @param boolean wmode
	 * @param object swfvars
	 * @return SWFObject
	 **/
	getSWFObject: function(element, swfname, swffile, width, height, bgcolor, swfvars) {
		swfobject = new SWFObject(swfname, swffile, width, height, bgcolor.replace(/#/g, ""), swfvars);
		
		return swfobject;
	},

	/**
	 * addSWFObject
	 *
	 * Adds a SWF to the swfobjects list and writes the SWFObject to the document
	 *
	 * @since initial
	 * @param string element
	 * @param object swfobject
	 * @return void
	 **/
	addSWFObject: function(element, swfobject) {
		swfname = swfobject.getAttribute("swfname");
		this.swfobjects[swfname] = {};
		this.swfobjects[swfname]["element"] = $(element);
		this.swfobjects[swfname]["object"] = swfobject;
		this.swfobjects[swfname]["object"].write(element);
	},

	/**
	 * addAlternateContent
	 *
	 * Adds alternate content to the document where the Flash should have been
	 *
	 * @since initial
	 * @param string element
	 * @param integer width
	 * @param integer height
	 * @param string bgcolor
	 * @return void
	 **/
	addAlternateContent: function(element, width, height, bgcolor) {
		if ($(element) ) {
			Element.setStyle(element, {"width" : width, "height" : height, "text-align" : "center"});
			$(element).innerHTML = "<a class='swfloadergetflash' href='http://www.adobe.com/go/flashplayer' target='_blank'><img src='/lib/swfloader/images/getflashplayer.gif' border='0'/></a>";
			element.fire("swfloader:loaded-getflashplayer");
			return true;
		}
		return false;
	},

	/**
	 * addAlternateContentCallback
	 *
	 * Callback function for when the autoupdate fails or has been canceled by the user
	 *
	 * @since Mon Sep 17 2007
	 * @param object swf
	 * @return void
	 **/
	addAlternateContentCallback: function(element, width, height) {
		this.addAlternateContent(element, width, height);
	},
	
	/**
	 * unload
	 *
	 * Unloads a SWFObject from the document
	 *
	 * @since Wed Oct 31 2007
	 * @param string swfname
	 * @return void
	 **/
	unload: function(swfname) {
		this.processUnload.bind(this).defer(swfname);
	},
	
	/**
	 * processUnload
	 *
	 * Unloads a SWFObject from the document
	 *
	 * @since Wed Oct 31 2007
	 * @param string swfname
	 * @return void
	 **/
	processUnload: function(swfname) {
		divElement = this.getDivByName(swfname);
		if (divElement) {
			divElement.removeChild(divElement.getElementsByTagName("embed")[0]);
			this.swfobjects[swfname] = undefined;
		}
	},
	
	/**
	 * getSWFObjectByName
	 *
	 * Returns the SWFObject with name swfname if it exists
	 * Otherwise returns false
	 *
	 * @since initial
	 * @param string swfname
	 * @return mixed
	 **/
	getSWFObjectByName: function(swfname) {
		if (this.swfobjects[swfname]) {
			return this.swfobjects[swfname]["object"];
		}
		else {
			return false;
		}
	},

	/**
	 * getDivByName
	 *
	 * Returns the div element with name swfname if it exists
	 * Otherwise returns false
	 *
	 * @since initial
	 * @param string swfname
	 * @return mixed
	 **/
	getDivByName: function(swfname) {
		if (this.swfobjects[swfname]) {
			return this.swfobjects[swfname]["element"];
		}
		else {
			return false;
		}
	},

	/**
	 * SWFCall
	 *
	 * Javascript interface for calls from SWFs
	 *
	 * @since Thu Oct 11 2007
	 * @return mixed
	 **/
	SWFCall: function() {
		flashVars = $A(arguments);
		
		swfname = flashVars.shift();
		methodName = flashVars.shift();
		methodVars = flashVars;
		
		swfobject = this.getSWFObjectByName(swfname);
		if (swfobject) {
			callbackFunction = swfobject.getCallbackByMethod(methodName);
			if (typeof(callbackFunction["callback"]) == "function") {
				return callbackFunction["callback"].apply(swfobject, methodVars);
			}
			else if (typeof(swfobject[methodName]) == "function") {
				return swfobject[methodName].apply(swfobject, methodVars);
			}
			else {
				this.SWFError("Method " + methodName + " doesn't exist in object " + swfname + ".");
			}
		}
		else {
			this.SWFError("Object with name " + swfname + " doesn't exist.");
		}
	},
	
	/**
	 * SWFLogStart
	 *
	 * Starts time log
	 * (For developers only)
	 *
	 * @since Thu Mar 20 2007
	 * @param object debugInfo
	 * @return void
	 **/
	SWFLogStart: function(debugInfo) {
		if (console) {
			console.log("loading (" + debugInfo.id + "): " + debugInfo.url);
			console.time("response time (" + debugInfo.id + ")");
		}
	},
	
	/**
	 * SWFLogEnd
	 *
	 * Ends time log
	 * (For developers only)
	 *
	 * @since Thu Mar 20 2007
	 * @param object debugInfo
	 * @return void
	 **/
	SWFLogEnd: function(debugInfo) {
		if (console) {
			console.timeEnd("response time (" + debugInfo.id + ")");
			console.log(debugInfo);
		}
	},
	
	/**
	 * SWFError
	 *
	 * Prints an error in the console
	 * (For developers only)
	 *
	 * @since Thu Oct 11 2007
	 * @param string message
	 * @param boolean log
	 * @return void
	 **/
	SWFError: function(message, log) {
		if ( $("consolewindow") ) {
			if (!log) {
				message = "ERROR: " + message;
			}
			
			$("consolewindow").innerHTML += message + "<br/>";
			
			return;
		}
		if (console) {
			if (log) {
				console.log(message);
			}
			else {
				console.error("ERROR: " + message);
			}
			
			return;
		}
	},
	
	/**
	 * SWFDebug
	 *
	 * Displays Debug messages from the SWF
	 *
	 * @since Mon Nov 10 2008
	 * @return void
	 **/
	SWFDebug: function(level) {
		var args = $A(arguments);
		args.shift();
		if (typeof(console) != "undefined" && typeof(console.error) == "function" && typeof(console.warn) == "function" && typeof(console.log) == "function") {
			switch (level) {
				case 1:
					console.error.apply(console, args);
					break;
				case 2:
					console.warn.apply(console, args);
					break;
				default:
					console.log.apply(console, args);
					break;
			}
		}
		else {
			throw Error(args);
		}
	},
	
	/**
	 * checkDeeplink
	 *
	 * Checks if deeplinking should be enabled for swfobject
	 *
	 * @since Thu Nov 06 2008
	 * @param SWFObject swfobject
	 * @return void
	 **/
	checkDeeplinking: function(swfobject) {
		var swfvars = swfobject.getVariables();
		if (swfvars["deeplinking"] == true) {
			swfobject.deeplinking = true;
			if (this.deeplinkListener == null) {
				this.deeplinkListener = new PeriodicalExecuter(this.broadcastDeeplink.bind(this), 0.05);
			}
		}
	},
	
	/**
	 * SWFDeeplink
	 *
	 * Deeplink functionality for flash websites
	 *
	 * @since Thu Nov 06 2008
	 * @param string link
	 * @param string title
	 * @return void
	 **/
	SWFDeeplink: function(link, title) {
		document.location.hash = "#" + link;
		if (title != "") {
			document.title = title;
		}
	},
	
	/**
	 * broadcastDeeplink
	 *
	 * Deeplink listener. Broadcasts the deeplink to the SWFObject's that have deeplinking enabled
	 *
	 * @since Thu Nov 06 2008
	 * @param string link
	 * @param string title
	 * @return void
	 **/
	broadcastDeeplink: function() {
		var deeplink = document.location.hash.replace(/#/g, "");
		if (deeplink != "" && this.broadcastedDeeplink != deeplink) {
			this.broadcastedDeeplink = deeplink;
			
			for (var swfname in this.swfobjects) {
				if (this.swfobjects[swfname]["object"].deeplinking) {
					this.swfobjects[swfname]["object"].setDeeplink(deeplink);
				}
			}
		}
	},
	
	/**
	 * initUnload
	 *
	 * Adds an onunload event to the document
	 * This is needed for an IE video streaming bug in Flash Player
	 *
	 * See: http://blog.deconcept.com/2006/07/28/swfobject-143-released/
	 *
	 * @since Fri Sep 14 2007
	 * @return void
	 **/
	initUnload: function() {
		if (!window.opera && document.all) {
			__flash_unloadHandler = function(){};
			__flash_savedUnloadHandler = function(){};
			
			//window.attachEvent("onunload", this.cleanupSWFObjects);
		}
	},

	/**
	 * cleanupSWFObjects
	 *
	 * Cleans up all the object elements
	 *
	 * @since Fri Sep 14 2007
	 * @return void
	 **/
	cleanupSWFObjects: function() {
		if (window.opera || !document.all) {
			return;
		}

		var objects = document.getElementsByTagName("object");
		for (i = 0; i < objects.length; i++) {
			objects[i].style.display = 'none';
			for (var x in objects[i]) {
				if (typeof objects[i][x] == 'function') {
					objects[i][x] = function() { };
				}
			}
		}
	}
}

/**
 * SWFObject
 *
 * Changelog
 * ---------
 *
 * Niels Nijens - Fri Nov 16 2007
 * -------------------------------
 * - Added registerCallback(); and getCallbackByMethod(); to override function calls from flash
 *
 * Niels Nijens - Mon Nov 05 2007
 * -------------------------------
 * - Added getContainer(); to get the SWFObject's parent div
 *
 * Niels Nijens - Mon Oct 22 2007
 * -------------------------------
 * - Added methodExists(); for flash function calls
 *
 * Niels Nijens - Tue Sep 18 2007
 * -------------------------------
 * - Added getColor and getWmode to combine them into one variable bgcolor
 *
 * @since Thu Sep 13 2007
 * @author Niels Nijens (niels@connectholland.nl)
 **/
var SWFObject = Class.create();
SWFObject.prototype = {

	/**
	 * initialize
	 *
	 * Initialize a new SWFObject
	 *
	 * @since initial
	 * @param string swfname
	 * @param string swffile
	 * @param integer width
	 * @param integer height
	 * @param string bgcolor
	 * @param boolean wmode
	 * @param object swfvars
	 * @return void
	 **/
	initialize: function(swfname, swffile, width, height, bgcolor, swfvars) {
		this.deeplinking = false;
		this.registeredCallbacks = {};
		
		this.initAttributes({"swffile" : swffile, "swfname" : swfname, "width" : width, "height" : height});
		this.initParams({"quality" : "high", "menu" : "false", "scale" : "noscale", "AllowScriptAccess" : "always", "bgcolor" : this.getColor(bgcolor), "wmode" : this.getWMode(bgcolor)});
		this.initVariables(swfvars);
		this.addFlashConfigVars();
	},

	/**
	 * initAttributes
	 *
	 * Creates and Fills the attributes object with variables
	 * (Is used for basic variables like filepath and the name of the SWF)
	 *
	 * @since initial
	 * @param object attributes
	 * @return void
	 **/
	initAttributes: function(attributes) {
		this.attributes = {};

		for(property in attributes) {
			if (attributes[property]) {
				this.setAttribute(property, attributes[property]);
			}
		}
	},

	/**
	 * initParams
	 *
	 * Creates and Fills the params object with variables
	 * (Is used for basic variables like bgcolor and wmode)
	 *
	 * @since initial
	 * @param object params
	 * @return void
	 **/
	initParams: function(params) {
		this.params = {};

		for(property in params) {
			if (params[property]) {
				this.addParam(property, params[property]);
			}
		}
	},

	/**
	 * initVariables
	 *
	 * Creates and Fills the variables object with variables
	 * (Is used for variables to be send to flash)
	 *
	 * @since initial
	 * @param object variables
	 * @return void
	 **/
	initVariables: function(variables) {
		this.variables = {};

		for(property in variables) {
			if (variables[property]) {
				this.addVariable(property, variables[property]);
			}
		}
	},

	/**
	 * addFlashConfigVars
	 *
	 * Adds the variables required for WMFlashConfig
	 *
	 * @since Fri Sep 21 2007
	 * @return void
	 **/
	addFlashConfigVars: function() {
		this.addVariable("swfname", this.getAttribute("swffile").substr(this.getAttribute("swffile").lastIndexOf("/") + 1, this.getAttribute("swffile").lastIndexOf(".swf") - (this.getAttribute("swffile").lastIndexOf("/") + 1) ) );
		this.addVariable("swfpath", this.getAttribute("swffile").substr(0, this.getAttribute("swffile").lastIndexOf("/") + 1) );
	},

	/**
	 * getColor
	 *
	 * Returns the bgcolor
	 *
	 * @since Tue Sep 18 2007
	 * @param string bgcolor
	 * @return mixed
	 **/
	getColor: function(bgcolor) {
		if (bgcolor == "transparent") {
			return false;
		}
		return bgcolor;
	},

	/**
	 * getWMode
	 *
	 * Returns the wmode
	 *
	 * @since Tue Sep 18 2007
	 * @param string bgcolor
	 * @return mixed
	 **/
	getWMode: function(bgcolor) {
		if (bgcolor != "transparent") {
			return false;
		}
		return bgcolor;
	},

	/**
	 * setAttribute
	 *
	 * Adds a variable with name and value to the attributes object
	 *
	 * @since initial
	 * @param string name
	 * @param mixed value
	 * @return void
	 **/
	setAttribute: function(name, value){
		this.attributes[name] = value;
	},

	/**
	 * getAttribute
	 *
	 * Returns the value of an attribute by name
	 *
	 * @since initial
	 * @param string name
	 * @return mixed
	 **/
	getAttribute: function(name) {
		return this.attributes[name];
	},

	/**
	 * addParam
	 *
	 * Adds a variable with name and value to the params object
	 *
	 * @since initial
	 * @param string name
	 * @param mixed value
	 * @return void
	 **/
	addParam: function(name, value) {
		this.params[name] = value;
	},

	/**
	 * getParams
	 *
	 * Returns the params object
	 *
	 * @since initial
	 * @return object
	 **/
	getParams: function() {
		return this.params;
	},

	/**
	 * addVariable
	 *
	 * Adds a variable with name and value to the variables object
	 *
	 * @since initial
	 * @param string name
	 * @param mixed value
	 * @return void
	 **/
	addVariable: function(name, value){
		this.variables[name] = value;
	},

	/**
	 * getVariables
	 *
	 * Returns the variables object
	 *
	 * @since initial
	 * @return object
	 **/
	getVariables: function() {
		return this.variables;
	},

	/**
	 * getVariablePairs
	 *
	 * Returns the variables object as an array with strings
	 *
	 * @since initial
	 * @return array
	 **/
	getVariablePairs: function(){
		variables = this.getVariables();
		variablePairs = new Array();
		for(property in variables){
			variablePairs.push(property + "=" + variables[property]);
		}
		return variablePairs;
	},

	/**
	 * getSWFHTML
	 *
	 * Returns the HTML for the SWF
	 *
	 * @since initial
	 * @return string
	 **/
	getSWFHTML: function() {
		if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) {
			this.addVariable("MMplayerType", "PlugIn");
			SWFNode = this.getSWFHTMLEmbed();
		} else {
			this.addVariable("MMplayerType", "ActiveX");
			SWFNode = this.getSWFHTMLObject();
		}
		return SWFNode;
	},

	/**
	 * getSWFHTMLEmbed
	 *
	 * Returns the HTML <embed> for the SWF
	 * (FireFox browsers)
	 *
	 * @since initial
	 * @return string
	 **/
	getSWFHTMLEmbed: function() {
		SWFNode = "<embed type='application/x-shockwave-flash' src='" + this.getAttribute("swffile") + "'";
		SWFNode += " width='" + this.getAttribute("width") + "' height='" + this.getAttribute("height") + "'";
		SWFNode += " id='" + this.getAttribute("swfname") + "' name='" + this.getAttribute("swfname") + "' ";
		SWFNode += this.getParamHTML(true);
		SWFNode += this.getVariableHTML(true);
		SWFNode += "/>";

		return SWFNode;
	},

	/**
	 * getSWFHTMLObject
	 *
	 * Returns the HTML <object> for the SWF
	 * (Internet Exploder browsers)
	 *
	 * @since initial
	 * @return string
	 **/
	getSWFHTMLObject: function() {
		SWFNode = "<object id='" + this.getAttribute("swfname") + "' classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'";
		SWFNode += " width='" + this.getAttribute("width") + "' height='" + this.getAttribute("height") + "'>";
		SWFNode += "<param name='movie' value='" + this.getAttribute("swffile") + "'/>";
		SWFNode += this.getParamHTML(false);
		SWFNode += this.getVariableHTML(false);
		SWFNode += "</object>";

		return SWFNode;
	},

	/**
	 * getParamHTML
	 *
	 * Returns the params as HTML string
	 *
	 * @since initial
	 * @param boolean embed
	 * @return string
	 **/
	getParamHTML: function(embed) {
		paramHTML = "";

		params = this.getParams();
		for(var property in params) {
			if (embed) {
				paramHTML += property + "='" + params[property] + "' ";
			}
			else {
				paramHTML += '<param name="'+ property +'" value="'+ params[property] +'" />';
			}
		}

		return paramHTML;
	},

	/**
	 * getVariableHTML
	 *
	 * Returns the variables as HTML string
	 *
	 * @since initial
	 * @param boolean embed
	 * @return string
	 **/
	getVariableHTML: function(embed) {
		variableHTML = "";

		variablestring = this.getVariablePairs().join("&");
		if (variablestring.length > 0) {
			if (embed) {
				variableHTML += "flashvars='" + variablestring + "'";
			}
			else {
				variableHTML += "<param name='flashvars' value='" + variablestring + "'/>";
			}
		}

		return variableHTML;
	},

	/**
	 * write
	 *
	 * Writes the SWF HTML to the document
	 * Returns true on success
	 *
	 * @since initial
	 * @return boolean
	 **/
	write: function(element) {
		if ($(element) ) {
			$(element).innerHTML = this.getSWFHTML();
			return true;
		}
		return false;
	},

	/**
	 * methodExists
	 *
	 * Returns if the method exists on the flash element
	 *
	 * @since Mon Oct 22 2007
	 * @param string methodName
	 * @return boolean
	 **/
	methodExists: function(methodName) {
		element = $(this.getAttribute("swfname") );
		if (element) {
			if (typeof(element[methodName]) == "function") {
				return true;
			}
		}
		return false;
	},
	
	/**
	 * registerCallback
	 *
	 * Registers a callback function to override the default function
	 *
	 * @since Fri Nov 16 2007
	 * @param string methodName
	 * @param function callbackFunction
	 * @return void
	 **/
	registerCallback: function(methodName, callbackFunction) {
		this.registeredCallbacks[methodName] = {};
		this.registeredCallbacks[methodName]["callback"] = callbackFunction;
	},
	
	/**
	 * getCallbackByMethod
	 *
	 * Returns the registered callback function
	 *
	 * @since Fri Nov 16 2007
	 * @param string methodName
	 * @return object
	 **/
	getCallbackByMethod: function(methodName) {
		if (this.registeredCallbacks[methodName] != undefined) {
			return this.registeredCallbacks[methodName];
		}
		return false;
	},
	
	/**
	 * getContainer
	 *
	 * Returns the parent (container) div of the SWFObject
	 *
	 * @since Mon Nov 05 2007
	 * @return mixed
	 **/
	getContainer: function() {
		return swfloader.getDivByName(this.getAttribute("swfname") );
	},
	
	/**
	 * setDeeplink
	 *
	 * Sets the deeplink
	 *
	 * @since Thu Nov 06 2008
	 * @param string link
	 * @return void
	 **/
	setDeeplink: function(link) {
		if (this.methodExists("setDeeplink") ) {
			$(this.getAttribute("swfname") ).setDeeplink(link);
		}
		else {
			this.setDeeplink.bind(this).delay(0.05, link);
		}
	}
}

var swfloader = new SWFLoader();
/** 
 * flowplayer.js 3.0.3. The Flowplayer API
 * 
 * Copyright 2008 Flowplayer Oy
 * 
 * This file is part of Flowplayer.
 * 
 * Flowplayer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Flowplayer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Flowplayer.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Version: 3.0.3 - Wed Jan 07 2009 13:22:37 GMT-0000 (GMT+00:00)
 */
(function(){function log(args){console.log("$f.fireEvent",[].slice.call(args));}
function clone(obj){if(!obj||typeof obj!='object'){return obj;}
var temp=new obj.constructor();for(var key in obj){if(obj.hasOwnProperty(key)){temp[key]=clone(obj[key]);}}
return temp;}
function each(obj,fn){if(!obj){return;}
var name,i=0,length=obj.length;if(length===undefined){for(name in obj){if(fn.call(obj[name],name,obj[name])===false){break;}}}else{for(var value=obj[0];i<length&&fn.call(value,i,value)!==false;value=obj[++i]){}}
return obj;}
function el(id){return document.getElementById(id);}
function extend(to,from,skipFuncs){if(to&&from){each(from,function(name,value){if(!skipFuncs||typeof value!='function'){to[name]=value;}});}}
function select(query){var index=query.indexOf(".");if(index!=-1){var tag=query.substring(0,index)||"*";var klass=query.substring(index+1,query.length);var els=[];each(document.getElementsByTagName(tag),function(){if(this.className&&this.className.indexOf(klass)!=-1){els.push(this);}});return els;}}
function stopEvent(e){e=e||window.event;if(e.preventDefault){e.stopPropagation();e.preventDefault();}else{e.returnValue=false;e.cancelBubble=true;}
return false;}
function bind(to,evt,fn){to[evt]=to[evt]||[];to[evt].push(fn);}
function makeId(){return"_"+(""+Math.random()).substring(2,10);}
var Clip=function(json,index,player){var self=this;var cuepoints={};var listeners={};self.index=index;if(typeof json=='string'){json={url:json};}
extend(this,json,true);each(("Begin*,Start,Pause*,Resume*,Seek*,Stop*,Finish*,LastSecond,Update,BufferFull,BufferEmpty,BufferStop").split(","),function(){var evt="on"+this;if(evt.indexOf("*")!=-1){evt=evt.substring(0,evt.length-1);var before="onBefore"+evt.substring(2);self[before]=function(fn){bind(listeners,before,fn);return self;};}
self[evt]=function(fn){bind(listeners,evt,fn);return self;};if(index==-1){if(self[before]){player[before]=self[before];}
if(self[evt]){player[evt]=self[evt];}}});extend(this,{onCuepoint:function(points,fn){if(arguments.length==1){cuepoints.embedded=[null,points];return self;}
if(typeof points=='number'){points=[points];}
var fnId=makeId();cuepoints[fnId]=[points,fn];if(player.isLoaded()){player._api().fp_addCuepoints(points,index,fnId);}
return self;},update:function(json){extend(self,json);if(player.isLoaded()){player._api().fp_updateClip(json,index);}
var conf=player.getConfig();var clip=(index==-1)?conf.clip:conf.playlist[index];extend(clip,json,true);},_fireEvent:function(evt,arg1,arg2,target){if(evt=='onLoad'){each(cuepoints,function(key,val){if(val[0]){player._api().fp_addCuepoints(val[0],index,key);}});return false;}
if(index!=-1){target=self;}
if(evt=='onCuepoint'){var fn=cuepoints[arg1];if(fn){return fn[1].call(player,target,arg2);}}
if(evt=='onStart'||evt=='onUpdate'){extend(target,arg1);if(!target.duration){target.duration=arg1.metaData.duration;}else{target.fullDuration=arg1.metaData.duration;}}
var ret=true;each(listeners[evt],function(){ret=this.call(player,target,arg1,arg2);});return ret;}});if(json.onCuepoint){var arg=json.onCuepoint;self.onCuepoint.apply(self,typeof arg=='function'?[arg]:arg);delete json.onCuepoint;}
each(json,function(key,val){if(typeof val=='function'){bind(listeners,key,val);delete json[key];}});if(index==-1){player.onCuepoint=this.onCuepoint;}};var Plugin=function(name,json,player,fn){var listeners={};var self=this;var hasMethods=false;if(fn){extend(listeners,fn);}
each(json,function(key,val){if(typeof val=='function'){listeners[key]=val;delete json[key];}});extend(this,{animate:function(props,speed,fn){if(!props){return self;}
if(typeof speed=='function'){fn=speed;speed=500;}
if(typeof props=='string'){var key=props;props={};props[key]=speed;speed=500;}
if(fn){var fnId=makeId();listeners[fnId]=fn;}
if(speed===undefined){speed=500;}
json=player._api().fp_animate(name,props,speed,fnId);return self;},css:function(props,val){if(val!==undefined){var css={};css[props]=val;props=css;}
json=player._api().fp_css(name,props);extend(self,json);return self;},show:function(){this.display='block';player._api().fp_showPlugin(name);return self;},hide:function(){this.display='none';player._api().fp_hidePlugin(name);return self;},toggle:function(){this.display=player._api().fp_togglePlugin(name);return self;},fadeTo:function(o,speed,fn){if(typeof speed=='function'){fn=speed;speed=500;}
if(fn){var fnId=makeId();listeners[fnId]=fn;}
this.display=player._api().fp_fadeTo(name,o,speed,fnId);this.opacity=o;return self;},fadeIn:function(speed,fn){return self.fadeTo(1,speed,fn);},fadeOut:function(speed,fn){return self.fadeTo(0,speed,fn);},getName:function(){return name;},_fireEvent:function(evt,arg){if(evt=='onUpdate'){var json=player._api().fp_getPlugin(name);if(!json){return;}
extend(self,json);delete self.methods;if(!hasMethods){each(json.methods,function(){var method=""+this;self[method]=function(){var a=[].slice.call(arguments);var ret=player._api().fp_invoke(name,method,a);return ret=='undefined'?self:ret;};});hasMethods=true;}}
var fn=listeners[evt];if(fn){fn.call(self,arg);if(evt.substring(0,1)=="_"){delete listeners[evt];}}}});};function Player(wrapper,params,conf){var
self=this,api=null,html,commonClip,playlist=[],plugins={},listeners={},playerId,apiId,playerIndex,activeIndex,swfHeight,wrapperHeight;extend(self,{id:function(){return playerId;},isLoaded:function(){return(api!==null);},getParent:function(){return wrapper;},hide:function(all){if(all){wrapper.style.height="0px";}
if(api){api.style.height="0px";}
return self;},show:function(){wrapper.style.height=wrapperHeight+"px";if(api){api.style.height=swfHeight+"px";}
return self;},isHidden:function(){return api&&parseInt(api.style.height,10)===0;},load:function(fn){if(!api&&self._fireEvent("onBeforeLoad")!==false){each(players,function(){this.unload();});html=wrapper.innerHTML;flashembed(wrapper,params,{config:conf});if(fn){fn.cached=true;bind(listeners,"onLoad",fn);}}
return self;},unload:function(){try{if(api&&api.fp_isFullscreen()){}}catch(error){return;}
if(api&&html.replace(/\s/g,'')!==''&&!api.fp_isFullscreen()&&self._fireEvent("onBeforeUnload")!==false){api.fp_close();wrapper.innerHTML=html;self._fireEvent("onUnload");api=null;}
return self;},getClip:function(index){if(index===undefined){index=activeIndex;}
return playlist[index];},getCommonClip:function(){return commonClip;},getPlaylist:function(){return playlist;},getPlugin:function(name){var plugin=plugins[name];if(!plugin&&self.isLoaded()){var json=self._api().fp_getPlugin(name);if(json){plugin=new Plugin(name,json,self);plugins[name]=plugin;}}
return plugin;},getScreen:function(){return self.getPlugin("screen");},getControls:function(){return self.getPlugin("controls");},getConfig:function(copy){return copy?clone(conf):conf;},getFlashParams:function(){return params;},loadPlugin:function(name,url,props,fn){if(typeof props=='function'){fn=props;props={};}
var fnId=fn?makeId():"_";self._api().fp_loadPlugin(name,url,props,fnId);var arg={};arg[fnId]=fn;var p=new Plugin(name,null,self,arg);plugins[name]=p;return p;},getState:function(){return api?api.fp_getState():-1;},play:function(clip){function play(){if(clip!==undefined){self._api().fp_play(clip);}else{self._api().fp_play();}}
if(api){play();}else{self.load(function(){play();});}
return self;},getVersion:function(){var js="flowplayer.js 3.0.3";if(api){var ver=api.fp_getVersion();ver.push(js);return ver;}
return js;},_api:function(){if(!api){throw"Flowplayer "+self.id()+" not loaded. Try moving your call to player's onLoad event";}
return api;},_dump:function(){console.log(listeners);},setClip:function(clip){self.setPlaylist([clip]);},getIndex:function(){return playerIndex;}});each(("Click*,Load*,Unload*,Keypress*,Volume*,Mute*,Unmute*,PlaylistReplace,Fullscreen*,FullscreenExit,Error").split(","),function(){var name="on"+this;if(name.indexOf("*")!=-1){name=name.substring(0,name.length-1);var name2="onBefore"+name.substring(2);self[name2]=function(fn){bind(listeners,name2,fn);return self;};}
self[name]=function(fn){bind(listeners,name,fn);return self;};});each(("pause,resume,mute,unmute,stop,toggle,seek,getStatus,getVolume,setVolume,getTime,isPaused,isPlaying,startBuffering,stopBuffering,isFullscreen,reset,close,setPlaylist").split(","),function(){var name=this;self[name]=function(arg){if(!api){return self;}
if(typeof(api["fp_"+name])!="function"){api=document.getElementById(api.id);}
var ret=(arg===undefined)?api["fp_"+name]():api["fp_"+name](arg);return ret=='undefined'?self:ret;};});self._fireEvent=function(evt,arg0,arg1,arg2){if(conf.debug){log(arguments);}
if(!api&&evt=='onLoad'&&arg0=='player'){api=api||el(apiId);swfHeight=api.clientHeight;each(playlist,function(){this._fireEvent("onLoad");});each(plugins,function(name,p){p._fireEvent("onUpdate");});commonClip._fireEvent("onLoad");}
if(evt=='onLoad'&&arg0!='player'){return;}
if(evt=='onError'){if(typeof arg0=='string'||(typeof arg0=='number'&&typeof arg1=='number')){arg0=arg1;arg1=arg2;}}
if(evt=='onContextMenu'){each(conf.contextMenu[arg0],function(key,fn){fn.call(self);});return;}
if(evt=='onPluginEvent'){var name=arg0.name||arg0;var p=plugins[name];if(p){p._fireEvent("onUpdate",arg0);p._fireEvent(arg1);}
return;}
if(evt=='onPlaylistReplace'){playlist=[];var index=0;each(arg0,function(){playlist.push(new Clip(this,index++,self));});}
var ret=true;if(arg0===0||(arg0&&arg0>=0&&arg0<playlist.length)){activeIndex=arg0;var clip=playlist[arg0];if(clip){ret=clip._fireEvent(evt,arg1,arg2);}
if(!clip||ret!==false){ret=commonClip._fireEvent(evt,arg1,arg2,clip);}}
var i=0;each(listeners[evt],function(){ret=this.call(self,arg0,arg1);if(this.cached){listeners[evt].splice(i,1);}
if(ret===false){return false;}
i++;});return ret;};function init(){if($f(wrapper)){$f(wrapper).getParent().innerHTML="";playerIndex=$f(wrapper).getIndex();players[playerIndex]=self;}else{players.push(self);playerIndex=players.length-1;}
wrapperHeight=parseInt(wrapper.style.height,10)||wrapper.clientHeight;if(typeof params=='string'){params={src:params};}
playerId=wrapper.id||"fp"+makeId();apiId=params.id||playerId+"_api";params.id=apiId;conf.playerId=playerId;if(typeof conf=='string'){conf={clip:{url:conf}};}
conf.clip=conf.clip||{};if(wrapper.getAttribute("href",2)&&!conf.clip.url){conf.clip.url=wrapper.getAttribute("href",2);}
commonClip=new Clip(conf.clip,-1,self);conf.playlist=conf.playlist||[conf.clip];var index=0;each(conf.playlist,function(){var clip=this;if(typeof clip=='object'&&clip.length){clip=""+clip;}
if(!clip.url&&typeof clip=='string'){clip={url:clip};}
each(conf.clip,function(key,val){if(clip[key]===undefined&&typeof val!='function'){clip[key]=val;}});conf.playlist[index]=clip;clip=new Clip(clip,index,self);playlist.push(clip);index++;});each(conf,function(key,val){if(typeof val=='function'){bind(listeners,key,val);delete conf[key];}});each(conf.plugins,function(name,val){if(val){plugins[name]=new Plugin(name,val,self);}});if(!conf.plugins||conf.plugins.controls===undefined){plugins.controls=new Plugin("controls",null,self);}
params.bgcolor=params.bgcolor||"#000000";params.version=params.version||[9,0];params.expressInstall='http://www.flowplayer.org/swf/expressinstall.swf';function doClick(e){if(!self.isLoaded()&&self._fireEvent("onBeforeClick")!==false){self.load();}
return stopEvent(e);}
html=wrapper.innerHTML;if(html.replace(/\s/g,'')!==''){if(wrapper.addEventListener){wrapper.addEventListener("click",doClick,false);}else if(wrapper.attachEvent){wrapper.attachEvent("onclick",doClick);}}else{if(wrapper.addEventListener){wrapper.addEventListener("click",stopEvent,false);}
self.load();}}
if(typeof wrapper=='string'){flashembed.domReady(function(){var node=el(wrapper);if(!node){throw"Flowplayer cannot access element: "+wrapper;}else{wrapper=node;init();}});}else{init();}}
var players=[];function Iterator(arr){this.length=arr.length;this.each=function(fn){each(arr,fn);};this.size=function(){return arr.length;};}
window.flowplayer=window.$f=function(){var instance=null;var arg=arguments[0];if(!arguments.length){each(players,function(){if(this.isLoaded()){instance=this;return false;}});return instance||players[0];}
if(arguments.length==1){if(typeof arg=='number'){return players[arg];}else{if(arg=='*'){return new Iterator(players);}
each(players,function(){if(this.id()==arg.id||this.id()==arg||this.getParent()==arg){instance=this;return false;}});return instance;}}
if(arguments.length>1){var swf=arguments[1];var conf=(arguments.length==3)?arguments[2]:{};if(typeof arg=='string'){if(arg.indexOf(".")!=-1){var instances=[];each(select(arg),function(){instances.push(new Player(this,clone(swf),clone(conf)));});return new Iterator(instances);}else{var node=el(arg);return new Player(node!==null?node:arg,swf,conf);}}else if(arg){return new Player(arg,swf,conf);}}
return null;};extend(window.$f,{fireEvent:function(id,evt,a0,a1,a2){var p=$f(id);return p?p._fireEvent(evt,a0,a1,a2):null;},addPlugin:function(name,fn){Player.prototype[name]=fn;return $f;},each:each,extend:extend});if(typeof jQuery=='function'){jQuery.prototype.flowplayer=function(params,conf){if(!arguments.length||typeof arguments[0]=='number'){var arr=[];this.each(function(){var p=$f(this);if(p){arr.push(p);}});return arguments.length?arr[arguments[0]]:new Iterator(arr);}
return this.each(function(){$f(this,clone(params),conf?clone(conf):{});});};}})();(function(){var jQ=typeof jQuery=='function';function isDomReady(){if(domReady.done){return false;}
var d=document;if(d&&d.getElementsByTagName&&d.getElementById&&d.body){clearInterval(domReady.timer);domReady.timer=null;for(var i=0;i<domReady.ready.length;i++){domReady.ready[i].call();}
domReady.ready=null;domReady.done=true;}}
var domReady=jQ?jQuery:function(f){if(domReady.done){return f();}
if(domReady.timer){domReady.ready.push(f);}else{domReady.ready=[f];domReady.timer=setInterval(isDomReady,13);}};function extend(to,from){if(from){for(key in from){if(from.hasOwnProperty(key)){to[key]=from[key];}}}
return to;}
function concatVars(vars){var out="";for(var key in vars){if(vars[key]){out+=[key]+'='+asString(vars[key])+'&';}}
return out.substring(0,out.length-1);}
function asString(obj){switch(typeOf(obj)){case'string':obj=obj.replace(new RegExp('(["\\\\])','g'),'\\$1');obj=obj.replace(/^\s?(\d+)%/,"$1pct");return'"'+obj+'"';case'array':return'['+map(obj,function(el){return asString(el);}).join(',')+']';case'function':return'"function()"';case'object':var str=[];for(var prop in obj){if(obj.hasOwnProperty(prop)){str.push('"'+prop+'":'+asString(obj[prop]));}}
return'{'+str.join(',')+'}';}
return String(obj).replace(/\s/g," ").replace(/\'/g,"\"");}
function typeOf(obj){if(obj===null||obj===undefined){return false;}
var type=typeof obj;return(type=='object'&&obj.push)?'array':type;}
if(window.attachEvent){window.attachEvent("onbeforeunload",function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};});}
function map(arr,func){var newArr=[];for(var i in arr){if(arr.hasOwnProperty(i)){newArr[i]=func(arr[i]);}}
return newArr;}
function getEmbedCode(p,c){var html='<embed type="application/x-shockwave-flash" ';if(p.id){extend(p,{name:p.id});}
for(var key in p){if(p[key]!==null){html+=key+'="'+p[key]+'"\n\t';}}
if(c){html+='flashvars=\''+concatVars(c)+'\'';}
html+='/>';return html;}
function getObjectCode(p,c,embeddable){var html='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" ';html+='width="'+p.width+'" height="'+p.height+'"';if(!p.id&&document.all){p.id="_"+(""+Math.random()).substring(5);}
if(p.id){html+=' id="'+p.id+'"';}
html+='>';if(document.all){p.src+=((p.src.indexOf("?")!=-1?"&":"?")+Math.random());}
html+='\n\t<param name="movie" value="'+p.src+'" />';var e=extend({},p);e.id=e.width=e.height=e.src=null;for(var k in e){if(e[k]!==null){html+='\n\t<param name="'+k+'" value="'+e[k]+'" />';}}
if(c){html+='\n\t<param name="flashvars" value=\''+concatVars(c)+'\' />';}
if(embeddable){html+=getEmbedCode(p,c);}
html+="</object>";return html;}
function getFullHTML(p,c){return getObjectCode(p,c,true);}
function getHTML(p,c){var isNav=navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length;return(isNav)?getEmbedCode(p,c):getObjectCode(p,c);}
window.flashembed=function(root,userParams,flashvars){var params={src:'#',width:'100%',height:'100%',version:null,onFail:null,expressInstall:null,debug:false,allowfullscreen:true,allowscriptaccess:'always',quality:'high',type:'application/x-shockwave-flash',pluginspage:'http://www.adobe.com/go/getflashplayer'};if(typeof userParams=='string'){userParams={src:userParams};}
extend(params,userParams);var version=flashembed.getVersion();var required=params.version;var express=params.expressInstall;var debug=params.debug;if(typeof root=='string'){var el=document.getElementById(root);if(el){root=el;}else{domReady(function(){flashembed(root,userParams,flashvars);});return;}}
if(!root){return;}
if(!required||flashembed.isSupported(required)){params.onFail=params.version=params.expressInstall=params.debug=null;root.innerHTML=getHTML(params,flashvars);return root.firstChild;}else if(params.onFail){var ret=params.onFail.call(params,flashembed.getVersion(),flashvars);if(ret===true){root.innerHTML=ret;}}else if(required&&express&&flashembed.isSupported([6,65])){extend(params,{src:express});flashvars={MMredirectURL:location.href,MMplayerType:'PlugIn',MMdoctitle:document.title};root.innerHTML=getHTML(params,flashvars);}else{if(root.innerHTML.replace(/\s/g,'')!==''){}else{root.innerHTML="<h2>Flash version "+required+" or greater is required</h2>"+"<h3>"+
(version[0]>0?"Your version is "+version:"You have no flash plugin installed")+"</h3>"+"<p>Download latest version from <a href='"+params.pluginspage+"'>here</a></p>";}}
return root;};extend(window.flashembed,{getVersion:function(){var version=[0,0];if(navigator.plugins&&typeof navigator.plugins["Shockwave Flash"]=="object"){var _d=navigator.plugins["Shockwave Flash"].description;if(typeof _d!="undefined"){_d=_d.replace(/^.*\s+(\S+\s+\S+$)/,"$1");var _m=parseInt(_d.replace(/^(.*)\..*$/,"$1"),10);var _r=/r/.test(_d)?parseInt(_d.replace(/^.*r(.*)$/,"$1"),10):0;version=[_m,_r];}}else if(window.ActiveXObject){try{var _a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{_a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");version=[6,0];_a.AllowScriptAccess="always";}catch(ee){if(version[0]==6){return;}}
try{_a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(eee){}}
if(typeof _a=="object"){_d=_a.GetVariable("$version");if(typeof _d!="undefined"){_d=_d.replace(/^\S+\s+(.*)$/,"$1").split(",");version=[parseInt(_d[0],10),parseInt(_d[2],10)];}}}
return version;},isSupported:function(version){var now=flashembed.getVersion();var ret=(now[0]>version[0])||(now[0]==version[0]&&now[1]>=version[1]);return ret;},domReady:domReady,asString:asString,getHTML:getHTML,getFullHTML:getFullHTML});if(jQ){jQuery.prototype.flashembed=function(params,flashvars){return this.each(function(){flashembed(this,params,flashvars);});};}})();/**
 * WJTransition is the base class for transition API classes that allow you to transform some HTML element into another. The WJTransition is the base decorator for transitions.
 *
 * @since Mon Apr 20 2009
 * @author Ron Rademaker
 **/
var WJTransition = Class.create({
	/**
	 * initialize
	 *
	 * Creates the new WJTransition
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param WJElementTransition elementTransition
	 * @param object properties
	 * @return void
	 **/
	initialize: function() { 
	},
	
	/**
	 * init
	 *
	 * Real init (because it can't easily be done at create time)
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param WJElementTransition elementTransition
	 * @param object properties
	 * @return void
	 **/
	init: function(elementTransition, properties) {
		this.elementTransition = elementTransition;
		this.properties = this._getProperties(properties);
	},

	/**
	 * _getProperties
	 *
	 * Function stub to allow setting default in subclasses
	 *
	 * @since Mon Apr 20 2009
	 * @access protected
	 * @param object properties
	 * @return object
	 **/
	_getProperties: function(properties) {
		return properties;
	},

	/**
	 * transit
	 *
	 * Transits the elementTransition
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @return void
	 **/
	transit: function() {
		var from = this.elementTransition.getFromElement();
		var to = this.elementTransition.getToElement();
		if (from && to) {
			this._doTransition(from, to);
		}
		else {
			WJDebugger.log(WJDebugger.ERROR, "Transition not ready, to and from should both be set but at least one isn't, logging both, to first then from", to, from);
		}
		this.elementTransition.notifyDone();
	},

	/**
	 * _doTransition
	 *
	 * Performs the action transition from from to to
	 * Overwrite this function to create transitions
	 *
	 * @since Mon Apr 20 2009
	 * @access protected
	 * @param htmlelement from
	 * @param htmlelement to
	 * @return void
	 **/
	_doTransition: function(from, to) { 
		from.replace(to);
	}
});
/**
 * WJElementTransition is the base class for transitions between two html elements, this is the decoratable
 *
 * @since Mon Apr 20 2009
 * @author Ron Rademaker
 **/
var WJElementTransition = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJElementTransition 
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param object properties
	 * @return void
	 **/
	initialize: function(properties) {
		var properties = properties || {};
		this.properties = this._getProperties(properties);
	},
	
	/**
	 * _getProperties
	 *
	 * Function stub to allow setting default in subclasses
	 *
	 * @since Mon Apr 20 2009
	 * @access protected
	 * @param object properties
	 * @return object
	 **/
	_getProperties: function(properties) {
		return properties;
	},
	
	/**
	 * setFromElement
	 *
	 * Sets the element to transite from
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param htmlelement from
	 * @return void
	 **/
	setFromElement: function(from) {
		this.from = from;
	},

	/**
	 * setToElement
	 *
	 * Sets the element to transite to
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param htmlelement to
	 * @return void
	 **/
	setToElement: function(to) {
		this.to = to;
	},

	/**
	 * getFromElement
	 *
	 * Gets the element to transite from
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @return void
	 **/
	getFromElement: function(from) {
		return this.from
	},

	/**
	 * getToElement
	 *
	 * Gets the element to transite to
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @return void
	 **/
	getToElement: function(to) {
		return this.to;
	},

	/**
	 * notifyDone
	 *
	 * Callback for the transition to notify the elemen transition that the transition is complete. Makes the to the from to allow continuous transitions
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @return void
	 **/
	notifyDone: function() {
		this.from = this.to;
	}

});

var WJBackgroundImage = Class.create();

/**
 * WJBackgroundImage
 *
 * WJBackgroundImage is the javascript class for automated backgroundimages used by backgroundimage uft
 *
 * @since Fri May 08 2009
 * @author Reyo Stallenberg (reyo@connectholland.nl)
 **/
WJBackgroundImage.prototype = {
	/**
	 * Is the backgroundimage a string
	 *
	 * @since Fri May 8 2009
	 * @access protected
	 * @var boolean
	 **/
	_started: false,

	/**
	 * initialize
	 *
	 * Initialize a new WJBackgroundImage
	 *
	 * @since Fri May 8 2009
	 * @access public
	 * @param integer backgroundimage
	 * @param Element element
	 * @return void
	 **/
	initialize: function(backgroundimage, element) {
		if (Object.isString(backgroundimage) && !backgroundimage.strip().empty() ) {
			this.backgroundimage = backgroundimage;
			this.element = element || document.body;
			this.sizeelement = element || document.viewport;
			Element.extend(this.element);
			Event.observe(window, "resize", this.resizeImage.bindAsEventListener(this), false);
			this._started = true;
		}
		else {
			WJDebugger.log(WJDebugger.ERROR, "Trying to run", this, "without a source, you have to overwrite the backgroundImageImgSrc template in your UFT");
		}
	},

	/**
	 * toString
	 *
	 * The name of this script
	 *
	 * @since Mon May 11 2009
	 * @access public
	 * @return string
	 **/
	toString: function() {
		return "WJBackgroundImage";
	},

	/**
	 * resizeImage
	 *
	 * Resize the backgroudimage
	 *
	 * @since Fri May 8 2009
	 * @access public
	 * @param boolean resize_x
	 * @param boolean resize_y
	 * @return void
	 **/
	resizeImage: function(resize_x, resize_y) {
		if (this._started) {
			var sizeelement = this.sizeelement.getDimensions();
			// Round sizes to 25px to prevent too much images will be created and add a margin to the height for better fitting
			var height = (sizeelement.height - (sizeelement.height % 25) ) + 25 + 100;
			var width = (sizeelement.width - (sizeelement.width % 25) ) + 25;
			var image = new Image();
			Event.observe(image, "load", this.setBackgroundImage.bindAsEventListener(this, image) );
			Event.observe(image, "error", this.noBackgroundImage.bindAsEventListener(this, image) );
			if (resize_x && resize_y) {
				image.src = "/images/thumbs/__rw" + width + "h" + height + this.backgroundimage;
			}
			else if (resize_x && !resize_y) {
				image.src = "/images/thumbs/__lw" + width + "h" + (height + 3000) + this.backgroundimage;
			}
			else if (!resize_x && resize_y) {
				image.src = "/images/thumbs/__lw" + (width + 5000) + "h" + height + this.backgroundimage;
			}
			else {
				image.src = "/images/thumbs/" + this.backgroundimage;
			}
		}
	},

	/**
	 * setBackgroundImage
	 *
	 * Sets the background image
	 *
	 * @since Fri May 8 2009
	 * @access public
	 * @param integer event
	 * @param integer image
	 * @return void
	 **/
	setBackgroundImage: function(event, image) {
		WJDebugger.log(WJDebugger.INFO, "Adding a backgroundimage", image, "to", this.element);
		this.element.setStyle({backgroundImage: "url(\"" + image.src + "\")"});
	},

	/**
	 * noBackgroundImage
	 *
	 * Handles an error on the backgroundimage
	 *
	 * @since Fri May 8 2009
	 * @access public
	 * @param integer event
	 * @param integer image
	 * @return void
	 **/
	noBackgroundImage: function(event, image) {
		WJDebugger.log(WJDebugger.ERROR, "Trying to add a backgroundimage that doesn't exist", image, event);
	}
};/**
 * WJMediaTransition is the base class for transitions between two media elements, ie. images or videos
 *
 * @since Mon Apr 20 2009
 * @author Ron Rademaker
 **/
var WJMediaTransition = Class.create(WJElementTransition, {
	/**
	 * initialize
	 *
	 * Creates a new WJMediaTransition 
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param object properties
	 * @return void
	 **/
	initialize: function($super, properties) {
		$super(properties);
	},
	
	/**
	 * _getProperties
	 *
	 * Mixes in defaults for the media transition
	 *
	 * @since Mon Apr 20 2009
	 * @access protected
	 * @param object properties
	 * @return object
	 **/
	_getProperties: function(properties) {
		var defaults = {
			width: 500,
			height: 500,
			background: "ffffff",
			backgroundResize: true,
			crop: false
		};

		for (var key in defaults) {
			var properties_keys = Object.keys(properties);
			if (properties_keys.include(key) ) {
				defaults[key] = properties[key];
			}
		}
		return defaults;
	},

	/**
	 * _getOperations
	 *
	 * Gets the mediamanager operations query for this transition's properties
	 *
	 * @since Mon Apr 20 2009
	 * @access protected
	 * @return string
	 **/
	_getOperations: function() {
		var sizeinfo = "w" + this.properties.width + "h" + this.properties.height;
		if (this.properties.backgroundResize) {
			return "__bc" + this.properties.background + sizeinfo;
		}
		else if (this.properties.crop) {
			return "__r" + sizeinfo + "/__c" + sizeinfo;
		}
		else {
			return "__l" + sizeinfo;
		}
	},

	/**
	 * setToImage
	 *
	 * Creates and sets a to html element for image
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param string image
	 * @return void
	 **/
	setToImage: function(image) {
		this.setToElement(new Element("img", {src: "/images/thumbs/" + this._getOperations() + image}));
	},
	
	/**
	 * setToVideo
	 *
	 * Creates a to html element for video
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param string video
	 * @return void
	 * @todo implement flowplayer
	 **/
	setToVideo: function(video) {
		this.setToElement(new Element("img", {src: "/images/thumbs/" + this._getOperations() + video + ".png"}));
	}
});

/**
 * WJTransitionAppear implements a fade / appear transition, ie. the element closes by fading away and the new one opens by fading in
 *
 * @since Wed Apr 22 2009
 * @author Ron Rademaker
 **/
var WJTransitionAppear = Class.create(WJTransition, {
	/**
	 * initialize
	 *
	 * Initializes the transition
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @return void
	 **/
	initialize: function() {},

	/**
	 * _doTransition
	 *
	 * Implements the transition from from to to
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param htmlelement from
	 * @param htmlelement to
	 * @return void
	 **/
	_doTransition: function(from, to) { 
		to.setStyle({"display": "none"});
		from.up().insert(to);
		from.up().setStyle({"height": from.getHeight() + "px"});
		Effect.Fade(from, {duration: 1});
		Effect.Appear.bind(Effect, to).delay(1);
		from.remove.bind(from).delay(1.5);
	}
});
/**
 * WJTransitionBlind implements a blinding transition, ie. the element closes by sliding its bottom towards its top and then the new element opens by starting to slide its bottom away from the top
 *
 * @since Wed Apr 22 2009
 * @author Ron Rademaker
 **/
var WJTransitionBlind = Class.create(WJTransition, {
	/**
	 * initialize
	 *
	 * Initializes the transition
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @return void
	 **/
	initialize: function() {},

	/**
	 * _doTransition
	 *
	 * Implements the transition from from to to
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param htmlelement from
	 * @param htmlelement to
	 * @return void
	 **/
	_doTransition: function(from, to) { 
		to.setStyle({"display": "none"});
		from.up().insert(to);
		Effect.BlindUp(from, {duration: 1});
		Effect.BlindDown.bind(Effect, to).delay(1);
		from.remove.bind(from).delay(1);
	}
});
