define(["dojo/_base/lang", "dojo/_base/declare", "dojo/dom-geometry", "dijit/registry", "dijit/_WidgetBase", "dojo/_base/html", 
		"dojo/_base/event", "dojox/gfx", "dojox/widget/_Invalidating","./ScaleBase", "dojox/gfx/matrix"],
	function(lang, // lang.extend
		declare, domGeom,  WidgetRegistry, _WidgetBase, html, event, gfx, _Invalidating, ScaleBase, matrix){
	return declare("dojox.dgauges.GaugeBase", [_WidgetBase, _Invalidating], {
		// summary: 
		//		This class is the base class for the circular and 
		//		rectangular (horizontal and vertical) gauge components.
		//		A gauge is a composition of elements added to the gauge using the addElement method.
		//		Elements are drawn from back to front in the same order they are added (using addElement).
		//		An elements can be: 
		//
		//		- A GFX drawing functions typically used for defining the style of the gauge.
		//		- A scale: CircularScale or RectangularScale depending on the type of gauge.
		//		- A text, using the TextIndicator
		//		Note: Indicator classes (value indicators, range indicators) are sub-elements of scales
		//		To create a custom gauge, subclass CircularGauge or RectangularGauge and
		//		configure its elements in the constructor.
		//		Ready to use, predefined gauges are available in dojox/dgauges/components/
		//		They are good examples of gauges built on top of the framework.
		
		_elements: null,
		_scales: null,
		_elementsIndex: null,
		_elementsRenderers: null,
		_gfxGroup: null,
		_mouseShield: null,
		_widgetBox: null,
		_node: null,

		// value: Number
		//		This property acts as a top-level wrapper for the value of the first indicator added to 
		//		its scale with the name "indicator", i.e. myScale.addIndicator("indicator", myIndicator).
		//		This property must be manipulated with get("value") and set("value", xxx).
		value: 0,
		_mainIndicator: null,
		
		_getValueAttr: function(){
			// summary:
			//		Internal method.
			// tags:
			//		private			
			if(this._mainIndicator){
				return this._mainIndicator.get("value");
			}else{
				this._setMainIndicator();
				if(this._mainIndicator){
					return this._mainIndicator.get("value");
				}
			}
			return this.value;
		},
		
		_setValueAttr: function(value){
			// summary:
			//		Internal method.
			// tags:
			//		private			
			this._set("value", value);
			if(this._mainIndicator){
				this._mainIndicator.set("value", value);
			}else{
				this._setMainIndicator();
				if(this._mainIndicator){
					this._mainIndicator.set("value", value);
				}
			}
		},
		
		_setMainIndicator: function(){
			// summary:
			//		Internal method.
			// tags:
			//		private	
			var indicator;
			for(var i=0; i<this._scales.length; i++){
				indicator = this._scales[i].getIndicator("indicator");
				if(indicator){
					this._mainIndicator = indicator;
				}
			}
		},
		
		_resetMainIndicator: function(){
			// summary:
			//		Internal method.
			// tags:
			//		private
			this._mainIndicator = null;
		},
		
		// font: Object
		//		The font of the gauge used by elements if not overridden.
		font: null,
		
		constructor: function(/* Object */args, /* DOMNode */ node){
			this.font = {
				family: "Helvetica",
				style: "normal",
				variant: "small-caps",
				weight: "bold",
				size: "10pt",
				color: "black"
			};
			this._elements = [];
			this._scales = [];
			this._elementsIndex = {};
			this._elementsRenderers = {};
			this._node = WidgetRegistry.byId(node);
			var box = html.getMarginBox(node);

			this.surface = gfx.createSurface(this._node, box.w || 1, box.h || 1);
			this._widgetBox = box;
			// _baseGroup is a workaround for http://bugs.dojotoolkit.org/ticket/14471
			this._baseGroup = this.surface.createGroup();
			this._mouseShield = this._baseGroup.createGroup();
			this._gfxGroup = this._baseGroup.createGroup();
		},
		
		_setCursor: function(type){
			// summary:
			//		Internal method.
			// tags:
			//		private
			if(this._node)
				this._node.style.cursor = type;
		},
		
		_computeBoundingBox: function(/* Object */element){
			// summary:
			//		Internal method.
			// tags:
			//		private
			return element ? element.getBoundingBox() : {x:0, y:0, width:0, height:0};
		},
		
		destroy: function(){
			// summary:
			//		Cleanup when a gauge is to be destroyed.
			this.surface.destroy();
			this.inherited(arguments);
		},

		resize: function(width, height){
			// summary:
			//		Resize the gauge to the dimensions of width and height.
			// description:
			//		Resize the gauge and its surface to the width and height dimensions.
			//		If a single argument of the form {w: value1, h: value2} is provided take that argument as the dimensions to use.
			//		Finally if no argument is provided, resize the surface to the marginBox of the gauge.
			// width: Number|Object?
			//		The new width of the gauge or the box definition.
			// height: Number?
			//		The new height of the gauge.
			// returns: dojox/dgauges/GaugeBase
			//		A reference to the current gauge for functional chaining.
			switch(arguments.length){
				// case 0, do not resize the div, just the surface
				case 1:
					// argument, override node box
					domGeom.setMarginBox(this._node, width);
					break;
				case 2:

					// argument, override node box
					domGeom.setMarginBox(this._node, {w: width, h: height});
					break;
			}
			// in all cases take back the computed box
			var box = domGeom.getMarginBox(this._node);
			this._widgetBox = box;
			var d = this.surface.getDimensions();
			if(d.width != box.w || d.height != box.h){
				// and set it on the surface
				this.surface.setDimensions(box.w, box.h);
				this._mouseShield.clear();
				this._mouseShield.createRect({x:0,y:0,width:box.w,height:box.h}).setFill([0, 0, 0, 0]);
				return this.invalidateRendering();
			}else{
				return this;
			}
		},
		
		addElement: function(/* String */name, /* Object */ element){
			// summary:
			//		Adds a element to the gauge.
			// name: String
			//		The name of the element to be added.
			// element: Object
			//		This parameter can be:
			//
			//		- A function which takes on argument of type GFX Group and return null or a
			//		GFX element retrievable using the getElementRenderer() method.
			//		- A Scale instance, i.e. CircularScale or RectangularScale.
			//		- A TextIndicator instance.
			if(this._elementsIndex[name] && this._elementsIndex[name] != element){
				this.removeElement(name);
			}
			
			if(lang.isFunction(element)){
				var gfxHolder = {};
				lang.mixin(gfxHolder, new _Invalidating());
				gfxHolder._name = name;
				gfxHolder._gfxGroup = this._gfxGroup.createGroup();
				gfxHolder.width = 0;
				gfxHolder.height = 0;
				gfxHolder._isGFX = true;
				gfxHolder.refreshRendering = function(){
					gfxHolder._gfxGroup.clear();
					return element(gfxHolder._gfxGroup, gfxHolder.width, gfxHolder.height);
				};
				this._elements.push(gfxHolder);
				this._elementsIndex[name] = gfxHolder;
			}else{
				element._name = name;
				element._gfxGroup = this._gfxGroup.createGroup();
				element._gauge = this;
				this._elements.push(element);
				this._elementsIndex[name] = element;
				
				if(element instanceof ScaleBase){
					this._scales.push(element);
				}
			}
			return this.invalidateRendering();
		},
		
		removeElement: function(/* String */name){
			// summary:
			//		Remove the element defined by name from the gauge.
			// name: String
			//		The name of the element as defined using addElement.
			// returns: Object
			//		A reference to the removed element.
			
			var element = this._elementsIndex[name];
			
			if(element){
				element._gfxGroup.removeShape();
				var idx = this._elements.indexOf(element);
				this._elements.splice(idx, 1);
				
				if(element instanceof ScaleBase){
					var idxs = this._scales.indexOf(element);
					this._scales.splice(idxs, 1);
					this._resetMainIndicator();
				}
				delete this._elementsIndex[name];
				delete this._elementsRenderers[name];
			}
			this.invalidateRendering();
			return element;
		},
		
		getElement: function(/* String */name){
			// summary:
			//		Get the given element, by name.
			// name: String
			//		The name of the element as defined using addElement.
			// returns: Object
			//		The element.
			return this._elementsIndex[name];
		},
		
		getElementRenderer: function(/* String */name){
			// summary:
			//		Get the given element renderer, by name.
			// name: String
			//		The name of the element as defined using addElement.
			// returns: Object
			//		The element renderer returned by the
			//		drawing function or by the refreshRendering() method
			//		in the case of framework classes.
			return this._elementsRenderers[name];
		},
		
		onStartEditing: function(event){
			// summary:
			//		Called when an interaction begins (keyboard, mouse or gesture).
			// event:
			//		On object with a unique member "indicator". This member is a reference to the modified indicator.
			// tags:
			//		callback
		},
		
		onEndEditing: function(event){
			// summary:
			//		Called when an interaction ends (keyboard, mouse or gesture).
			// event:
			//		On object with a unique member "indicator". This member is a reference to the modified indicator.
			// tags:
			//		callback
		}
	})
});
