define(['scripts/knockout-3.3.0','scripts/act.js','scripts/CallWebObject.js', 'scripts/ACTUtils.js'], function(ko, ACTSocket, InvokeCallback, ACTUtils) {

	return function ACTComponent($element, definition) {
		var self = this;
		self.definition = definition;
		self.Model = definition.Model;
		self.$element = $element;
		self.draggedElementIndex = -1;
		$element.attr("id", definition.Id);

		self.createDraggable = function(dragDropDataId, scope) {
			ACTUtils.CreateDraggable(self.$element, scope);
			self.$element.data("dataId", dragDropDataId);
		}

		self.createDroppable = function(scope) {
			ACTUtils.CreateDroppable(self.$element, scope);
		}

		self.createSortable = function(sortInfo) {
			var items = sortInfo.SortableItem;
			var connectWith = sortInfo.SortableConnectedWith;
			var clone = sortInfo.SortableClone;
			var axis = sortInfo.SortAxis;

			self.$element.sortable({
				appendTo: document.body,
				cursor: "move",
				items: items,
				scroll: true,
				helper: clone ? "clone" : "original",
				placeholder: "sortable-placeholder",
				cancel: "._DONT_SORT_",
				axis: axis,
				cursorAt: {left: 0, top: 0},
				opacity: 0.4,
				sort: function(event, ui) { ACTUtils.BetterMultiLevelSort(event, ui, self.$element); },
			});
			if (connectWith) {
				// we use setTimeout to let time for all the component to be initialized and created
				setTimeout(function() {
					self.$element.sortable("option", "connectWith", connectWith);
				}, 0);
			}
		}

		self.isSortable = function() {
			var instance = self.$element.sortable("instance");
			return typeof(instance) !== "undefined";
		}

		self.destroySortable = function() {
			if (self.isSortable())
				self.$element.sortable("destroy");
		}

		self.$element.on("dragstart", function(event, ui) {
			self.Model.DragAndDropInfo.dragstart();
		});

		self.$element.on("dragstop", function(event, ui) {
			self.Model.DragAndDropInfo.dragstop();
		});

		self.$element.on("drop", function(event, ui) {
			if (!ACTUtils.IsDroppable(self.$element))
				return;

			var dataId = ui.draggable.data("dataId");
			var x = ui.offset.left;
			var y = ui.offset.top;
			var absoluteX = x - self.$element.offset().left;
			var absoluteY = y - self.$element.offset().top;

			var args = null;
			if (self.onDrop) {
				args = self.onDrop(event, ui);
			}

			self.Model.DragAndDropInfo.dropped(dataId, absoluteX, absoluteY, x, y, args);
		});

		self.getIndexes = function($item) {
			var $currentItem = $item;
			var wantedSelector = self.Model.SortInfo.SortableItem;
			var indexes = [];
			while ($currentItem.length > 0 && self.$element[0] != $currentItem[0]) {
				var tmpIndex = $.inArray($currentItem[0], self.$element.find(wantedSelector).toArray());
				if (tmpIndex != -1) {
					// get index from direct parent
					var index = ACTUtils.GetElementIndex($currentItem.parent(), $currentItem, self.Model.SortInfo.SortableItem);
					indexes.push(index);
				}
				$currentItem = $currentItem.parent();
			}

			// put the indexes array in the intuitive order
			for (var i = 0; i < indexes.length * 0.5; i++) {
				var endIndex = indexes.length - 1 - i;
				var t = indexes[i];
				indexes[i] = indexes[endIndex];
				indexes[endIndex] = t;
			}

			return indexes.length == 0 ? null : indexes;
		}

		self.$element.on("sortstart", function(event, ui){
			if (!self.isSortable())
				return;

			self.draggedElementIndexes = self.getIndexes(ui.item);
			if (self.draggedElementIndexes)
				self.Model.SortInfo.sortstart(self.draggedElementIndexes);
		});

		self.$element.on("sortupdate", function(event, ui){
			if (!self.isSortable())
				return;

			var newIndexes = self.getIndexes(ui.item);
			if (self.draggedElementIndexes && newIndexes)
				self.Model.SortInfo.sortupdate(self.draggedElementIndexes, newIndexes);
			self.draggedElementIndexes = null;
		});

		self.$element.on("sortreceive", function(event, ui) {
			if (!self.isSortable())
				return;

			var prevIndexes = self.getIndexes(ui.item.prev());
			var nextIndexes = self.getIndexes(ui.item.next());
			if (prevIndexes || nextIndexes) {
				if (prevIndexes == null) {
					prevIndexes = nextIndexes.slice();
					prevIndexes[prevIndexes.length - 1] = nextIndexes[prevIndexes.length - 1] - 1;
				}

				if (nextIndexes == null) {
					nextIndexes = prevIndexes.slice();
					nextIndexes[nextIndexes.length - 1] = prevIndexes[nextIndexes.length - 1] + 1;
				}

				self.Model.SortInfo.sortreceive(prevIndexes, nextIndexes);
			}

			if (self.Model.SortInfo.SortCancelWhenReceive)
				ui.sender.sortable("cancel");
		});

		self.$element.on("sortstop", function(event, ui) {
			if (!self.isSortable())
				return;

			// if we don't do that we have a weird behavior when we drop the sortable in himself
			if (self.Model.SortInfo.SortCancelWhenReceive)
				self.$element.sortable("cancel");
		});

		self.onRefresh = function(obj) {};
		self.onMessage = function(name, data) { console.warn("Command not handle " + name); };
		self.onMessageInternal = function(obj) {
			console.log("On " + obj.Name + " ( " + self.definition.Name + " )");
			if (obj.Name == "Refresh")
			{
				ACTUtils.DestroyDraggable(self.$element);
				if (obj.Data.DragAndDropInfo.Draggable)
					self.createDraggable(obj.Data.DragAndDropInfo.DragDropDataId, obj.Data.DragAndDropInfo.DragScope);

				ACTUtils.DestroyDroppable(self.$element);
				if (obj.Data.DragAndDropInfo.Droppable)
					self.createDroppable(obj.Data.DragAndDropInfo.DropScope);

				self.destroySortable();
				if (obj.Data.SortInfo.Sortable)
					self.createSortable(obj.Data.SortInfo);

				self.onRefresh(obj.Data);
			}
			else
				self.onMessage(obj.Name, obj.Data);
		};

		self.addCallback = function(obj, name, fct, args) {
			if (obj[fct]==null) {
				obj[name] = function() {};
			} else {
				obj[name] = function() {
					InvokeCallback(self.Model.Id, this[fct], args);
				}.bind(obj);
			}
		};

		self.addCallback1 = function(obj, name, fct) {
			if (obj[fct]==null) {
				obj[name] = function() {};
			} else {
				obj[name] = function(arg1) {
					InvokeCallback(self.Model.Id, this[fct], [arg1]);
				}.bind(obj);
			}
		};

		self.addCallbackX = function(obj, name, fct) {
			if (obj[fct]==null) {
				obj[name] = function() {};
			} else {
				obj[name] = function() {
					var args = [];
					for (var i = 0; i < arguments.length; i++) {
						args.push(arguments[i]);
					}
					InvokeCallback(self.Model.Id, this[fct], args);
				}.bind(obj);
			}
		};

		self.initialize = function() {
			self.onRefresh(self.Model);
			ACTSocket.registerService(self.Model.ServiceName, self.onMessageInternal);
		};

		if (self.Model.DragAndDropInfo.Draggable)
			self.createDraggable(self.Model.DragAndDropInfo.DragDropDataId, self.Model.DragAndDropInfo.DragScope);

		if (self.Model.DragAndDropInfo.Droppable)
			self.createDroppable(self.Model.DragAndDropInfo.DropScope);

		if (self.Model.SortInfo.Sortable)
			self.createSortable(self.Model.SortInfo);

		if (self.Model.DragAndDropInfo.TreeNodeDropable)
			$element.addClass("_ACT_TREE_DROPPABLE_");


		self.addCallbackX(self.Model.DragAndDropInfo, "dropped", "Dropped");
		self.addCallbackX(self.Model.DragAndDropInfo, "dragstart", "DragStart");
		self.addCallbackX(self.Model.DragAndDropInfo, "dragstop", "DragStop");
		self.addCallbackX(self.Model.DragAndDropInfo, "treenodedrop", "TreeNodeDrop");
		self.addCallbackX(self.Model.SortInfo, "sortupdate", "SortUpdate");
		self.addCallbackX(self.Model.SortInfo, "sortstart", "SortStart");
		self.addCallbackX(self.Model.SortInfo, "sortreceive", "SortReceive");
	};

});
//self.addCallback(self.buttons()[key],"click","Clicked", []);
