//call it before getContext() function using
function msie_canvas( obj ) {
	if( !window.clientInformation ) return;	//msie?
	if( !obj.tagName ) return;
	if( obj.tagName.toLowerCase() != 'canvas' ) return;
	if( obj.getContext ) return;

	CanvasEmulate( obj );
}

//common init
(function() {
	if( !window.clientInformation ) return;
	if( document.createElement( 'canvas' ).getContext ) return;

	var Z = 10;
	var Z2 = Z / 2;

	function getContext() {
		return this.context_ || (this.context_ = new CanvasRenderingContext2D_(this));
	}

	function CanvasEmulate_(el) {
		if( el.getContext ) return el;
		
		if( !document.namespaces['g_vml_'] )document.namespaces.add( 'g_vml_', 'urn:schemas-microsoft-com:vml', '#default#VML' );
		if( !document.namespaces['g_o_'] )	document.namespaces.add( 'g_o_', 'urn:schemas-microsoft-com:office:office', '#default#VML' );
		
		if( !document.styleSheets['ex_canvas_'] ) {
			with( document.createStyleSheet() ) {
				owningElement.id = 'ex_canvas_';
				cssText = '' +
				'canvas {' +
				'	display: inline-block;' +
				'	overflow:hidden;' +
				'	text-align: left;' +
				'	width: 300px;' +
				'	height: 150px' +
				'}' +
				'g_vml_\\:* { behavior: url( #default#VML ) }' +
				'g_o_\\:* { behavior: url( #default#VML ) }';
			}
		}
		
		el.getContext = getContext;
		el.innerHTML = '';
		el.attachEvent( 'onpropertychange', onPropertyChange );
		el.attachEvent( 'onresize', onResize );

		var attrs = el.attributes;
		if (attrs.width && attrs.width.specified) {
			el.style.width = attrs.width.nodeValue + 'px';
		}
		else {
			el.width = el.clientWidth;
		}
		if( attrs.height && attrs.height.specified ) {
			el.style.height = attrs.height.nodeValue + 'px';
		}
		else {
			el.height = el.clientHeight;
		}
		return el;
	}

	function onPropertyChange(e) {
		var el = e.srcElement;
		switch( e.propertyName ) {
			case 'width':
				el.style.width = el.attributes.width.nodeValue + 'px';
				el.getContext().clearRect();
			break;
			case 'height':
				el.style.height = el.attributes.height.nodeValue + 'px';
				el.getContext().clearRect();
			break;
		}
	}

	function onResize(e) {
		var el = e.srcElement;
		if( el.firstChild ) {
			el.firstChild.style.width =  el.clientWidth + 'px';
			el.firstChild.style.height = el.clientHeight + 'px';
		}
	}


	var dec2hex = [];
	for (var i = 0; i < 16; i++) {
		for (var j = 0; j < 16; j++) {
			dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
		}
	}

	function createMatrixIdentity() {
		return [
			[1, 0, 0],
			[0, 1, 0],
			[0, 0, 1]
		];
	}

	function matrixMultiply(m1, m2) {
		var result = createMatrixIdentity();

		for( var x = 0; x < 3; x++ ) {
			for( var y = 0; y < 3; y++ ) {
				var sum = 0;
				for (var z = 0; z < 3; z++) {
					sum += m1[x][z] * m2[z][y];
				}
				result[x][y] = sum;
			}
		}
		return result;
	}

	function copyState(o1, o2) {
		o2.fillStyle     = o1.fillStyle;
		o2.lineCap       = o1.lineCap;
		o2.lineJoin      = o1.lineJoin;
		o2.lineWidth     = o1.lineWidth;
		o2.miterLimit    = o1.miterLimit;
		o2.shadowBlur    = o1.shadowBlur;
		o2.shadowColor   = o1.shadowColor;
		o2.shadowOffsetX = o1.shadowOffsetX;
		o2.shadowOffsetY = o1.shadowOffsetY;
		o2.strokeStyle   = o1.strokeStyle;
		o2.globalAlpha   = o1.globalAlpha;
		o2.arcScaleX_    = o1.arcScaleX_;
		o2.arcScaleY_    = o1.arcScaleY_;
		o2.lineScale_    = o1.lineScale_;
	}

	function processStyle(styleString) {
		var str, alpha = 1;

		styleString = String(styleString);
		if( styleString.substring(0, 3) == 'rgb' ) {
			var start = styleString.indexOf('(', 3);
			var end = styleString.indexOf(')', start + 1);
			var guts = styleString.substring(start + 1, end).split(',');

			str = '#';
			for (var i = 0; i < 3; i++) {
				str += dec2hex[Number(guts[i])];
			}

			if (guts.length == 4 && styleString.substr(3, 1) == 'a') {
				alpha = guts[3];
			}
		}
		else {
			str = styleString;
		}
		return {color: str, alpha: alpha};
	}

	function processLineCap(lineCap) {
		switch (lineCap) {
			case 'butt': return 'flat';
			case 'round': return 'round';
			case 'square': default: return 'square';
		}
	}

//==============================

	function CanvasRenderingContext2D_(surfaceElement) {
		this.m_ = createMatrixIdentity();

		this.mStack_ = [];
		this.aStack_ = [];
		this.currentPath_ = [];

		this.strokeStyle = '#000';
		this.fillStyle = '#000';

		this.lineWidth = 1;
		this.lineJoin = 'miter';
		this.lineCap = 'butt';
		this.miterLimit = Z * 1;
		this.globalAlpha = 1;
		this.canvas = surfaceElement;

		var el = surfaceElement.ownerDocument.createElement('div');
		el.style.width =  surfaceElement.clientWidth + 'px';
		el.style.height = surfaceElement.clientHeight + 'px';
		el.style.overflow = 'hidden';
		el.style.position = 'absolute';
		surfaceElement.appendChild(el);

		this.element_ = el;
		this.arcScaleX_ = 1;
		this.arcScaleY_ = 1;
		this.lineScale_ = 1;
	}

	var contextPrototype = CanvasRenderingContext2D_.prototype;

	contextPrototype.clearRect = function() {
		this.element_.innerHTML = '';
	};

	contextPrototype.beginPath = function() {
		this.currentPath_ = [];
	};

	contextPrototype.moveTo = function(aX, aY) {
		var p = this.getCoords_(aX, aY);
		this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
		this.currentX_ = p.x;
		this.currentY_ = p.y;
	};

	contextPrototype.lineTo = function(aX, aY) {
		var p = this.getCoords_(aX, aY);
		this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});

		this.currentX_ = p.x;
		this.currentY_ = p.y;
	};

	contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {
		var p = this.getCoords_(aX, aY);
		var cp1 = this.getCoords_(aCP1x, aCP1y);
		var cp2 = this.getCoords_(aCP2x, aCP2y);
		bezierCurveTo(this, cp1, cp2, p);
	};

	function bezierCurveTo(self, cp1, cp2, p) {
		self.currentPath_.push({
			type: 'bezierCurveTo',
			cp1x: cp1.x,
			cp1y: cp1.y,
			cp2x: cp2.x,
			cp2y: cp2.y,
			x: p.x,
			y: p.y
		});
		self.currentX_ = p.x;
		self.currentY_ = p.y;
	}

	contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
		var cp = this.getCoords_(aCPx, aCPy);
		var p = this.getCoords_(aX, aY);

		var cp1 = {
			x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
			y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
		};
		var cp2 = {
			x: cp1.x + (p.x - this.currentX_) / 3.0,
			y: cp1.y + (p.y - this.currentY_) / 3.0
		};

		bezierCurveTo(this, cp1, cp2, p);
	};

	contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {
		aRadius *= Z;
		var arcType = aClockwise ? 'at' : 'wa';

		var xStart = aX + Math.cos(aStartAngle) * aRadius - Z2;
		var yStart = aY + Math.sin(aStartAngle) * aRadius - Z2;

		var xEnd = aX + Math.cos(aEndAngle) * aRadius - Z2;
		var yEnd = aY + Math.sin(aEndAngle) * aRadius - Z2;

		if (xStart == xEnd && !aClockwise) {
			xStart += 0.125;
		}

		var p = this.getCoords_(aX, aY);
		var pStart = this.getCoords_(xStart, yStart);
		var pEnd = this.getCoords_(xEnd, yEnd);

		this.currentPath_.push( {
			type: arcType,
			x: p.x,
			y: p.y,
			radius: aRadius,
			xStart: pStart.x,
			yStart: pStart.y,
			xEnd: pEnd.x,
			yEnd: pEnd.y
		} );
	};

	contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
		this.moveTo(aX, aY);
		this.lineTo(aX + aWidth, aY);
		this.lineTo(aX + aWidth, aY + aHeight);
		this.lineTo(aX, aY + aHeight);
		this.closePath();
	};

	contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
		var oldPath = this.currentPath_;
		this.beginPath();

		this.moveTo(aX, aY);
		this.lineTo(aX + aWidth, aY);
		this.lineTo(aX + aWidth, aY + aHeight);
		this.lineTo(aX, aY + aHeight);
		this.closePath();
		this.stroke();

		this.currentPath_ = oldPath;
	};

	contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
		var oldPath = this.currentPath_;
		this.beginPath();

		this.moveTo(aX, aY);
		this.lineTo(aX + aWidth, aY);
		this.lineTo(aX + aWidth, aY + aHeight);
		this.lineTo(aX, aY + aHeight);
		this.closePath();
		this.fill();

		this.currentPath_ = oldPath;
	};

	contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
		var gradient = new CanvasGradient_('gradient');
		gradient.x0_ = aX0;
		gradient.y0_ = aY0;
		gradient.x1_ = aX1;
		gradient.y1_ = aY1;
		return gradient;
	};

	contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) {
		var gradient = new CanvasGradient_('gradientradial');
		gradient.x0_ = aX0;
		gradient.y0_ = aY0;
		gradient.r0_ = aR0;
		gradient.x1_ = aX1;
		gradient.y1_ = aY1;
		gradient.r1_ = aR1;
		return gradient;
	};

	contextPrototype.drawImage = function(image, var_args) {
		var dx, dy, dw, dh, sx, sy, sw, sh;

		var oldRuntimeWidth = image.runtimeStyle.width;
		var oldRuntimeHeight = image.runtimeStyle.height;
		image.runtimeStyle.width = 'auto';
		image.runtimeStyle.height = 'auto';

		var w = image.width;
		var h = image.height;

		image.runtimeStyle.width = oldRuntimeWidth;
		image.runtimeStyle.height = oldRuntimeHeight;

		if (arguments.length == 3) {
			dx = arguments[1];
			dy = arguments[2];
			sx = sy = 0;
			sw = dw = w;
			sh = dh = h;
		}
		else if (arguments.length == 5) {
			dx = arguments[1];
			dy = arguments[2];
			dw = arguments[3];
			dh = arguments[4];
			sx = sy = 0;
			sw = w;
			sh = h;
		}
		else if (arguments.length == 9) {
			sx = arguments[1];
			sy = arguments[2];
			sw = arguments[3];
			sh = arguments[4];
			dx = arguments[5];
			dy = arguments[6];
			dw = arguments[7];
			dh = arguments[8];
		}
		else {
			throw Error('Invalid number of arguments');
		}

		var d = this.getCoords_(dx, dy);

		var w2 = sw / 2;
		var h2 = sh / 2;

		var vmlStr = [];

		var W = 10;
		var H = 10;

		vmlStr.push(
			' <g_vml_:group',
			' coordsize="', Z * W, ',', Z * H, '"',
			' coordorigin="0,0"' ,
			' style="width:', W, 'px;height:', H, 'px;position:absolute;'
		);

		if (this.m_[0][0] != 1 || this.m_[0][1]) {
			var filter = [];

			filter.push('M11=', this.m_[0][0], ',',
			'M12=', this.m_[1][0], ',',
			'M21=', this.m_[0][1], ',',
			'M22=', this.m_[1][1], ',',
			'Dx=', Math.round( d.x / Z), ',',
			'Dy=', Math.round(d.y / Z), '');

			var max = d;
			var c2 = this.getCoords_(dx + dw, dy);
			var c3 = this.getCoords_(dx, dy + dh);
			var c4 = this.getCoords_(dx + dw, dy + dh);

			max.x = Math.max(max.x, c2.x, c3.x, c4.x);
			max.y = Math.max(max.y, c2.y, c3.y, c4.y);

			vmlStr.push(
				'padding:0 ', Math.round(max.x / Z), 'px ', Math.round(max.y / Z),
				'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
				filter.join(''), ", sizingmethod='clip');"
			);
		}
		else {
			vmlStr.push('top:', Math.round(d.y / Z), 'px;left:', Math.round(d.x / Z), 'px;');
		}

		vmlStr.push(' ">' ,
			'<g_vml_:image src="', image.src, '"',
			' style="width:', Z * dw, 'px;',
			' height:', Z * dh, 'px;"',
			' cropleft="', sx / w, '"',
			' croptop="', sy / h, '"',
			' cropright="', (w - sx - sw) / w, '"',
			' cropbottom="', (h - sy - sh) / h, '"',
			' />',
			'</g_vml_:group>'
		);

		this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
	};

	contextPrototype.stroke = function(aFill) {
		var lineStr = [];
		var lineOpen = false;
		var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
		var color = a.color;
		var opacity = a.alpha * this.globalAlpha;

		var W = 10;
		var H = 10;

		lineStr.push(
			'<g_vml_:shape',
			' filled="', !!aFill, '"',
			' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
			' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
			' stroked="', !aFill, '"',
			' path="'
		);

		var newSeq = false;
		var min = {x: null, y: null};
		var max = {x: null, y: null};

		for( var i = 0; i < this.currentPath_.length; i++ ) {
			var p = this.currentPath_[i];
			var c;

			switch( p.type ) {
				case 'moveTo':
					c = p;
					lineStr.push(' m ', Math.round(p.x), ',', Math.round(p.y));
				break;
				case 'lineTo':
					lineStr.push(' l ', Math.round(p.x), ',', Math.round(p.y));
				break;
				case 'close':
					lineStr.push(' x ');
					p = null;
				break;
				case 'bezierCurveTo':
					lineStr.push(' c ',
					Math.round(p.cp1x), ',', Math.round(p.cp1y), ',',
					Math.round(p.cp2x), ',', Math.round(p.cp2y), ',',
					Math.round(p.x), ',', Math.round(p.y));
				break;
				case 'at':
				case 'wa':
					lineStr.push(' ', p.type, ' ',
					Math.round(p.x - this.arcScaleX_ * p.radius), ',',
					Math.round(p.y - this.arcScaleY_ * p.radius), ' ',
					Math.round(p.x + this.arcScaleX_ * p.radius), ',',
					Math.round(p.y + this.arcScaleY_ * p.radius), ' ',
					Math.round(p.xStart), ',', Math.round(p.yStart), ' ',
					Math.round(p.xEnd), ',', Math.round(p.yEnd));
				break;
			}

			if( p ) {
				if( min.x == null || p.x < min.x ) min.x = p.x;
				if( max.x == null || p.x > max.x ) max.x = p.x;
				if( min.y == null || p.y < min.y ) min.y = p.y;
				if( max.y == null || p.y > max.y ) max.y = p.y;
			}
		}
		lineStr.push(' ">');

		if (!aFill) {
			var lineWidth = this.lineScale_ * this.lineWidth;

			if (lineWidth < 1) {
				opacity *= lineWidth;
			}

			lineStr.push(
				'<g_vml_:stroke',
				' opacity="', opacity, '"',
				' joinstyle="', this.lineJoin, '"',
				' miterlimit="', this.miterLimit, '"',
				' endcap="', processLineCap(this.lineCap), '"',
				' weight="', lineWidth, 'px"',
				' color="', color, '" />'
			);
		}
		else if( typeof this.fillStyle == 'object' ) {
			var fillStyle = this.fillStyle;
			var angle = 0;
			var focus = {x: 0, y: 0};

			var shift = 0;
			var expansion = 1;

			if( fillStyle.type_ == 'gradient' ) {
				var x0 = fillStyle.x0_ / this.arcScaleX_;
				var y0 = fillStyle.y0_ / this.arcScaleY_;
				var x1 = fillStyle.x1_ / this.arcScaleX_;
				var y1 = fillStyle.y1_ / this.arcScaleY_;
				var p0 = this.getCoords_(x0, y0);
				var p1 = this.getCoords_(x1, y1);
				var dx = p1.x - p0.x;
				var dy = p1.y - p0.y;
				angle = Math.atan2(dx, dy) * 180 / Math.PI;

				if( angle < 0 ) angle += 360;
				if (angle < 1e-6) angle = 0;
			}
			else {
				var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_);
				var width  = max.x - min.x;
				var height = max.y - min.y;
				focus = {
					x: (p0.x - min.x) / width,
					y: (p0.y - min.y) / height
				};

				width  /= this.arcScaleX_ * Z;
				height /= this.arcScaleY_ * Z;
				var dimension = Math.max(width, height);
				shift = 2 * fillStyle.r0_ / dimension;
				expansion = 2 * fillStyle.r1_ / dimension - shift;
			}

			var stops = fillStyle.colors_;
			stops.sort(function(cs1, cs2) {
				return cs1.offset - cs2.offset;
			});

			var length = stops.length;
			var color1 = stops[0].color;
			var color2 = stops[length - 1].color;
			var opacity1 = stops[0].alpha * this.globalAlpha;
			var opacity2 = stops[length - 1].alpha * this.globalAlpha;

			var colors = [];
			for( var i = 0; i < length; i++ ) {
				var stop = stops[i];
				colors.push(stop.offset * expansion + shift + ' ' + stop.color);
			}

			lineStr.push(
				'<g_vml_:fill type="', fillStyle.type_, '"',
				' method="none" focus="100%"',
				' color="', color1, '"',
				' color2="', color2, '"',
				' colors="', colors.join(','), '"',
				' opacity="', opacity2, '"',
				' g_o_:opacity2="', opacity1, '"',
				' angle="', angle, '"',
				' focusposition="', focus.x, ',', focus.y, '" />'
			);
		}
		else {
			lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
		}

		lineStr.push('</g_vml_:shape>');

		this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
	};

	contextPrototype.fill = function() {
		this.stroke(true);
	}

	contextPrototype.closePath = function() {
		this.currentPath_.push({type: 'close'});
	};

	contextPrototype.getCoords_ = function(aX, aY) {
		var m = this.m_;
		return {
			x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
			y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
		}
	};

	contextPrototype.save = function() {
		var o = {};
		copyState(this, o);
		this.aStack_.push(o);
		this.mStack_.push(this.m_);
		this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
	};

	contextPrototype.restore = function() {
		copyState(this.aStack_.pop(), this);
		this.m_ = this.mStack_.pop();
	};

	function matrixIsFinite(m) {
		for( var j = 0; j < 3; j++ ) {
			for( var k = 0; k < 2; k++ ) {
				if( !isFinite(m[j][k]) || isNaN(m[j][k]) ) {
					return false;
				}
			}
		}
		return true;
	}

	function setM(ctx, m, updateLineScale) {
		if( !matrixIsFinite(m) ) return;
		ctx.m_ = m;

		if( updateLineScale ) {
			var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
			ctx.lineScale_ = Math.sqrt(Math.abs(det));
		}
	}

	contextPrototype.translate = function(aX, aY) {
		var m1 = [
		  [1,  0,  0],
		  [0,  1,  0],
		  [aX, aY, 1]
		];

		setM(this, matrixMultiply(m1, this.m_), false);
	};

	contextPrototype.rotate = function(aRot) {
		var c = Math.cos(aRot);
		var s = Math.sin(aRot);

		var m1 = [
			[c,  s, 0],
			[-s, c, 0],
			[0,  0, 1]
		];
		setM(this, matrixMultiply(m1, this.m_), false);
	};

	contextPrototype.scale = function(aX, aY) {
		this.arcScaleX_ *= aX;
		this.arcScaleY_ *= aY;
		var m1 = [
			[aX, 0,  0],
			[0,  aY, 0],
			[0,  0,  1]
		];
		setM(this, matrixMultiply(m1, this.m_), true);
	};

	contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
		var m1 = [
			[m11, m12, 0],
			[m21, m22, 0],
			[dx,  dy,  1]
		];
		setM(this, matrixMultiply(m1, this.m_), true);
	};

	contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
		var m = [
			[m11, m12, 0],
			[m21, m22, 0],
			[dx,  dy,  1]
		];
		setM(this, m, true);
	};

	contextPrototype.clip = function() {
		// TODO: Implement
	};

	contextPrototype.arcTo = function() {
		// TODO: Implement
	};

	contextPrototype.createPattern = function() {
		return new CanvasPattern_;
	};

	function CanvasGradient_(aType) {
		this.type_ = aType;
		this.x0_ = 0;
		this.y0_ = 0;
		this.r0_ = 0;
		this.x1_ = 0;
		this.y1_ = 0;
		this.r1_ = 0;
		this.colors_ = [];
	}

	CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
		aColor = processStyle(aColor);
		this.colors_.push({
			offset: aOffset,
			color: aColor.color,
			alpha: aColor.alpha
		});
	};

	function CanvasPattern_() {}

	CanvasEmulate = CanvasEmulate_;
	CanvasRenderingContext2D = CanvasRenderingContext2D_;
	CanvasGradient = CanvasGradient_;
	CanvasPattern = CanvasPattern_;
})();

