define (
	['scripts/knockout-3.3.0','scripts/ACTComponent.js', "scripts/ACTUtils.js"],
	function(ko, ACTComponent, ACTUtils
) {
	function ComponentListComponentViewModel(params, element) {
		var self = this;

		self.base = new ACTComponent($(element), params);
		self.element = element;
		self.datas = ko.observableArray([]);

		self.addComponent = function(data, index) {
			data.gridPanel = self;
			self.componentIndexByName[data.Name] = index;
		}

		self.popComponent = function() {
			var comp = self.componentModelsByIndex[self.componentModelsByIndex.length - 1];
			var name = comp.Name;
			self.componentModelsByIndex.pop();

			delete self.componentModels[name];
			delete self.componentIndexByName[name];

			// cause the component will disapear and should no longer be count
			var isReady = comp.definition().ready();
			if (isReady)
				self.readyChildrenCount--;
		}

		self.base.onRefresh = function(obj) {
			params.ready(false);
			self.componentIndexByName = {};
			self.componentModelsByIndex = [];

			self.componentModels = {}

			self.readyChildrenCount = 0;
			self.maxHeight = null;
			self.maxWidth  = null;

			self.topMargin        = obj.TopMargin;
			self.verticalOffset   = obj.VerticalOffset;
			self.horizontalOffset = obj.HorizontalOffset;
			self.widthType        = obj.WidthType;
			self.widthValue       = obj.WidthValue;
			self.alignment        = obj.Alignment;
			self.maxColumnCount   = obj.MaxColumnCount;

			var datas = obj.Datas;
			for (var i = 0; i < datas.length; i++)
				self.addComponent(datas[i], i);
			self.datas(datas);

			if (datas.length == 0) {
				// following line will not be called if we have no children that became ready (registerComponent)
				params.ready(true);
			}
		}

		self.base.onMessage = function(name, obj) {
			if (name == "QKAddComponent") {
				params.ready(false);
				self.addComponent(obj, self.datas().length);
				self.datas.push(obj);
			} else if (name == "QKPopComponent") {
				self.popComponent();
				self.datas.pop();
			}
		}

		params.baseModel.lockFunctions.push(function(lockKey, noLockPaths) {
			for (var key in self.componentModels) {
				self.componentModels[key].lock(lockKey, noLockPaths);
			}
		});

		params.baseModel.unlockFunctions.push(function(lockKey) {
			for (var key in self.componentModels) {
				self.componentModels[key].unlock(lockKey);
			}
		});

		params.baseModel.showFunctions.push(function() {
			for (var key in self.componentModels)
				self.componentModels[key].callShowFunctions();
		});

		params.baseModel.hideFunctions.push(function() {
			for (var key in self.componentModels)
				self.componentModels[key].callHideFunctions();
		});

		// we change the default computeWidth/Height functions cause don't know our size before knowing the size of our components
		params.baseModel.setComputeHeightFunction(
			function() {
				if (self.maxHeight === null) {
					self.resizeChildren();
				}
				var res = self.maxHeight || 0;
				return res;
			}
		);
		params.baseModel.setComputeWidthFunction(
			function() {
				if (self.maxWidth === null) {
					self.resizeChildren();
				}

				var res = self.maxWidth || 0;
				return res;
			}
		);

		self.forceResize = false;
		params.baseModel.resizeFunctions[params.baseModel.resizeFunctions.length] = function(height,width, sizeChange) {
			if (sizeChange || self.forceResize) {
				self.forceResize = false;
				self.resizeChildren();
			}
		}

		self.makeColumn = function() {
			// do columns
			var previousMaxWidth = self.maxWidth;
			self.maxWidth = 0;
			var align = self.alignment;
			var widthIsFitToContent  = self.base.definition.WidthType  == "FitToContent";
			var heightIsFitToContent = self.base.definition.HeightType == "FitToContent";
			var neverJumpLine = align == "OneLineLeft" || align == "OneLineLeftYCenter" || widthIsFitToContent;
			var alwaysJumpLine = align == "VerticalCenter" || align == "VerticalLeft";
			var isLeft = align == "Left" || align == "CenterLeft" || align == "OneLineLeft" || align == "VerticalLeft" || align == "OneLineLeftYCenter" || align == "LeftYCenter";
			var isRight = align == "Right" || align == "RightYCenter";
			var isCentered = align == "Center" || align == "CenterLeft" || align == "VerticalCenter";
			var isYCentered = align == "OneLineLeftYCenter" || align == "LeftYCenter" || align == "RightYCenter";

			if (widthIsFitToContent && isCentered)
				console.warn("You asked for a centered list with a undefined width. Put a percentage or fixed size or change your alignment.")
			if (heightIsFitToContent && isYCentered)
				console.warn("You asked for an Y centered list with a undefined height. Put a percentage or fixed size or change your alignment.")

			var selfWidth = widthIsFitToContent ? 0 : params.baseModel.getW();
			if (self.widthType == "Fixed") {
				self.maxWidth = min(self.widthValue, selfWidth);
			} else if (self.widthType == "Percentage") {
				self.maxWidth = selfWidth * (self.widthValue * 0.01);  // percentage
			}
			var selfHeight = heightIsFitToContent ? 0 : params.baseModel.getH();

			var firstComp = self.componentModelsByIndex[0];
			var currentWidth = 0;
			var aboveComp = null;
			var biggestSameLevelComp = null;
			var notUsedWidths = {};  // to center components (if center alignment)
			var previousComponent = null;
			var columnCount = 0;
			self.aboveMaxHeight = 0;
			self.maxHeight = 0;
			for (var i = 0; i < self.componentModelsByIndex.length; i++) {
				var comp = self.componentModelsByIndex[i];
				var compDef = comp.definition();

				// searchBar will hide elements
				if (!compDef.Visible || $(comp.element).find(".content").css("display") == "none")
					continue;

				columnCount++;
				var compWidth = comp.getOuterDim().width;
				if (compWidth >= self.maxWidth && !neverJumpLine)
					console.warn("Component width is bigger thant the list (not correctly handle)");

				// left offset
				var newWidth = compWidth + currentWidth + self.horizontalOffset;
				if (currentWidth == 0 ||
					((newWidth > self.maxWidth || (self.maxColumnCount > 0 && columnCount >= self.maxColumnCount))
					&& !neverJumpLine || alwaysJumpLine))
				{
					// go to next line
					aboveComp = biggestSameLevelComp;
					biggestSameLevelComp = comp;
					currentWidth = compWidth;
					columnCount = 0;
					self.aboveMaxHeight = self.maxHeight;

					var leftOffset = 0;
					if (isRight) {
						leftOffset = selfWidth - compWidth;
					} else if (align == "Center" || align == "CenterLeft" || align == "VerticalCenter") {
						if (previousComponent != null) {
							// when we go next line we can now the residual width for the previous line
							var notUsedWidth = selfWidth - (previousComponent.definition().LeftOffset + previousComponent.getOuterDim().width);
							var aboveTopAttachment = (aboveComp && aboveComp.definition().TopAttachment) || "";

							notUsedWidths[aboveTopAttachment] = notUsedWidth;
						}
					}
					compDef.LeftOffset = leftOffset;
				} else {
					var leftOffset = 0;
					if (isLeft || align == "Center") {
						leftOffset = currentWidth + self.horizontalOffset;
					} else if (isRight) {
						leftOffset = selfWidth - newWidth;
					}
					compDef.LeftOffset = leftOffset;

					currentWidth = newWidth;
				}

				// top offset
				if (aboveComp != null) {
					compDef.TopAttachment = aboveComp.definition().Name;
					compDef.TopOffset = self.verticalOffset;
				} else {
					compDef.TopAttachment = "";
					compDef.TopOffset = self.topMargin;
				}

				// compute max height
				var height = self.aboveMaxHeight + compDef.TopOffset + comp.getOuterDim().height;
				if (height > self.maxHeight)
					self.maxHeight = height;

				// keep bigest same level comp
				if (biggestSameLevelComp == null ||
					comp.getOuterDim().height > biggestSameLevelComp.getOuterDim().height)
				{
					biggestSameLevelComp = comp;
				}

				previousComponent = comp;
			}

			// second pass to center elements
			var lastComponent = previousComponent;
			if (isCentered) {
				for (var i = 0; i < self.componentModelsByIndex.length; i++) {
					var comp = self.componentModelsByIndex[i];
					var compDef = comp.definition();
					var topAttachment = compDef.TopAttachment;

					var offsetValue = notUsedWidths[topAttachment];
					if (!offsetValue) {
						// last line (not compute because we never jump that line)
						if (lastComponent) {
							if (aboveComp && align == "CenterLeft") {
								offsetValue = notUsedWidths[aboveComp.definition().TopAttachment];
							} else {
								offsetValue = selfWidth - (lastComponent.definition().LeftOffset + lastComponent.getOuterDim().width);
							}
						}
					}
					if (offsetValue) {
						compDef.LeftOffset += offsetValue * 0.5;
					}
				}
			}

			if (isYCentered) {
				var $l = $(self.element).find(".listComponents");
				$l.css("top", Math.floor((selfHeight - self.maxHeight) * 0.5) + "px");
			}

			self.maxWidth = currentWidth;
			if (widthIsFitToContent && currentWidth != previousMaxWidth)
			{
				// cause the size isn't valid anymore and we have to notify the parent
				params.ready(false);
				params.ready(true);
			}

			// second pass to fix position when width is unknown and when we need that one to have a correct position
			if (widthIsFitToContent && !(isLeft))
			{
				for (var i = 0; i < self.componentModelsByIndex.length; i++) {
					var comp = self.componentModelsByIndex[i];
					var compDef = comp.definition();
					compDef.LeftOffset += self.maxWidth;
				}
			}
		}

		self.resizeChildren = function() {
			var $element = $(self.element);
			// remember scroll position to not having to go top at each child resize
			var scrollInfo = ACTUtils.GetScrollPosition($element);

			// to avoid blinking of scollbars
			var oldOverflow = $element.css("overflow");
			$element.css("overflow", "hidden");

			if (self.componentModels.length == 0) {
				return;
			}

			// compute their size before making columns
			for (var i = 0; i < self.componentModelsByIndex.length; i++) {
				var comp = self.componentModelsByIndex[i];
				var compDef = comp.definition();

				/*
					we reset offsets cause they can influence the dimention computation.
					(width sizes in percentages for exemple)
				*/
				var def = comp.definition();
				def.LeftOffset = def.RightOffset = def.TopOffset = def.BottomOffset = 0;

				ACTUtils.Log("resize component: " + compDef.Name, 1);
				comp.setDirty();
				comp.resize();
			}

			self.makeColumn();

			// resize them to update their position
			for (var i = 0; i < self.componentModelsByIndex.length; i++) {
				var comp = self.componentModelsByIndex[i];
				var compDef = comp.definition();

				ACTUtils.Log("compute position component: " + compDef.Name, 1);
				comp.setDirtyPosition();
				comp.resize();
			}

			// if we are in fit to content and the size change we need to resize all the layout
			if (params.HeightType == "FitToContent" && self.maxHeight != params.baseModel.h())
			{
				ACTUtils.Log("list " + params.Name + " need all resize", 1);
				params.baseModel.setDirty();
				params.baseModel.resize();
			}

			$element.css("overflow", oldOverflow);

			ACTUtils.SetScrollPosition($element, scrollInfo);
		}

		self.registerComponentModel = function(name, component) {
			var index = self.componentIndexByName[name];
			self.componentModelsByIndex[index] = component;

			self.componentModels[name] = component;
		}

		self.componentReady = function(component, newVal) {
			if (newVal) {
				self.readyChildrenCount++;
			} else {
				self.readyChildrenCount--;
				params.ready(false);
			}
			if (self.readyChildrenCount == self.datas().length) {
				var dataChangedCallbacks = params.baseModel.GetCollection("dataChangedCallbacks");
				for (var i = 0; i < dataChangedCallbacks.length; i++)
					dataChangedCallbacks[i]();

				// timeout 0 : to give time to knockout to update the values of the last ready component
				setTimeout(function() { self.forceResize = true; params.ready(true); }, 0);
			}

			if (self.readyChildrenCount > self.datas().length)
				console.warn("Too much component are ready. That sounldn't suppose to happen. The same component may exist multiple time that can come from Model.Id");
		}

		self.base.initialize();
	}

	return {
		createViewModel: function(params, componentInfo) {
			return new ComponentListComponentViewModel(params, componentInfo.element);
		}
	};
});
