dojo.provide("dojox.drawing.manager.Mouse");

dojox.drawing.manager.Mouse = dojox.drawing.util.oo.declare(
	// summary:
	//		Master object (instance) that tracks mouse
	//		events. A new instance is created for each
	//		Drawing object.
	// description:
	//		You could connect to any method or event in this
	//		class, but it is designed to have the object
	//		'registered'. All objects with the current event
	//		will be called directly.
	//
	//		Custom events are used often. In addition to
	//		standard events onDown, onUp, onDrag, etc, if
	//		a certain object is clicked upon (or dragged, etc),
	//		that object's drawingType will create the custom event,
	//		such as onAnchorDown, or onStencilDown.
	//
	function(/* Object */options){
		this.util = options.util;
		this.keys = options.keys;
		this.id = options.id || this.util.uid("mouse");
		this.currentNodeId = "";
		this.registered = {};
	},
	
	{
		// doublClickSpeed: Number
		//		Milliseconds between clicks to
		//		register as for onDoubleClick
		doublClickSpeed:400,
		
		
		// rightClickMenu: boolean
		//		If true, right clicks bubble up so that context menus
		//		can be attached to them or the default can be shown.
		//		Otherwise right click is interpreted the same as a left click.
		rightClickMenu: false,
		
		// private properties
		
		_lastx:0,
		_lasty:0,
		__reg:0,
		_downOnCanvas:false,
		
/*=====
CustomEventMethod: function(){
	// summary:
	//		The custom event method that an Object that has
	//		registered with manager.Mouse can receive.
	//		Can contain any or all of the following methods
	//		and they will be called as mouse events. All events
	//		will be sent a EventObject event object.
	//	NOTE:
	//		Events happen anywhere in the document unless
	//		otherwise noted.
	//
	//	onMove
	//		Fires on mousemove when mouse is up
	//	onDown
	//		Fires on mousedown *on the canvas*
	//	onDrag
	//		Fires on mousemove when mouse is down
	//	onUp
	//		Fires on mouseup, anywhere in the document
	//	onStencilDown
	//		Fired on mousedown on a Stencil
	//	onStencilDrag
	//		Fired when mouse moves and mose is down on a Stencil
	//	onStencilUp
	//		Fired on mouseup off of a Stencil
	//	on[Custom]Up|Down|Move
	//		Custom events can bet set and fired by setting a
	//		different drawingType on a Stencil, or by calling
	//		setEventMode(customEventName)
},
EventObject: function(){
	// summary:
	//		The custom event object that is sent to registered objects
	//		and their respective methods.
	//	NOTE: Most event objects are the same with the exception
	//		of the onDown events, which have fewer.
	//
	// All event properties included onDown:
	//
	//	id: String
	//		Id of the focused object
	//	pageX: Number
	//		The X coordinate of the mouse from the left side of
	//		the document.
	//	pageY: Number
	//		The Y coordinate of the mouse from the top of
	//		the document.
	//	x:	Number
	//		The X coordinate of the mouse from the left side
	//		of the canvas
	//	y:	Number
	//		The Y coordinate of the mouse from the top
	//		of the canvas
	//
	// These event properties are *not* in onDown:
	//
	//	last:	Object
	//		The x and y coordinates of the last mousemove
	//		relative to the canvas
	//	move: Object
	//		The x and y amounts the mouse moved since the last event
	//	orgX:	Number
	//		The left side of the canvas from the side of the document
	//	orgY:	Number
	//		The top of the canvas from the top of the document
	//	scroll: Object
	//		The 'top' and 'left' scroll amounts of the canvas.
	//	start:	Object
	//		The x and y coordinates of the mousedown event
	//	withinCanvas: Boolean
	//		Whether the event happened within the Canvas or not
	
},
=====*/
			
		init: function(/* HTMLNode*/node){
			//	summary:
			//		Internal. Initializes mouse.
			//
			this.container = node;
			this.setCanvas();
			var c;
			var _isDown = false;
			dojo.connect(this.container, "rightclick", this, function(evt){
				console.warn("RIGHTCLICK")
			});
			
			dojo.connect(document.body, "mousedown", this, function(evt){
				//evt.preventDefault();
				//dojo.stopEvent(evt);
			});
			
			dojo.connect(this.container, "mousedown", this, function(evt){
				this.down(evt);
				// Right click shouldn't trigger drag
				if(evt.button != dojo.mouseButtons.RIGHT){
					_isDown = true;
					c = dojo.connect(document, "mousemove", this, "drag");
				}
			});
			dojo.connect(document, "mouseup", this, function(evt){
				if(evt.button != dojo.mouseButtons.RIGHT){
					dojo.disconnect(c);
					_isDown = false;
				}
				this.up(evt);
			});
			dojo.connect(document, "mousemove", this, function(evt){
				if(!_isDown){
					this.move(evt);
				}
			});
			dojo.connect(this.keys, "onEsc", this, function(evt){
				this._dragged = false;
			});
		},
		
		setCanvas: function(){
			// summary:
			//		Internal. Sets canvas position
			var pos = dojo.coords(this.container.parentNode);
			this.origin = dojo.clone(pos);
		},
		
		scrollOffset: function(){
			// summary:
			// 	Gets scroll offset of canvas
			return {
				top:this.container.parentNode.scrollTop,
				left:this.container.parentNode.scrollLeft
			}; // Object
		},

		resize: function(width,height){
			if(this.origin){
				this.origin.w=width;
				this.origin.h=height;
			}
		},

		register: function(/* Object*/scope){
			// summary:
			//		All objects (Stencils) should register here if they
			//		use mouse events. When registering, the object will
			//		be called if it has that method.
			//	argument:
			//		The object to be called
			//	Returns: handle
			//		Keep the handle to be used for disconnection.
			// See: CustomEventMethod and EventObject
			//
			var handle = scope.id || "reg_"+(this.__reg++);
			if(!this.registered[handle]){ this.registered[handle] = scope; }
			return handle; // String
		},
		unregister: function(handle){
			// summary:
			// 		Disconnects object. Mouse events are no longer
			//		called for it.
			if(!this.registered[handle]){ return; }
			delete this.registered[handle];
		},
		
		_broadcastEvent:function(strEvt, obj){
			// summary:
			//		Fire events to all registered objects.
			//
			//console.log("mouse.broadcast:", strEvt, obj)
			for(var nm in this.registered){
				if(this.registered[nm][strEvt]) this.registered[nm][strEvt](obj);
			}
		},
		
		onDown: function(obj){
			// summary:
			// 		Create on[xx]Down event and send to broadcaster.
			//		Could be connected to.
			//console.info("onDown:", this.eventName("down"))
			this._broadcastEvent(this.eventName("down"), obj);
		},
		
		onDrag: function(obj){
			// summary:
			// 		Create on[xx]Drag event and send to broadcaster.
			//		Could be connected to.
			//
			var nm = this.eventName("drag");
			if(this._selected && nm == "onDrag"){
				nm = "onStencilDrag"
			}
			this._broadcastEvent(nm, obj);
		},
		
		onMove: function(obj){
			// summary:
			// 		Create onMove event and send to broadcaster.
			//		Could be connected to.
			//		Note: onMove never uses a custom event
			//		Note: onMove is currently not enabled in the app.
			//
			this._broadcastEvent("onMove", obj);
		},
		
		overName: function(obj,evt){
			var nm = obj.id.split(".");
			evt = evt.charAt(0).toUpperCase() + evt.substring(1);
			if(nm[0] == "dojox" && (dojox.drawing.defaults.clickable || !dojox.drawing.defaults.clickMode)){
				return "onStencil"+evt;
			}else{
				return "on"+evt;
			}
			
		},
		
		onOver: function(obj){
			// summary:
			//
			this._broadcastEvent(this.overName(obj,"over"), obj);
		},
		
		onOut: function(obj){
			// summary:
			//
			this._broadcastEvent(this.overName(obj,"out"), obj);
		},
		
		onUp: function(obj){
			// summary:
			// 		Create on[xx]Up event and send to broadcaster.
			//		Could be connected to.
			//
			// 	blocking first click-off (deselect), largely for TextBlock
			// 	TODO: should have param to make this optional?
			var nm = this.eventName("up");
			
			if(nm == "onStencilUp"){
				this._selected  = true;
			}else if(this._selected && nm == "onUp"){ //////////////////////////////////////////
				nm = "onStencilUp";
				this._selected = false;
			}
			
			console.info("Up Event:", this.id, nm, "id:", obj.id);
			this._broadcastEvent(nm, obj);
			
			// Silverlight double-click handled in Silverlight class
			if(dojox.gfx.renderer == "silverlight"){ return; }
			
			// Check Double Click
			// If a double click is detected, the onDoubleClick event fires,
			// but does not replace the normal event. They both fire.
			this._clickTime = new Date().getTime();
			if(this._lastClickTime){
				if(this._clickTime-this._lastClickTime<this.doublClickSpeed){
					var dnm = this.eventName("doubleClick");
					console.warn("DOUBLE CLICK", dnm, obj);
					this._broadcastEvent(dnm, obj);
				}else{
					//console.log("    slow:", this._clickTime-this._lastClickTime)
				}
			}
			this._lastClickTime = this._clickTime;
			
		},
		
		zoom: 1,
		setZoom: function(zoom){
			// summary:
			// 		Internal. Sets the mouse zoom percentage to
			//		that of the canvas
			this.zoom = 1/zoom;
		},
		
		setEventMode: function(mode){
			// summary:
			//		Sets the mouse mode s that custom events can be called.
			//		Also can 'disable' events by using a bogus mode:
			// 		|	mouse.setEventMode("DISABLED")
			//		(unless any object subscribes to this event,
			//		it is effectively disabled)
			//
			this.mode = mode ? "on" + mode.charAt(0).toUpperCase() + mode.substring(1) :  "";
		},
		
		eventName: function(name){
			// summary:
			//		Internal. Determine the event name
			//
			name = name.charAt(0).toUpperCase() + name.substring(1);
			if(this.mode){
				if(this.mode == "onPathEdit"){
					return "on"+name;
				}
				if(this.mode == "onUI"){
					//return "on"+name;
				}
				return this.mode + name;
			}else{
				//Allow a mode where stencils aren't clickable
				if(!dojox.drawing.defaults.clickable && dojox.drawing.defaults.clickMode){return "on"+name;}
				var dt = !this.drawingType || this.drawingType=="surface" || this.drawingType=="canvas" ? "" : this.drawingType;
				var t = !dt ? "" : dt.charAt(0).toUpperCase() + dt.substring(1);
				return "on"+t+name;
			}
		},
		
		up: function(evt){
			// summary:
			//		Internal. Create onUp event
			//
			this.onUp(this.create(evt));
		},
		
		down: function(evt){
			// summary:
			//		Internal. Create onDown event
			//
			this._downOnCanvas = true;
			var sc = this.scrollOffset();
			var dim = this._getXY(evt);
			this._lastpagex = dim.x;
			this._lastpagey = dim.y;
			var o = this.origin;
			var x = dim.x - o.x + sc.left;
			var y = dim.y - o.y + sc.top;
			
			var withinCanvas = x>=0 && y>=0 && x<=o.w && y<=o.h;
			x*= this.zoom;
			y*= this.zoom;
			
			o.startx = x;
			o.starty = y;
			this._lastx = x;
			this._lasty = y;
			
			this.drawingType = this.util.attr(evt, "drawingType") || "";
			var id = this._getId(evt);
			//console.log("DOWN:", this.id, id, withinCanvas);
			//console.log("this.drawingType:", this.drawingType);
			
			if(this.rightClickMenu && (evt.button == dojo.mouseButtons.RIGHT) && this.id == "mse"){
				// Allow event to bubble for right click, for menus
			}else{
				evt.preventDefault();
				dojo.stopEvent(evt);
			}
			this.onDown({
				mid:this.id,
				x:x,
				y:y,
				pageX:dim.x,
				pageY:dim.y,
				withinCanvas:withinCanvas,
				id:id
			});
			
		},
		over: function(obj){
			// summary:
			//		Internal.
			//
			this.onOver(obj);
		},
		out: function(obj){
			// summary:
			//		Internal.
			//
			this.onOut(obj);
		},
		move: function(evt){
			// summary:
			//		Internal.
			//
			var obj = this.create(evt);
			if(this.id=="MUI"){
				//console.log("obj.id:", obj.id, "was:", this.currentNodeId)
			}
			if(obj.id != this.currentNodeId){
				// TODO: I wonder if an ID is good enough
				//	that would avoid the mixin
				var outObj = {};
				for(var nm in obj){
					outObj[nm] = obj[nm];
				}
				outObj.id = this.currentNodeId;
				this.currentNodeId && this.out(outObj);
				obj.id && this.over(obj);
				this.currentNodeId = obj.id;
			}
			this.onMove(obj);
		},
		drag: function(evt){
			// summary:
			//		Internal. Create onDrag event
			this.onDrag(this.create(evt, true));
		},
		create: function(evt, squelchErrors){
			// summary:
			//		Internal. Create EventObject
			//
			var sc = this.scrollOffset();
			var dim = this._getXY(evt);
			
			var pagex = dim.x;
			var pagey = dim.y;
			
			var o = this.origin;
			var x = dim.x - o.x + sc.left;
			var y = dim.y - o.y + sc.top;

			var withinCanvas = x>=0 && y>=0 && x<=o.w && y<=o.h;
			x*= this.zoom;
			y*= this.zoom;
			
			var id = withinCanvas ? this._getId(evt, squelchErrors) : "";
			var ret = {
				mid:this.id,
				x:x,
				y:y,
				pageX:dim.x,
				pageY:dim.y,
				page:{
					x:dim.x,
					y:dim.y
				},
				orgX:o.x,
				orgY:o.y,
				last:{
					x: this._lastx,
					y: this._lasty
				},
				start:{
					x: this.origin.startx, //+ sc.left,
					y: this.origin.starty //+ sc.top
				},
				move:{
					x:pagex - this._lastpagex,
					y:pagey - this._lastpagey
				},
				scroll:sc,
				id:id,
				withinCanvas:withinCanvas
			};
			
			//console.warn("MSE LAST:", x-this._lastx, y-this._lasty)
			this._lastx = x;
			this._lasty = y;
			this._lastpagex = pagex;
			this._lastpagey = pagey;
			dojo.stopEvent(evt);
			return ret; //Object
		},
		_getId: function(evt, squelchErrors){
			// summary:
			//		Internal. Gets ID of focused node.
			return this.util.attr(evt, "id", null, squelchErrors); // String
		},
		_getXY: function(evt){
			// summary:
			//		Internal. Gets mouse coords to page.
			return {x:evt.pageX, y:evt.pageY}; // Object
		},
		
		setCursor: function(cursor,/* HTMLNode*/node){
			// summary:
			//		Sets the cursor for  a given node.  If no
			//		node is specified the containing node is used.
			if(!node){
				dojo.style(this.container, "cursor", cursor);
			}else{
				dojo.style(node, "cursor", cursor);
			}
		}
	}
);
