/**
 * $Id$
 *
 * Project: DNI Advertising Javascript Module
 * Version: 0.0.1
 * Author: alucas
 *
 */


if (typeof(discovery) == "undefined") var discovery = {};
if (typeof(discovery.advertising) == "undefined") discovery.advertising = {};
if (typeof(discovery.PAGE) == "undefined") discovery.PAGE = {};
function augment(obj, args, func) { func.apply(obj, args) }





//#ADVERTISING MANAGER

augment({}, [discovery.advertising], function(module) {
	
	
	//EXTERNALS
	
	var utils = {
		each:						discovery.common.object.each,
		mergeObjects:				discovery.common.object.mergeObjects,
		getElementsByClassName:		discovery.common.dom.getElementsByClassName,
		addEventListener:			discovery.common.dom.addEventListener,
		buildUrl:					discovery.common.url.buildUrl,
		
		EventManager:				discovery.common.lib.EventManager
	}
	
	//PRIVATE VARS
	
	var _AD_FRAME_URL = "/resources/static/ads/esi/ad-frame.shtml";
	var _CONTAINER_ID = "dni-advertising-module";
	var _INNER_FRAME_CONTAINER_ID = "dni-advertising-frame-content";
	var _frames = {};
	var _ad_manager_instances = [];
	
	
	//PRIVATE METHODS
	
	/**
	 * @void
	 * Prepares a <code>Window</code> instance to work
	 * with an AdManager.
	 * 
	 * @param	{Window}
	 */
	var initialiseWindow = function(window) {

		var document = window.document;
		var container;
		
		if (!(container = document.getElementById(_CONTAINER_ID))) {
			
			var hiddenElement = document.createElement("div");
			hiddenElement.id = "dni-advertising-module";
			hiddenElement.style.display = "none";
			
			document.body.appendChild(hiddenElement);
			container = hiddenElement;
		}
		
		return container;
	}
	
	
	var importVariables = function(from, to, origProps) {
		
		from.document.getElementById = function(id) {
			return to.document.getElementById(id);
		}
		
		var attrs = {};
		for (var i in from) {
			try {
				var val = from[i];
				if ((typeof origProps[i] == "undefined")
						&&(typeof val !== "function")
						&&(!val.constructor)) {
					attrs[i] = val;
				}
			} catch(e) {}
		}
		utils.mergeObjects(to, [attrs]);
	}
	
	//PUBLIC INTERFACE
	
	
	/**
	 * Notifies relevant callbacks of a frame load event.
	 * This is called by an ad frame once it has loaded.
	 * 
	 * @param	{Window}		window
	 * @return	{HTMLElement}
	 */
	module.__registerFrameEvent = function(window, props) {
		
		var frame = _frames[window.document.location.hash.slice(1)];
		if (frame)
			frame.position.updateContent(props);
	}
	
	/**
	 * Returns the ready state of the advertising module.
	 * <code>AdManager</code> instances will not
	 * initialise ad positions while this is true.
	 * 
	 * @return	{Boolean}
	 */
	module.isReady = function() {
		return false;
	}

	
	/**
	 * Returns an <code>AdManager</code> instance,
	 * through which ad position can be managed.
	 * 
	 * @param	{Window}	window 
	 * @return	{AdManager}
	 */
	module.createManager = function(window) {
		
		
		//PRIVATE VARS
		
		var _usedFrameIds = [];
		
		
		//PRIVATE METHODS
		
		var newFrameId = function() {
			var returnValue = _usedFrameIds.length;
			_usedFrameIds.push(null);
			return returnValue;
		}

		//PUBLIC INTERFACE
		
		/**
		 * @constructor
		 * Manages ad positions associated with a
		 * <code>Window</code>.
		 * 
		 * @param	{Window}	window
		 */
		function AdManager(window) {
			
			var self = this;
			var positions = [];
			
			this.EVENT = utils.EventManager(this);
			this.EVENT.create("ready");
			
			var init = function() {
				
				var container = initialiseWindow(window);
				var document = window.document;
				
				self.__getHiddenElement = function() { return container }
				self.__getWindow = function() { return window }
				self.__getDocument = function() { return document }
			}
			
			this.reload = function() {
				utils.each(positions, function(){
					this.reload();
				})
			}
			
			if (!discovery.advertising.isReady())
				this.EVENT.attachListener("ready", init);
			else
				init();
			
			this.createPosition = function(name, config) {
				
				var exec = function() {
					var position = self.constructor
							.prototype.createPosition(config, self);
					if (position) {
						self[name] = position;
						positions.push(position);
					}
				}
				
				if (module.isReady()) {
					exec();						
				} else {
					this.EVENT.attachListener("ready", exec);
				}
			}
		}
		
		
		/**
		 * Creates an ad frame to be managed
		 * via an id which is returned.
		 * 
		 *  @return	{Integer}
		 */
		AdManager.prototype.__createFrame = function(position) {
			
			var id = newFrameId();
			var location = position.getAdUrl();
			
			var frameElem = this.__getDocument()	
					.createElement("iframe");
			frameElem.src = location + "#" + id;
			frameElem.width = "1000";
			frameElem.height = "1000";
			frame = {
				id: id, element: frameElem, position: position
			};
			
			_frames[id] = frame;
			this.__getHiddenElement().appendChild(frameElem);
			
			return frame;
		}
		
		
		/**
		 * Returns an <code>AdPosition</code> instance.
		 * 
		 * @param	{Object}		config
		 * @param	{AdManager}		manager
		 * @return	{AdPosition}
		 */
		AdManager.prototype.createPosition = function(config, manager) {
			
			
			/**
			 * @constructor
			 * Represents an ad position or group of ad
			 * elements and allows actions to be performed
			 * on them. 
			 * 
			 * @param	{Object}		config
			 * @param	{AdManager}		manager
			 */
			function AdPosition(config, manager) {
				
				this.getManager = function() { return manager }
				this.initCheck = function() {
					if (config.delayInit) {
						initAd.call(this);
						config.delayInit = false;
						return true;
					} else return false;
				}
				
				if (processConfig.call(this, config)) {
					if (!config.delayInit)
						this.initCheck();
					
				} else this.error = true;
			}
			
			/**
			 * Processes <code>AdPosition</code> config object
			 * 
			 * @param	{Object}	config
			 * @return	{Boolean}
			 */
			function processConfig (config) {
				
				var returnValue;
				var target = config.target;
				
				if (!target) {
					throw AdPositionTargetNotFound("Not Specified.");
					
				} else if (typeof(target) == "string") {
					
					target = this.getManager().__getDocument()
							.getElementById(target);
				}
					
				if ((!target)||(!target.tagName)) {
					returnValue = false;
					
				} else {
					returnValue = true;
					this.getConfig = function() { return config }
					this.getTarget = function() { return target }
					
					var url = utils.buildUrl(
							_AD_FRAME_URL, config.params);
					this.getAdUrl = function() { return url }
				}
				
				return returnValue;
			}
			
			/**
			 * Prepares and configures ad frame for requests.
			 * 
			 * @return	{Boolean}
			 */
			var initAd = function() {
				
				var frame = manager.__createFrame(this);
				var frameId = frame.id;
				var frameElement = frame.element;
				
				this.getFrameId = function() { return frameId }
				this.__getFrameDocument = function() {
					return frameElement.contentWindow.document
				}
				this.__getFrameWindow = function() {
					return frameElement.contentWindow;
				}
				
				return true;
			}
			
			/**
			 * @void
			 * Reloads position.
			 */
			AdPosition.prototype.reload =  function() {
				
				if (!this.initCheck())
					this.__getFrameDocument().location
							.reload(true);
			}
			
			/**
			 * @void
			 * Transfers the contents of an ad to
			 * the target element.
			 */
			AdPosition.prototype.updateContent = function(windowProps) {
				
				if (!this.initCheck()) {
					var target = this.getTarget();
					var contents = this.__getFrameDocument()
							.getElementById(_INNER_FRAME_CONTAINER_ID)
							.innerHTML
							.replace(removeBadElements, "");
					
					importVariables(this.__getFrameWindow(),
							this.getManager().__getWindow(), windowProps);
					
					target.innerHTML = contents;
				}
			}
			
			var removeBadElements = new RegExp('<(script|noscript)(.*\/>|' +
					'[\\s\\S]*?(?:[^"\'])\/(script|noscript))>', "g");
			
			
			//CLOSE AD POSITION FACTORY
			this.createPosition = function(config, manager) {
				var pos =  new AdPosition(config, manager);
				if (!pos.error) return pos;
				else return null
			}
			return this.createPosition(config, manager);
		}
		
		
		
		
		//CLOSE AD MANAGER FACTORY
		this.createAdManager = function(window) {
			
			var inst = new AdManager(window);
			_ad_manager_instances.push(inst);
			return inst;
		}
		return this.createAdManager(window);
	}
	
	
	//MODULE INITIALISATION
	
	utils.addEventListener.call(window, "ready", function(){
		
		module.isReady = function() { return true }
		
		utils.each(_ad_manager_instances, function(){
			this.EVENT.broadcast("ready");
		});
	});
	
	
	//EXCEPTIONS
	
	var AdManagerContainerError = function(id) {
		var err = Error("[AdManager] Container not found. Unable to create.");
		return err;
	}
	
	var AdPositionTargetNotFound = function(id) {
		var err = Error("[AdPosition] Target not found: " + id);
		return err;
	}
	
});