/**
 * @author Sergey Chikuyonok (gonarch@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 */

if(!Function.prototype.inheritFrom){
	Function.prototype.inheritFrom = function(BaseClass) { // inheritance's method

		var Inheritance = function() {};

		Inheritance.prototype = BaseClass.prototype;

		this.prototype = new Inheritance();
		this.prototype.constructor = this;
		this.baseConstructor = BaseClass;
		this.superClass = BaseClass.prototype;

	}
}

if(!Object.extend){
	Object.extend = function(destination, source) {
		for(var property in source){
			destination[property] = source[property];
		}
		return destination;
	}
}

if(!Object.copy){
	Object.copy=function(obj){
		var new_obj={};
		for(var a in obj){
			new_obj[a]=obj[a];
		}

		return new_obj;
	}
}

/**
 * Simple line drawing API
 * @constructor
 * @param {String, Element} [context] Point where attach drawing canvas
 */
function SimpleCanvas(context){
	if(context){
		if(typeof(context) == 'string')
			context=document.getElementById(context);
	}
	else{
		context=document.body;
	}

	this.setContainer(context);

	var renderes=[SimpleCanvas.Renderer.Canvas, SimpleCanvas.Renderer.VML];
	for(var i=0; i<renderes.length; i++){
		if(renderes[i].isSupported()){
			this.setRenderer(new renderes[i](context));
			break;
		}
	}

	this._history={};
}

SimpleCanvas.prototype={
	setRenderer: function(obj){
		this._renderer=obj;
	},

	getRenderer: function(){
		return this._renderer;
	},

	setContainer: function(obj){
		this._container=obj;
	},

	getContainer: function(){
		return this._container;
	},

	getDimensions: function(){
		return this.getRenderer().getDimensions();
	},

	/**
	 * Draw line
	 * @param {Object} from Line start point
	 * @param {Object} to Line end point
	 * @param {Object} [properties] Line properties
	 * @param {Boolean} [dont_add] Don't add line to history
	 */
	drawLine: function(from, to, properties, dont_add){
		var renderer=this.getRenderer();
		if(renderer){
			renderer.drawLine(from, to, properties);
			if(!dont_add){
				this._addToHistory('line', from, to, properties);
			}
		}
	},

	/**
	 * Draw curved line
	 * @param {Object} from Line start point
	 * @param {Object} to Line end point
	 * @param {Object} cp1 First control point
	 * @param {Object} cp2 Second control point
	 * @param {Object} [properties] Line properties
	 * @param {Boolean} [dont_add] Don't add line to history
	 */
	drawCurve: function(from, to, cp1, cp2, properties, dont_add){
		var renderer=this.getRenderer();
		if(renderer){
			renderer.drawCurve(from, to, cp1, cp2, properties);
			if(!dont_add){
				this._addToHistory('curve', from, to, cp1, cp2, properties);
			}
		}
	},

	/**
	 * Draw curved line
	 * @param {Object} from Line start point
	 * @param {Object} to Line end point
	 * @param {Object} cp Control point
	 * @param {Object} [properties] Line properties
	 * @param {Boolean} [dont_add] Don't add line to history
	 */
	drawQuadCurve: function(from, to, cp, properties, dont_add){
		var renderer=this.getRenderer();
		if(renderer){
			renderer.drawQuadCurve(from, to, cp, properties);
			if(!dont_add){
				this._addToHistory('quadcurve', from, to, cp, properties);
			}
		}
	},

	clear: function(){
		this._clearHistory();
		this._emptyCanvas();
	},

	repaint: function(){
		var renderer=this.getRenderer();
		if(renderer)
			renderer.reflow();
		this._redrawFromHistory();
	},

	_emptyCanvas: function(){
		var renderer=this.getRenderer();
		if(renderer)
			renderer.clear();
	},

	/**
	 * Add object to history for redrawing on reflow
	 * @param {String} item_type History item type (only 'line' supported)
	 * @param {Object} ... Object parameters needed to redraw
	 */
	_addToHistory: function(item_type){
		if(!this._history[item_type]){
			this._history[item_type]=[];
		}
		var params=[].slice.call(arguments, 1);

		this._history[item_type].push(params);
		//console.log(this._history);
	},

	_clearHistory: function(){
		this._history=[];
	},

	/**
	 * Returns history items of specified type
	 * @param {String} item_type History item type (only 'line' supported)
	 * @return {Array}
	 */
	_getHistoryByType: function(item_type){
		if(this._history && this._history[item_type] && this._history[item_type].length)
			return this._history[item_type];
		else
			return null;
	},

	_redrawFromHistory: function(){
		this._emptyCanvas();
		var lines=this._getHistoryByType('line');
		if(lines){
			var line;
			for(var i=0, il=lines.length; i<il; i++){
				line=lines[i];
				this.drawLine(line[0], line[1], line[2], true);
			}
		}

		var curves=this._getHistoryByType('curve');
		if(curves){
			var curve;
			for(var i=0, il=curves.length; i<il; i++){
				curve=curves[i];
				this.drawCurve(curve[0], curve[1], curve[2], curve[3], curve[4], true);
			}
		}

		curves=this._getHistoryByType('quadcurve');
		if(curves){
			var curve;
			for(var i=0, il=curves.length; i<il; i++){
				curve=curves[i];
				this.drawCurve(curve[0], curve[1], curve[2], curve[3], true);
			}
		}
	}
};

