dojo.provide("dojox.charting.Element");

dojo.require("dojox.gfx");

dojo.declare("dojox.charting.Element", null, {
	//	summary:
	//		A base class that is used to build other elements of a chart, such as
	//		a series.
	//	chart: dojox.charting.Chart2D
	//		The parent chart for this element.
	//	group: dojox.gfx.Group
	//		The visual GFX group representing this element.
	//	htmlElement: Array
	//		Any DOMNodes used as a part of this element (such as HTML-based labels).
	//	dirty: Boolean
	//		A flag indicating whether or not this element needs to be rendered.

	chart: null,
	group: null,
	htmlElements: null,
	dirty: true,

	constructor: function(chart){
		//	summary:
		//		Creates a new charting element.
		//	chart: dojox.charting.Chart2D
		//		The chart that this element belongs to.
		this.chart = chart;
		this.group = null;
		this.htmlElements = [];
		this.dirty = true;
		this.trailingSymbol = "...";
		this._events = [];
	},
	createGroup: function(creator){
		//	summary:
		//		Convenience function to create a new dojox.gfx.Group.
		//	creator: dojox.gfx.Surface?
		//		An optional surface in which to create this group.
		//	returns: dojox.charting.Element
		//		A reference to this object for functional chaining.
		if(!creator){ creator = this.chart.surface; }
		if(!this.group){
			this.group = creator.createGroup();
		}
		return this;	//	dojox.charting.Element
	},
	purgeGroup: function(){
		//	summary:
		//		Clear any elements out of our group, and destroy the group.
		//	returns: dojox.charting.Element
		//		A reference to this object for functional chaining.
		this.destroyHtmlElements();
		if(this.group){
			this.group.clear();
			this.group.removeShape();
			this.group = null;
		}
		this.dirty = true;
		if(this._events.length){
			dojo.forEach(this._events, function(item){
				item.shape.disconnect(item.handle);
			});
			this._events = [];
		}
		return this;	//	dojox.charting.Element
	},
	cleanGroup: function(creator){
		//	summary:
		//		Clean any elements (HTML or GFX-based) out of our group, and create a new one.
		//	creator: dojox.gfx.Surface?
		//		An optional surface to work with.
		//	returns: dojox.charting.Element
		//		A reference to this object for functional chaining.
		this.destroyHtmlElements();
		if(!creator){ creator = this.chart.surface; }
		if(this.group){
			this.group.clear();
		}else{
			this.group = creator.createGroup();
		}
		this.dirty = true;
		return this;	//	dojox.charting.Element
	},
	destroyHtmlElements: function(){
		//	summary:
		//		Destroy any DOMNodes that may have been created as a part of this element.
		if(this.htmlElements.length){
			dojo.forEach(this.htmlElements, dojo.destroy);
			this.htmlElements = [];
		}
	},
	destroy: function(){
		//	summary:
		//		API addition to conform to the rest of the Dojo Toolkit's standard.
		this.purgeGroup();
	},
	//text utilities
	getTextWidth: function(s, font){
		return dojox.gfx._base._getTextBox(s, {font: font}).w || 0;
	},
	getTextWithLimitLength: function(s, font, limitWidth, truncated){
		//	summary:
		//		Get the truncated string based on the limited width in px(dichotomy algorithm)
		//	s: String?
		//		candidate text.
		//	font: String?
		//		text's font style.
		//	limitWidth: Number?
		//		text limited width in px.
		//	truncated: Boolean?
		//		whether the input text(s) has already been truncated.
		//	returns: Object
		//		{
		//			text: processed text, maybe truncated or not
		//			truncated: whether text has been truncated
		//		}
		if (!s || s.length <= 0) {
			return {
				text: "",
				truncated: truncated || false
			};
		}
		if(!limitWidth || limitWidth <= 0){
			return {
				text: s,
				truncated: truncated || false
			};
		}
		var delta = 2,
			//golden section for dichotomy algorithm
			trucPercentage = 0.618,
			minStr = s.substring(0,1) + this.trailingSymbol,
			minWidth = this.getTextWidth(minStr, font);
		if (limitWidth <= minWidth) {
			return {
				text: minStr,
				truncated: true
			};
		}
		var width = this.getTextWidth(s, font);
		if(width <= limitWidth){
			return {
				text: s,
				truncated: truncated || false
			};
		}else{
			var begin = 0,
				end = s.length;
			while(begin < end){
				if(end - begin <= delta ){
					while (this.getTextWidth(s.substring(0, begin) + this.trailingSymbol, font) > limitWidth) {
						begin -= 1;
					}
					return {
						text: (s.substring(0,begin) + this.trailingSymbol),
						truncated: true
					};
				}
				var index = begin + Math.round((end - begin) * trucPercentage),
					widthIntercepted = this.getTextWidth(s.substring(0, index), font);
				if(widthIntercepted < limitWidth){
					begin = index;
					end = end;
				}else{
					begin = begin;
					end = index;
				}
			}
		}
	},
	getTextWithLimitCharCount: function(s, font, wcLimit, truncated){
		//	summary:
		//		Get the truncated string based on the limited character count(dichotomy algorithm)
		//	s: String?
		//		candidate text.
		//	font: String?
		//		text's font style.
		//	wcLimit: Number?
		//		text limited character count.
		//	truncated: Boolean?
		//		whether the input text(s) has already been truncated.
		//	returns: Object
		//		{
		//			text: processed text, maybe truncated or not
		//			truncated: whether text has been truncated
		//		}
		if (!s || s.length <= 0) {
			return {
				text: "",
				truncated: truncated || false
			};
		}
		if(!wcLimit || wcLimit <= 0 || s.length <= wcLimit){
			return {
				text: s,
				truncated: truncated || false
			};
		}
		return {
			text: s.substring(0, wcLimit) + this.trailingSymbol,
			truncated: true
		};
	},
	// fill utilities
	_plotFill: function(fill, dim, offsets){
		// process a plot-wide fill
		if(!fill || !fill.type || !fill.space){
			return fill;
		}
		var space = fill.space;
		switch(fill.type){
			case "linear":
				if(space === "plot" || space === "shapeX" || space === "shapeY"){
					// clone a fill so we can modify properly directly
					fill = dojox.gfx.makeParameters(dojox.gfx.defaultLinearGradient, fill);
					fill.space = space;
					// process dimensions
					if(space === "plot" || space === "shapeX"){
						// process Y
						var span = dim.height - offsets.t - offsets.b;
						fill.y1 = offsets.t + span * fill.y1 / 100;
						fill.y2 = offsets.t + span * fill.y2 / 100;
					}
					if(space === "plot" || space === "shapeY"){
						// process X
						var span = dim.width - offsets.l - offsets.r;
						fill.x1 = offsets.l + span * fill.x1 / 100;
						fill.x2 = offsets.l + span * fill.x2 / 100;
					}
				}
				break;
			case "radial":
				if(space === "plot"){
					// this one is used exclusively for scatter charts
					// clone a fill so we can modify properly directly
					fill = dojox.gfx.makeParameters(dojox.gfx.defaultRadialGradient, fill);
					fill.space = space;
					// process both dimensions
					var spanX = dim.width  - offsets.l - offsets.r,
						spanY = dim.height - offsets.t - offsets.b;
					fill.cx = offsets.l + spanX * fill.cx / 100;
					fill.cy = offsets.t + spanY * fill.cy / 100;
					fill.r  = fill.r * Math.sqrt(spanX * spanX + spanY * spanY) / 200;
				}
				break;
			case "pattern":
				if(space === "plot" || space === "shapeX" || space === "shapeY"){
					// clone a fill so we can modify properly directly
					fill = dojox.gfx.makeParameters(dojox.gfx.defaultPattern, fill);
					fill.space = space;
					// process dimensions
					if(space === "plot" || space === "shapeX"){
						// process Y
						var span = dim.height - offsets.t - offsets.b;
						fill.y = offsets.t + span * fill.y / 100;
						fill.height = span * fill.height / 100;
					}
					if(space === "plot" || space === "shapeY"){
						// process X
						var span = dim.width - offsets.l - offsets.r;
						fill.x = offsets.l + span * fill.x / 100;
						fill.width = span * fill.width / 100;
					}
				}
				break;
		}
		return fill;
	},
	_shapeFill: function(fill, bbox){
		// process shape-specific fill
		if(!fill || !fill.space){
			return fill;
		}
		var space = fill.space;
		switch(fill.type){
			case "linear":
				if(space === "shape" || space === "shapeX" || space === "shapeY"){
					// clone a fill so we can modify properly directly
					fill = dojox.gfx.makeParameters(dojox.gfx.defaultLinearGradient, fill);
					fill.space = space;
					// process dimensions
					if(space === "shape" || space === "shapeX"){
						// process X
						var span = bbox.width;
						fill.x1 = bbox.x + span * fill.x1 / 100;
						fill.x2 = bbox.x + span * fill.x2 / 100;
					}
					if(space === "shape" || space === "shapeY"){
						// process Y
						var span = bbox.height;
						fill.y1 = bbox.y + span * fill.y1 / 100;
						fill.y2 = bbox.y + span * fill.y2 / 100;
					}
				}
				break;
			case "radial":
				if(space === "shape"){
					// this one is used exclusively for bubble charts and pie charts
					// clone a fill so we can modify properly directly
					fill = dojox.gfx.makeParameters(dojox.gfx.defaultRadialGradient, fill);
					fill.space = space;
					// process both dimensions
					fill.cx = bbox.x + bbox.width  / 2;
					fill.cy = bbox.y + bbox.height / 2;
					fill.r  = fill.r * bbox.width  / 200;
				}
				break;
			case "pattern":
				if(space === "shape" || space === "shapeX" || space === "shapeY"){
					// clone a fill so we can modify properly directly
					fill = dojox.gfx.makeParameters(dojox.gfx.defaultPattern, fill);
					fill.space = space;
					// process dimensions
					if(space === "shape" || space === "shapeX"){
						// process X
						var span = bbox.width;
						fill.x = bbox.x + span * fill.x / 100;
						fill.width = span * fill.width / 100;
					}
					if(space === "shape" || space === "shapeY"){
						// process Y
						var span = bbox.height;
						fill.y = bbox.y + span * fill.y / 100;
						fill.height = span * fill.height / 100;
					}
				}
				break;
		}
		return fill;
	},
	_pseudoRadialFill: function(fill, center, radius, start, end){
		// process pseudo-radial fills
		if(!fill || fill.type !== "radial" || fill.space !== "shape"){
			return fill;
		}
		// clone and normalize fill
		var space = fill.space;
		fill = dojox.gfx.makeParameters(dojox.gfx.defaultRadialGradient, fill);
		fill.space = space;
		if(arguments.length < 4){
			// process both dimensions
			fill.cx = center.x;
			fill.cy = center.y;
			fill.r  = fill.r * radius / 100;
			return fill;
		}
		// convert to a linear gradient
		var angle = arguments.length < 5 ? start : (end + start) / 2;
		return {
			type: "linear",
			x1: center.x,
			y1: center.y,
			x2: center.x + fill.r * radius * Math.cos(angle) / 100,
			y2: center.y + fill.r * radius * Math.sin(angle) / 100,
			colors: fill.colors
		};
		return fill;
	}
});
