define(["wc/dom/shed",
	"wc/ajax/triggerManager",
	"wc/ui/ajaxRegion", "wc/dom/initialise", "wc/dom/uid", "wc/dom/Widget",
	"wc/dom/classList", "wc/dom/convertDynamicContent", "wc/timers", "wc/dom/event", "wc/ui/getForm"],
function(shed, triggerManager, ajaxRegion, initialise, uid, Widget, classList, convertDynamicContent, timers, event, getForm) {
	"use strict";

	/**
	 * Provides a meachanism to load the content of containers on demand. This is used by the various AJAX enabled
	 * container components and LAME mode.
	 *
	 * @module
	 *
	 * @requires module:wc/dom/shed
	 * @requires module:wc/ajax/triggerManager
	 * @requires module:wc/ui/ajaxRegion
	 * @requires module:wc/dom/initialise
	 * @requires module:wc/dom/uid
	 * @requires module:wc/dom/Widget
	 * @requires module:wc/dom/classList
	 * @requires module:wc/dom/convertDynamicContent
	 * @requires module:wc/timers
	 * @requires module:wc/dom/event
	 * @requires module:wc/ui/getForm
	 *
	 * @todo document private members.
	 */
	var instance = new Container();

	/**
	 * @constructor
	 * @alias module:wc/ui/containerload~Container
	 * @private
	 */
	function Container() {
		var MAGIC_CLASS = "wc_magic",
			MAGIC_CONTAINER = new Widget("", MAGIC_CLASS),
			DYNAMIC_CONTAINER = MAGIC_CONTAINER.extend("wc_dynamic"),
			LAME_CONTAINER = new Widget("", "wc_lame"),
			GET_ATTRIB = "data-wc-get",
			inited;

		/**
		 * register an ajaxTrigger for the container when it is required.
		 * NOTE: ajax containers are self loading, so the "loads" property is itself.
		 *
		 * @function
		 * @private
		 * @param {Element} element The container for which we are registering a trigger.
		 * @param {Boolean} noPost Indicates the trigger does not need to post data (for eager load).
		 * @param {Boolean} get Use GET rather than POST.
		 */
		function registerTrigger(element, noPost, get) {
			var alias, getData,
				id = element.id || (element.id = uid()),
				oneShot = !DYNAMIC_CONTAINER.isOneOfMe(element),
				method = get ? "get" : "post",
				serialiseForm = !noPost;

			alias = element.getAttribute("data-wc-ajaxalias") || id;
			getData = element.getAttribute(GET_ATTRIB) || null;

			ajaxRegion.register({
				id: id,
				loads: [id],
				alias: alias,
				oneShot: oneShot,
				getData: getData,
				serialiseForm: serialiseForm,
				method: method,
				formRegion: alias
			});
		}

		/**
		 * Calls for ajax load of an element (container).
		 *
		 * The element will be automatically ajax enabled and set up its own trigger.
		 * It is up to the caller to ensure that it makes sense to load this element via AJAX.
		 *
		 * @function
		 * @private
		 * @param {Element} element The element to load
		 * @param {Boolean} eager true for eager load (does not need to post data)
		 * @param {boolean} get true to use GET rather than POST which is useful for eager panels.
		 */
		function requestLoad(element, eager, get) {
			var trigger, promise;
			if (element) {
				promise = new Promise(function(resolve, reject) {
					registerTrigger(element, eager, get);
					trigger = triggerManager.getTrigger(element.id);
					if (trigger) {
						/* If dynamic we MUST remove the trigger otherwise the panel becomes clickable and will
						 * reload itself on every click in the panel.
						 * If not dynamic (e.g. lazy) we MUST NOT remove the trigger, otherwise we lose the
						 * "one shot" state and a new trigger will be created which always has one shot left
						 * (so lazy effectively becomes dynamic).
						 */
						if (DYNAMIC_CONTAINER.isOneOfMe(element)) {
							triggerManager.removeTrigger(element.id);
						} else {
							/* if not dynamic remove the magic class, otherwise we will reload every time we open
							 * because we build a new trigger for each AJAX load just in case it is closed/hidden
							 * inside a dynamic ancestor
							 */
							classList.remove(element, MAGIC_CLASS);
						}
						// Fire in a timeout to ensure controls have set state for form serialisation
						timers.setTimeout(function() {
							trigger.fire().then(resolve, reject);
						}, 0);
					} else {
						reject();
					}
				});
			} else {
				promise = Promise.resolve();
			}
			return promise;
		}

		/**
		 * Helper for shedSubscriber.
		 * Deal with an element being expanded or shown.
		 *
		 * @param element The element being shown or expanded.
		 * @private
		 * @function
		 */
		function handleExpandOrShow(element) {
			var promise, form;
			if (element) {
				if (LAME_CONTAINER.isOneOfMe(element)) {
					if ((form = getForm(element, true))) {
						timers.setTimeout(event.fire, 0, form, "submit");
					}
				} else if (MAGIC_CONTAINER.isOneOfMe(element)) {
					promise = requestLoad(element, false, true);
				}
			}
			return promise || Promise.resolve();
		}

		/**
		 * Helper for shedSubscriber.
		 * Deal with an element being collapsed or hidden.
		 *
		 * @param element The element being collapsed or hidden.
		 * @param action The action, COLLAPSE or HIDE.
		 * @private
		 * @function
		 */
		function handleCollapseOrHide(element, action) {
			var el, widgets = [LAME_CONTAINER, DYNAMIC_CONTAINER];
			if (action === shed.actions.COLLAPSE) {
				el = Widget.isOneOfMe(element, widgets) ? element : (Widget.findDescendant(element, widgets, true) || element);
			} else {
				el = element;
			}
			if (el && Widget.isOneOfMe(el, widgets)) {
				convertDynamicContent(el);
			}
		}

		function init() {
			if (!inited) {
				inited = true;
				shed.subscribe(shed.actions.EXPAND, instance.onexpand);
				shed.subscribe(shed.actions.COLLAPSE, handleCollapseOrHide);
				shed.subscribe(shed.actions.SHOW, instance.onshow);
				shed.subscribe(shed.actions.HIDE, handleCollapseOrHide);
			}
		}


		/**
		 * Request that a container's content be loaded. Deliberately does not check MAGIC_CONTAINER.isOneOfMe
		 * so that anything can leverage this functionality regardless of whether it possesses the right "className"
		 * or not. In particular can be called by registration scripts built in XSLT phase.
		 *
		 * @function
		 * @private
		 * @param {String} id The id of the container to load.
		 */
		function requestEagerLoad(id) {
			var element = document.getElementById(id);
			if (element && !(element.innerHTML && element.innerHTML.trim())) {
				console.log("Eager loading: ", id);
				requestLoad(element, true, true);
			}
		}


		function processNow(idArr) {
			var id;
			init();
			while ((id = idArr.shift())) {
				requestEagerLoad(id);
			}
		}

		// we have to register the initialise because we have components which rely on the shed subscribers to
		//    load containers (collapsible, tab etc)
		initialise.addCallback(init);

		/**
		 * Load a container. This allows any other container component to actively call an ajax load.
		 * Used primarily by {@link module:wc/dom/dialog}.
		 * @function module:wc/ui/containerload.load
		 * @public
		 * @param {Element} element the container to load
		 * @param {boolean} eager true to load without serialising the whole form
		 * @param {boolean} get true to do a HTTP GET rather than POST
		 */
		this.load = function(element, eager, get) {
			init();
			requestLoad(element, eager, get);
		};

		/**
		 * To be called when a candidate element is made visible.
		 *
		 * @param {Element} element The element being made visisble.
		 */
		this.onshow = function(element) {
			return handleExpandOrShow(element);
		};

		/**
		 * To be called when a candidate element is expanded.
		 *
		 * @param {Element} element The element being expanded.
		 */
		this.onexpand = function(element) {
			var _widgets = [LAME_CONTAINER, MAGIC_CONTAINER],
				_element = Widget.isOneOfMe(element, _widgets) ? element : Widget.findDescendant(element, _widgets, true);
			return handleExpandOrShow(_element);
		};

		/**
		 * Set up the initialisation process for components in the current payload. This is called from the page
		 * setup (XSLT).
		 * @function module:wc/ui/containerload.register
		 * @public
		 * @param {String[]} idArr An array of ids of elements which need to have this functionality.
		 */
		this.register = function(idArr) {
			if (idArr && idArr.length) {
				initialise.addCallback(function() {
					processNow(idArr);
				});
			}
		};
	}

	return instance;
});