SimpleCanvas.Renderer=function(context){
	this.setContext(context);
	this.setDefaultStrokeStyle({
		color: '#000000',
		width: 1
	});
};

SimpleCanvas.Renderer.prototype={
	width: 0,
	height: 0,

	/**
	 * Set context element where to draw
	 * @param {Element} context
	 */
	setContext: function(context){
		this._context=context;
	},

	/**
	 * Get context element
	 * @return {Element}
	 */
	getContext: function(){
		return this._context;
	},

	setDefaultStrokeStyle: function(style){
		this._strokeStyle=style;
	},

	getDefaultStrokeStyle: function(){
		return this._strokeStyle;
	},

	reflow: function(){
		this.updateDimensions();
	},

	getDimensions: function(){
		return {width: this.width, height: this.height};
	},

	updateDimensions: function(){
		var ctx = this.getContext();
		this.width = ctx.offsetWidth;
		this.height = ctx.offsetHeight;
		return this.getDimensions();
	},

	vx: function(point){
		var dims = this.getDimensions();
	    var c = {x:0, y:0};

	    var re_unit = /([\d\.]+)(\S*)/g;
	    var props = ['x', 'y'], prop;
	    var dim_props = ['width', 'height'];
		var m, unit;
	    for(var i = 0; i < props.length; i++){
			/** @type {String} */
	        prop = props[i];

			var prop_abs = String(point[ prop ]);

			while( (m = re_unit.exec( point[ prop ] )) ){
				var abs_value = 0;
				switch(m[2]){
	                case '%':
	                    abs_value = parseFloat(m[1])/100 * dims[dim_props[i]];
	                    break;
	                default:
	                    abs_value = parseFloat(m[1]);
	            }

				prop_abs = prop_abs.replace((m[2]) ? m[0] : new RegExp('(?:^|\\b)'+m[0]+'(?:$|\\b)'), abs_value);
			}
			c[prop] = eval(prop_abs);
	    }
	    return c;
	}
};

/**
 * Draw using canvas
 * @extends {SimpleCanvas.Renderer}
 * @param {Element} context
 */
SimpleCanvas.Renderer.Canvas=function(context){
	SimpleCanvas.Renderer.Canvas.baseConstructor.call(this, context);
	var canvas=document.createElement('canvas');
	canvas.style.width='100%';
	canvas.style.height='100%';
	context.appendChild(canvas);
	canvas.setAttribute('width', canvas.offsetWidth);
	canvas.setAttribute('height', canvas.offsetHeight);
	this._setCanvas(canvas);
	this.updateDimensions();
};

SimpleCanvas.Renderer.Canvas.inheritFrom(SimpleCanvas.Renderer);

SimpleCanvas.Renderer.Canvas.isSupported=function(){
	var elem=document.createElement('canvas');
	return (elem.getContext);
};

/**
 * Set canvas element where to draw
 * @param {Object} context
 */
SimpleCanvas.Renderer.Canvas.prototype._setCanvas=function(canvas){
	this._canvas=canvas;
};

/**
 * Get canvas
 * @return {Object}
 */
SimpleCanvas.Renderer.Canvas.prototype._getCanvas=function(){
	return this._canvas.getContext('2d');
};

/**
 * Draw line
 * @param {Object} from Line start point
 * @param {Object} to Line end point
 * @param {Object} [properties] Line properties
 */
SimpleCanvas.Renderer.Canvas.prototype.drawLine=function(from, to, properties){
	var dims = this.getDimensions();

	var c=this._getCanvas();
	this._setStrokeStyle(properties);
	c.beginPath();

	from = this.vx(from);
	to = this.vx(to);

	c.moveTo(from.x, from.y);
	c.lineTo(to.x, to.y);
	c.stroke();
	c.closePath();
};

/**
 * Draw curved line
 * @param {Object} from Line start point
 * @param {Object} to Line end point
 * @param {Object} cp1 First control point
 * @param {Object} cp2 Second control point
 * @param {Object} [properties] Line properties
 */
