define(['scripts/knockout-3.3.0','scripts/ACTComponent.js','scripts/joint.min','scripts/css', 'scripts/ACTUtils.js', 'mod/componentbase.js'], function(ko, ACTComponent, joint, css, ACTUtils, cbase) {
	css(['css/joint.min']);

	function WorkflowComponentViewModel(params, element) {
		var self = this;
		self.base = new ACTComponent($(element), params);
		self.element = element;
		self.baseModel = params.baseModel;
		self.Resize = params.Model.Resize;
		self.ShowPopupMenu = params.Model.ShowPopupMenu;
		self.PositionChanged = params.Model.PositionChanged;
		self.MouseEnter = params.Model.MouseEnter;
		self.MouseLeave = params.Model.MouseLeave;
		self.Click = params.Model.Click;
		self.paperElement = $(element).find("#paper");
		self.paper = null;
		self.graph = null;
		self.posChanged = ko.observable();
		self.posChanged.extend({ 
			notify: 'always',
			rateLimit: 300 
		});
		self.posChanged.subscribe(function(val) {
			var taskName = val[0];
			self.positionChanged(val[1], val[2], taskName);
		}.bind(self));
		self.lastWebObject = null;

		$(element).css("background-color", "white");

		self.base.onRefresh = function(obj) {
			self.lastWebObject = obj;
			if (self.baseModel.isVisible())
				self.UpdateGraph();
		}

		joint.shapes.html = {};
		joint.shapes.html.Component = joint.shapes.basic.Rect.extend({
			defaults: joint.util.deepSupplement({
				type: 'html.Component',
			}, joint.shapes.basic.Rect.prototype.defaults)
		});
		
		joint.shapes.html.ComponentView = joint.dia.ElementView.extend({

			template: [
				'<div style="position: absolute;" class="panelComponent htmlTask">',
				'<div class="content" data-bind="css: definition().ComponentName, component: {name: definition().ComponentName, params: definition()}, style: {top: \'\'+y()+\'px\', left: \'\'+x()+\'px\', width: \'\'+w()+\'px\', height: \'\'+h()+\'px\'}">',
				'</div>',
				'<div class="lockComponent" data-bind="style: {top: \'\'+y()+\'px\', left: \'\'+x()+\'px\', width: \'\'+w()+\'px\', height: \'\'+h()+\'px\'}"/>',
				'</div>'
			].join(''),

			initialize: function() {
				_.bindAll(this, 'updateComponent');
				joint.dia.ElementView.prototype.initialize.apply(this, arguments);

				this.$box = $(_.template(this.template)());
				// Prevent paper from handling pointerdown.
				this.$box.find('input,select').on('mousedown click', function(evt) { evt.stopPropagation(); });
				// Update the box position whenever the underlying model changes.
				this.model.on('change', this.updateComponent, this);
				// Remove the box when the model gets removed from the graph.
				this.model.on('remove', this.removeComponent, this);

				this.updateComponent();
				
				definition = this.model.get('component')
				definition.gridPanel = self;
				viewModel = cbase.createViewModel(definition,{element: this.$box.children()[0]});
				ko.applyBindings(viewModel, this.$box.children()[0]);				
				viewModel.$realElement = this.$box;
			},
			render: function() {
				joint.dia.ElementView.prototype.render.apply(this, arguments);
				this.paper.$el.prepend(this.$box);
				this.updateComponent();
				return this;
			},
			updateComponent: function() {
				// Set the position and dimension of the box so that it covers the JointJS element.
				var bbox = this.model.getBBox();
				this.$box.css({ width: bbox.width, height: bbox.height, left: bbox.x, top: bbox.y, transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)' });
			},
			removeComponent: function(evt) {
				this.$box.remove();
			}
		});
		
		self.componentModels = {}
		self.registerComponentModel = function(name, component) {
			self.componentModels[name] = component;
		}
		self.componentReady = function(component, newVal) {
			if (newVal) {
				// need last dom update
				setTimeout(function() { component.baseModel.resize(); }, 0);
			}
		}

		self.getTasksElement = function() {
			return $(element).find(".rotatable");
		}

		self.getTaskAttrs = function(taskInfo, styleName) {
			var style = self.taskStyles[styleName];
			return {
				rect: {
					fill: {
						type: 'linearGradient',
						stops: [
							{ offset: '0%', color: style.FillColorGradient1 },
							{ offset: '100%', color: style.FillColorGradient2 }
						],
						attrs: { x1: '0%', y1: '0%', x2: '0%', y2: '100%' }
					},
					stroke: style.StrokeColor,
					'stroke-width': style.StrokeWidth
				},
				text: {
					text: taskInfo.Caption,
					fill: style.TextColor,
					'ref-y': style.TextRefY,
					'font-size': ''+style.TextSize+'px',
				}
			};
		}

		self.taskEnterCalled = {};
		self.elementMouseLeaveHandler = function(e) {
			var taskName = $(this).attr("taskname");

			// to avoid multiple call
			if (!self.taskEnterCalled[taskName])
				return;
			delete self.taskEnterCalled[taskName];

			var task = self.tasksByName[taskName];
			var taskInfo = self.tasksInfosByName[taskName];
			if (taskInfo.HoverStyle)
				task.attr(self.getTaskAttrs(taskInfo, taskInfo.Style));

			self.mouseLeave(taskName);
		}

		self.elementMouseEnterHandler = function(e) {
			var taskName = $(this).attr("taskname");

			// to avoid multiple call
			if (self.taskEnterCalled[taskName])
				return;
			self.taskEnterCalled[taskName] = true;

			var task = self.tasksByName[taskName];
			var taskInfo = self.tasksInfosByName[taskName];
			if (taskInfo.HoverStyle)
				task.attr(self.getTaskAttrs(taskInfo, taskInfo.HoverStyle));

			self.mouseEnter(taskName);
		}
		
		self.CreateTaskOnGraph = function(graph, t, parent) {
			var style = self.taskStyles[t.Style];

			var taskProps = {
				position: { x: t.X, y: t.Y },
				size: { width: style.Width, height: style.Height },
				attrs: self.getTaskAttrs(t, t.Style),
			};

			var task = null;
			if (t.Component!=null) {
				taskProps.component = t.Component;
				task = new joint.shapes.html.Component(taskProps);
			} else {
				task = new joint.shapes.basic.Rect(taskProps);
			}

			task.prop("taskName",t.Name);
			task.on('change:position', function() { 
				var taskName = this.attributes.taskName;
				var val = [taskName, this.attributes.position.x, this.attributes.position.y, taskName];
				self.posChanged(val);
				self.paper.fitToContent({padding:20});
			})
			graph.addCell(task);
			if (parent!=null) {
				parent.embed(task);
			}

			self.tasksByName[t.Name] = task;
			self.tasksInfosByName[t.Name] = t;
			self.getTasksElement().last().attr("taskname", t.Name);
			
			for (var i=0;i<t.Children.length;i++) {
				var tc = t.Children[i];
				self.CreateTaskOnGraph(graph, tc, task);
			}
		}

		self.UpdateGraph = function() {
			if (self.lastWebObject == null)
				return; // update already done

			var obj = self.lastWebObject;
			self.lastWebObject = null;  // to not do the update multiple time

			self.tasksInfosByName = {}
			self.taskStyles = obj.TaskStyles;

			// create paper
			if (self.paper == null) {
				self.graph = new joint.dia.Graph;
				self.paper = new joint.dia.Paper({
					el: self.paperElement,
					height: self.paperElement.height(),
					width: self.paperElement.width(),
					gridSize: 10,
					model: self.graph
				});

				self.paper.on('blank:contextmenu', function(evt, x, y) {
					evt.preventDefault();
					var posX = x + self.paperElement.offset().left;
					var posY = y + self.paperElement.offset().top;
					self.showPopupMenu(posX, posY, x, y);
				});

				self.paper.on('cell:contextmenu', function(cell, evt, x, y) {
					evt.stopPropagation();
					evt.preventDefault();
					var posX = x + self.paperElement.offset().left;
					var posY = y + self.paperElement.offset().top;
					var taskName = cell.model.attributes.taskName;
					self.showPopupMenu(posX, posY, x, y, taskName);
				});

				self.paper.on('cell:pointerclick', function(cell, evt, x, y) {
					var posX = x + self.paperElement.offset().left;
					var posY = y + self.paperElement.offset().top;
					var taskName = cell.model.attributes.taskName;
					self.click(posX, posY, x, y, taskName);
				});
			}

			self.tasksByName = {}

			var graph = self.graph;
			graph.clear();

			var MyLink = joint.dia.Link.extend({
				markup: '<path class="connection"/><path class="marker-target"/><g class="labels" />'
			});

			for (var i=0;i<obj.Tasks.length;i++) {
				var t = obj.Tasks[i];
				self.CreateTaskOnGraph(graph, t, null);
			}

			for (var i=0;i<obj.Links.length;i++) {
				var l = obj.Links[i];
				var style = obj.TaskLinkStyles[l.Style];

				var source = self.tasksByName[l.In];
				var target = self.tasksByName[l.Out];
				if (!source || !target) {
					console.warn("Task unknown : " + (source ? l.Out : l.In));
					continue;
				}

				var link = new MyLink({
					source: { id: source.id },
					target: { id: target.id },
					router: { 
						name: style.RouterType.toLowerCase(),
						args: {
							startDirections: ACTUtils.StringArrayToLower(style.StartDirections),
							endDirections: ACTUtils.StringArrayToLower(style.EndDirections),
						}
					},
					connector: { name: 'normal' },
					attrs: {
						'.connection': {
							stroke: style.StrokeColor,
							'stroke-width': style.StrokeWidth,
						},
						'.marker-target': {
							fill: style.FillColor,
							d: 'M 6 0 L 0 3 L 6 6 z'
						}
					}
				});
				graph.addCell(link);
			}

			$tasks = self.getTasksElement();
			$tasks.off("mouseleave", self.elementMouseLeaveHandler);
			$tasks.on( "mouseleave", self.elementMouseLeaveHandler);
			$tasks.off("mouseenter", self.elementMouseEnterHandler);
			$tasks.on( "mouseenter", self.elementMouseEnterHandler);

			self.paper.fitToContent({padding:20});
		};

		self.baseModel.showFunctions.push(self.UpdateGraph);

		self.base.addCallbackX(self, "showPopupMenu", "ShowPopupMenu");
		self.base.addCallbackX(self, "positionChanged", "PositionChanged");
		self.base.addCallbackX(self, "mouseLeave", "MouseLeave");
		self.base.addCallbackX(self, "mouseEnter", "MouseEnter");
		self.base.addCallbackX(self, "click", "Click");

		self.base.initialize();
		params.ready(true);
	}

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

});

