argos.require("utils");
argos.require("products");
argos.require("widgets.Carousel");
argos.require("widgets.TabbedArea");

argos.heroes = new (function() {
	var _module = this;
	var _heroes = new Array();

	var _helpers = {
		strToDetectedType : argos.utils.strToDetectedType,
		getType : argos.utils.getType
	}


	/* CLASSES
	***********/
	function Hero(container, json) {
		var cssClass = "heroes";
		var cssClassDefault = cssClass + "_default";

		// Helpers.
		var _attachStylesheet = argos.utils.attachStylesheet;

		// Initialise component.
		(function _init() {

			// Insert user content.
			if(json.content) {
				_addContent(json.content);	
			}
	
			// Manual rearrangement.
			if(json.layout.rearrange) {
				_rearrange(json.layout.rearrange);
			}
	
			// Apply sorting to products.
			if(json.layout.productOrder) {
				_productOrder(json.layout);
			} 


			// Apply CSS customisations.
			container.removeClass();
			container.addClass(cssClass);
			container.addClass(json.layout.className);
			if(json.layout.applyDefaultCss!=false) {
				container.addClass(cssClassDefault);
			}

			if(json.layout.css) {
				for(var i=0, custom=json.layout.css.replace(/\n|\r/g, "").split(","); i<custom.length; i++) {
					_attachStylesheet(custom[i], true);
				}
			}
	
			// Apply any specified (valid) effect.
			switch(String(json.effect.type).toLowerCase()) {
				case "carousel" :
						_setUpCarousel(json.effect);
						break;
				case "tabbed" :
						_setUpTabbedArea(json.effect);
						break;
				default: ; // nothing
			}
		}());


		// Private class functions.
		function _addContent(content) {
			var findScript = new RegExp(/<script.*?>.*?<\/script>/mig);
			var removedScript = "Inline script not allowed";

			if(content.html) container.prepend(content.html.replace(findScript, removedScript));
		}
	
		function _rearrange(order) {
			// Rearrange order of specified objects within the container.
			// @order = Array. First element is jQuery target, rest are integers depicting new order.
			var target = order.length > 1 ? order.shift() : null; 
			var rearrange = order.length > 0 ? order : new Array();
			var items = $(target, container);
			var parent = items.parent();
	
			if(items.length > 1) {
				for(var i=rearrange.length-1, x; i>=0; i--) {
					x = rearrange[i] - 1;
					try { 
						parent.prepend(items[x]); 
					}
					catch(e) {
						// Doubt get here because jQuery appears to handle Array index unknown errors.
						// console.debug("didn't find element rearrange[" + i + "]: ", e); 
					}
				} 
			}
		}
	
		function _setUpCarousel(config) {
			// Create carousel from Hero content.
			var	carousel = new argos.widgets.Carousel($(config.item || ".group", container), {"buttonTextPrevious" : "previous"});

			// Do we automate carousel?
			if(config.automate) {
				carousel.automate({
					"interval" : config.interval,
					"reverse" : config.reverse 
				});
			}
		}
	
		function _productOrder(config) {
			// Uses passed sortBy value to active correct sorting of products.
			var products = $(config.product || ".product", container);
			switch(config.productOrder) {
				case "titleA2Z" : _sort(products, argos.products.sorting.titleAToZ);
					break;
				case "priceL2H" : _sort(products, argos.products.sorting.priceLowToHigh);
					break;
				case "ratingH2L" : _sort(products, argos.products.sorting.ratingHighToLow);
					break;
				case "reverse" : _sort(products, argos.products.sorting.reversed);
					break;
				default: ; // nothing.
			}
		}
	
		function _sort(products, sortingFunction) {
			// Orders products (only within the same parent).
			var parent = products.parent().get(0);
			var groups = new Array();
			var i = 0;
	
			// First work out parents to arrange groups.
			groups.push(new Array);
			products.each(function(a){
				var product = $(this);
				var p = product.parent().get(0);
				if(p != parent) {
					parent = p;
					groups.push(new Array);
					i++;
				}
				groups[i].push(product);
			});

			// Loop over groups and sort products within by given sortingFunction.
			for(var i=0; i<groups.length; ++i) {
				// Introduced check because argos.products.sorting.reversed doesn't work in IE. Using .reverse is easier.
				(sortingFunction === argos.products.sorting.reversed) ? groups[i].reverse() : groups[i].sort(sortingFunction);

				// Rearrange DOM element order based on sorted array.
				for(var a=0; a<groups[i].length; ++a) {
					if(a + 1 != groups[i].length) {
						groups[i][a].after(groups[i][a + 1]);
					}
				}
			}	
		}
	
		function _setUpTabbedArea(config) {
			// Create tabbedArea from Hero content.
			var	tabbedArea = new argos.widgets.TabbedArea($(config.item, container));

			// Do we automate tabbing?
			if(config.automate == true) { // IE doesn't seem to like without '== true'.
				tabbedArea.setOptions({
					"automate" : true,
					"interval" : config.interval,
					"reverse" : config.reverse 
				});
				tabbedArea.automate();
			}
		}

		// Public class functions.
		this.getContainer = function() {
			return container;
		}

		this.getConfiguration = function() {
			return json;
		}
	}


	/* PRIVATE FUNCTIONS
	*********************/
	function _convertVariablesToJson(str) {
		// Examples of expected input lines: 
		// layout_automate=true
		// layout_rearrange=".group", 3, 1, 2
		// effect_type=carousel
		// Expected JSON output using above example...
		// {
		//   layout : {
		//     automate : true,
		//     rearrange : [".group", 3,1,2]
		//   },
		//   effect : {
		//     type : "carousel"
		//   }
		// }
		var json = {};
		var re = /^([layout_|effect_]\w+=.*$)/gim;
		var options = str.match(re);
		var s, sep, obj, opt, val, dtv;

		for(var i=0; i<options.length; i++) {
			s = options[i].split(/=(?!"|')/);
			sep = s[0].indexOf("_");
			obj = s[0].substring(0, sep);
			opt = s[0].substring(sep + 1);
			val = s[1];

			dtv = _helpers.strToDetectedType(val);
			dtv = (typeof dtv == "string") ? dtv.replace(/\n|\r/g, "") : dtv; // remove line breaks from strings. .

			if(!json[obj]) json[obj] = {};
			json[obj][opt] = dtv;
		}

		return json; 
	}


	/* PUBLIC FUNCTIONS
	********************/
	this.add = function(container, config) {
		_heroes.push(new Hero(container, config));
	}

	this.getAll = function() {
		return _heroes;
	}
	
	this.getConfigurationFromTextFile = function(filename) {
		var text = "";
		var failed = false;
		var ajax = $.ajax({
			async : false,
			cache : false,
			url : "/argosincludes/heroes/" + filename + ".txt",
			error : function() { 
				alert("Error: Failed to find specified Heroes configuration file \n" + this.url.replace(/(.*)\?.*/,"$1")); 
				failed = true; 
			}
		});
 
		return (failed) ? null : _convertVariablesToJson(ajax.responseText);		
	}

});



/*
 Keeping mbox specific stuff out of the argos.heroes code, above.
*****************************************************************/
argos.heroes.mboxes = new (function() {
	var _module = this;
	var _container = null;
	var _cssLoadingClass = "loading";
	var _mboxHeroes = new Array();


	/* CLASSES
	***********/
	function MboxHero(args) {
		var _t = this;
		var _requests = new Array();
		var _responses = new Array();
		var _id = (args.length > 0) ? args[0] : "";
		var _container = $("#" + _id);
		var _mboxAreaId = (new Date()).toString().replace(/[^\w]/g,"") + "-" + _id;
		var _whenReadyTries = 0;
		var _configuration = "";


		/* Initialise MboxHero.
		***********************/
		(function _init() {
			// Create mboxDefault elements and mboxCreate calls between two marker elements.
			if(_container.length > 0) {
				document.write("<div id=\"" + _mboxAreaId + "\" style=\"display:none;\">");
				for(var i=1; i<args.length; ++i) {
					// Add request to internal array.
					_requests.push(new MboxRequest(args[0], args[i]));
				}
				document.write("</div>");
			}
			else {
				alert("Could not find HTML element with ID attribute: " + args[0]); 
				return null; 
			}
		}());


		/* Private MboxHero functions.
		******************************/
		function _whenReady() {
			// Polling function to detect when mbox has really finished (see .ready function comment).
			// Allows you to setup a function to run when finally done.
			var delay = 500;
			var cutoff = 60000; // 1000 = 1 second.
			var configuration = null;
			var mboxImportedSelector = "div[id^=\"mboxImported\"]";
 
			_whenReadyTries++;

			if(_t.ready()) {
				configuration = argos.heroes.getConfigurationFromTextFile(_configuration);
				if(configuration) {
					// Set up target items for any effect (too complicated to expect user to create required jQuery selector).
					if(configuration.effect) {
						configuration.effect.item = mboxImportedSelector;
					}

					_replaceContainerContentWith(_mboxContent());
					argos.heroes.add(_t.getContainer(), configuration);
				}
				else {		
					alert("Hero component configuration aborted due to error.");
				}
			}
			/*else if(_whenReadyTries * delay > cutoff) {
				// TODO: Find out what we want to do here. Currently disabled until we know if wanted.
				// Currently showing a message (for Dev only), then putting Old content back in page.
				alert("mbox content has not completed within " + (cutoff / 1000) + "second cutoff limit.\nHero configuration script now aborting.\nTODO: Fall back to original content."); 
			}*/
			else {
				window.setTimeout(_whenReady, delay);
			}
		}

		function _replaceContainerContentWith(items) {
			// items is array of jQuery objects (elements).
			_container.empty();
			for(var i=0; i<items.length; ++i) {
				_container.append(items[i]);
			}
		}

		function _contentReady() {
			// Return true if detects iframe content (contains .group) loaded back into parent document.
			var content = _mboxContent();
			var count = 0;
			var idMarker = "mboxImported";
			var element; 

			for(var i=0; i<content.length; ++i) {
				element = $(content[i]);
				if(element.attr("id") && element.attr("id").indexOf(idMarker) >= 0 && element.find("span").contents().length > 0) {
					count++;
				}
			} 
			return  count == _requests.length;
		}

		function _mboxContent() {
			return $("#" + _mboxAreaId).contents(":not(script)");
		}


		/* Public MboxHero functions.
		*****************************/
		this.getId = function() {
			// Should be the HTML id of the container.
			return _id;
		}

		this.addResponse = function(id, config) {
			_responses.push(id);
			_configuration = config;
			if(_t.allResponded()) {
				_whenReady();
			}
		}

		this.ready = function() {
			// Need to check that iframes are loaded and content copied into parent document.
			return _t.allResponded() && _contentReady();
		}

		this.allResponded = function() {
			// Should be ready at this point because all responses registered. 
			// Unfortunately mbox puts response in iframe first, before copying 
			// back into actual document. This means responses are in but mbox 
			// might not be finished.
			return _requests.length == _responses.length;
		}

		this.getContainer = function() {
			return _container;
		}

		this.getRequests = function() {
			return _requests;
		}

		this.getRequest = function(id) {
			return _requests[id];
		}

		this.getFrameIds = function() {
			// Returns array of iframes ids created for each mboxCreate action on this MboxHero.
			var ids = new Array();
			for(var i in _requests) {
				ids.push(_requests[i].mbox.Db.iframeId);
			}
			return ids; 
		}
	}

	function MboxRequest(container, name) {
		var _t = this;
		var _activated = false;
		var _mbox = {};

		/* Initialise MboxRequest.
		**************************/
		(function _init() {
			if(!_activated) {
				_activated = true;
				_request(name);
			}
		}());

		/* Private MBoxRequest functions
		*********************************/
		function _request(id) {
			// mboxCreate is global in mbox.js function.
			// Replicate standard mbox DIV+request action.
			document.write("<div class=\"mboxDefault\"></div>");
			return mboxCreate(id); 
		}

		/* Public MBoxRequest functions
		********************************/
		this.getContainerId = function() {
			return container;
		}

		this.getName = function() {
			return name;
		}

		this.getMbox = function() {
			return _mbox;
		}
	}


	/* PUBLIC FUNCTIONS
	********************/
	this.add = function() {
		// Adds the .mboxDefault elements (to be replaced) to DOM, keeps track of mboxes
		// being created and calls mboxCreate required number of times.
		var mboxHero = null;
		var args = arguments;
 
		// arguments should be a list of target_container_html_id, mbox_value_1, mbox_value_2, ...
		if(args.length > 0) {
			_mboxHeroes.push(new MboxHero(args));
		}
		return mboxHero;
	}

	this.handleResponse = function(id, config) {
		// Searches the known _mboxHeroes to register response against the correct one.
		// Note: Have replaced frameIds method with request.name since frameIds relies
		// on getting value from potentially unstable mbox.Db.iframeId
		var found = false;
		//var frameIds = [];
		var requests = [];
		for(var mboxhero in _mboxHeroes) {
			//frameIds = _mboxHeroes[mboxhero].getFrameIds();
			requests = _mboxHeroes[mboxhero].getRequests();
			for(var request in requests) {
				if(id.indexOf(requests[request].getName()) >= 0) {
					_mboxHeroes[mboxhero].addResponse(id, config);
					break;
				}
			}
			if(found) break;
		}
	}

	this.getAll = function() {
		return _mboxHeroes;
	}


	/* PRIVATE FUNCTIONS
	*********************/

});