SimpleCanvas.Renderer.Canvas.prototype.drawCurve=function(from, to, cp1, cp2, properties){
	var dims = this.getDimensions();

	var c=this._getCanvas();
	this._setStrokeStyle(properties);

	from = this.vx(from);
	to = this.vx(to);
	cp1 = this.vx(cp1);
	cp2 = this.vx(cp2);

	c.beginPath();
	c.moveTo(from.x, from.y);
	c.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, to.x, to.y);
	c.stroke();
	c.closePath();
};

/**
 * Draw quadratic curve
 * @param {Object} from Line start point
 * @param {Object} to Line end point
 * @param {Object} cp Control point
 * @param {Object} [properties] Line properties
 */
SimpleCanvas.Renderer.Canvas.prototype.drawQuadCurve=function(from, to, cp, properties){
	var dims = this.getDimensions();

	var c=this._getCanvas();
	this._setStrokeStyle(properties);

	from = this.vx(from);
	to = this.vx(to);
	cp1 = this.vx(cp1);

	c.beginPath();
	c.moveTo(this._makeValue(from.x, dims.width), this._makeValue(from.y, dims.height));
	c.quadraticCurveTo(
		this._makeValue(cp.x, dims.width),
		this._makeValue(cp.y, dims.height),
		this._makeValue(to.x, dims.width),
		this._makeValue(to.y, dims.height)
	);
	c.stroke();
	c.closePath();
};

SimpleCanvas.Renderer.Canvas.prototype.reflow=function(){
	var dims = this.updateDimensions();

	this._canvas.setAttribute('width', dims.width);
	this._canvas.setAttribute('height', dims.height);
};


SimpleCanvas.Renderer.Canvas.prototype._setStrokeStyle=function(properties){
	var style=Object.copy(this.getDefaultStrokeStyle()), c=this._getCanvas();
	if(properties)
		Object.extend(style, properties);
	c.strokeStyle=style.color;
	c.lineWidth=style.width;
};

SimpleCanvas.Renderer.Canvas.prototype.clear=function(){
	var cv=this._getCanvas();
	var dims = this.getDimensions();
	cv.clearRect(0, 0, dims.width, dims.height);
};

SimpleCanvas.Renderer.Canvas.isSupported=function(){
	var elem=document.createElement('canvas');
	return (elem.getContext);
};

/**
 * Draw using VML (supported by IE)
 * @extends {SimpleCanvas.Renderer}
 * @constructor
 * @param {Element} context
 */
SimpleCanvas.Renderer.VML=function(context){
	SimpleCanvas.Renderer.VML.baseConstructor.call(this, context);
	if (!document.namespaces["v"]) {
		document.namespaces.add("v", "urn:schemas-microsoft-com:vml");
      // setup default css
      var ss = document.createStyleSheet();
      ss.cssText = "v\\:* {behavior:url(#default#VML);}";
	}
};

SimpleCanvas.Renderer.VML.inheritFrom(SimpleCanvas.Renderer);

SimpleCanvas.Renderer.VML.isSupported=function(){
	return (document.body.runtimeStyle);
};

SimpleCanvas.Renderer.VML.prototype.drawLine=function(from, to, properties){
	var style=Object.copy(this.getDefaultStrokeStyle());

	var ctx = this.getContext();
	if(properties)
		Object.extend(style, properties);

	var dims = this.getDimensions();

	var line=document.createElement('v:line');

	from = this.vx(from);
	to = this.vx(to);

	line.setAttribute('from', from.x+', '+from.y);
	line.setAttribute('to', to.x+', '+to.y);
	line.setAttribute('strokecolor', style.color);
	line.setAttribute('strokeweight', style.width);

	ctx.appendChild(line);
};

SimpleCanvas.Renderer.VML.prototype.drawCurve=function(from, to, cp1, cp2, properties){
	var style=Object.copy(this.getDefaultStrokeStyle());
	var ctx = this.getContext();
	if(properties)
		Object.extend(style, properties);

	var dims = this.getDimensions();

	var line=document.createElement('v:curve');

	from = this.vx(from);
	to = this.vx(to);
	cp1 = this.vx(cp1);
	cp2 = this.vx(cp2);

	line.setAttribute('from', from.x+', '+from.y);
	line.setAttribute('to', to.x+', '+to.y);
	line.setAttribute('control1', cp1.x+', '+cp1.y);
	line.setAttribute('control2', cp2.x+', '+cp2.y);
	line.setAttribute('strokecolor', style.color);
	line.setAttribute('strokeweight', style.width);
	line.setAttribute('filled', false);

	ctx.appendChild(line);
};

SimpleCanvas.Renderer.VML.prototype.vx=function(point){
	var c = SimpleCanvas.Renderer.prototype.vx.call(this, point);
	c.x+='px';
	c.y+='px';
	return c;
};

SimpleCanvas.Renderer.VML.prototype.clear=function(){
	var c=this.getContext();
	var ch=c.childNodes;
	for(var i=ch.length - 1; i>=0; i--){
		ch[i].parentNode.removeChild(ch[i]);
	}
};
