
var Color = {
	rgb : function(red, green, blue) {
		return 'rgb(' + red + ',' + green + ',' + blue + ')';
	}
}

	Buffer = function(bigEndian, buffer){
        this.bigEndian = bigEndian || 0, this.buffer = [], this.setBuffer(buffer);
    }
    Buffer.prototype = {
        readBits : function(start, length){
            //shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni)
            function shl(a, b){
                for(++b; --b; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1);
                return a;
            }
            if(start < 0 || length <= 0)
                return 0;
            this.checkBuffer(start + length);
            for(var offsetLeft, offsetRight = start % 8, curByte = this.buffer.length - (start >> 3) - 1,
                lastByte = this.buffer.length + (-(start + length) >> 3), diff = curByte - lastByte,
                sum = ((this.buffer[ curByte ] >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1))
                + (diff && (offsetLeft = (start + length) % 8) ? (this.buffer[ lastByte++ ] & ((1 << offsetLeft) - 1))
                << (diff-- << 3) - offsetRight : 0); diff; sum += shl(this.buffer[ lastByte++ ], (diff-- << 3) - offsetRight)
            );
            return sum;
        },
        setBuffer : function(data){
            if(data){
                for(var l, i = l = data.length, b = this.buffer = new Array(l); i; b[l - i] = data.charCodeAt(--i));
                this.bigEndian && b.reverse();
            }
        },
        hasNeededBits : function(neededBits){
            return this.buffer.length >= -(-neededBits >> 3);
        },
        checkBuffer : function(neededBits){
            if(!this.hasNeededBits(neededBits))
                throw new Error("checkBuffer::missing bytes");
        }
    }


var HexInputStream = function () {
	this.initialize.apply(this, arguments);
}
HexInputStream.prototype = {
	fp : 0,
	rawData : undefined,
	initialize : function(hexString) {
		this.rawData = hexString;
	},
	readRaw : function(bytes) {
		var raw = this.rawData.substring(this.fp, this.fp + bytes*2);
		this.fp += bytes*2;
		return raw;
	},
	readInt : function() {
		return this.readByte() |
				this.readByte() << 8 |
				this.readByte() << 16 |
				this.readByte() << 24;
	},
	readShort : function() {
		return (this.readByte()<<16 | this.readByte() << 24)>>16;
	},
	readUnsignedShort : function() {
		return this.readByte() | this.readByte() << 8;
	},
	readByte : function() {
		var val = parseInt(this.rawData.charAt(this.fp) + this.rawData.charAt(this.fp + 1), 16);
		this.fp += 2;
		return val;
	},
	skip : function(bytes) {
		this.fp += bytes*2;
	},
	readFloat : function() {
		var bin =  (function(s){
			for(var i = 0, l = s.length, r = ""; i < l; r += String.fromCharCode(parseInt(s.substr(i, 2), 16)), i += 2);
			return r;
		})(this.readRaw(4));
		return this.decodeFloat(bin, 23, 8);
	},
	
	// http://jsfromhell.com/classes/binary-parser
	decodeFloat : function(data, precisionBits, exponentBits){
        var b = ((b = new Buffer(false, data)).checkBuffer(precisionBits + exponentBits + 1), b),
            bias = Math.pow(2, exponentBits - 1) - 1, signal = b.readBits(precisionBits + exponentBits, 1),
            exponent = b.readBits(precisionBits, exponentBits), significand = 0,
            divisor = 2, curByte = b.buffer.length + (-precisionBits >> 3) - 1,
            byteValue, startBit, mask;
        do
            for(byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit;
                mask >>= 1; (byteValue & mask) && (significand += 1 / divisor), divisor *= 2);
        while(precisionBits -= startBit);
        return exponent == (bias << 1) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity
            : (1 + signal * -2) * (exponent || significand ? !exponent ? Math.pow(2, -bias + 1) * significand
            : Math.pow(2, exponent - bias) * (1 + significand) : 0);
    }
}

var Emf = function () {
	this.initialize.apply(this, arguments);
}
Emf.prototype = {
	parsed : false,
	is : undefined,
	objectMap : {},
	header : {},
	penStyle : undefined,
	penWidth : undefined,
	brushStyle : undefined,
	penColor : undefined,
	brushColor : undefined,
	textColor : undefined,
	cap : undefined,
	join : undefined,
	miter : undefined,
	drawableList : [],

	initialize : function(path) {
		
		var self = this;
		var xhr = new XMLHttpRequest();
		xhr.open("get", path, true);
		xhr.onreadystatechange = function() {
			if (xhr.readyState == 4) { // DONE
				if (xhr.status == 200) { // OK
					self.is = new HexInputStream(xhr.responseText);
					self.parseFile();
					self.parsed = true;
					if (self.waitContext) {
						self.draw(self.waitContext);
					}
				} else {
					// load error
			    } 
			}
		}
		xhr.send();
	},
	hitTest : function(g2dContext, points) {
		if (!this.parsed) return;
		var ctx = this.ctx = g2dContext;

		ctx.save();

		for (var i in this.drawableList) {
			this.drawableList[i].func.apply(this, [this.drawableList[i].data, points]);
		}
		
		ctx.restore();
	},

	draw : function(g2dContext) {
		if (!this.parsed) {
			this.waitContext = g2dContext;
		}
		var ctx = this.ctx = g2dContext;

		ctx.save();

		var len = this.drawableList.length;
		for (i = 0; i < len; i++) {
			this.drawableList[i].func.apply(this, [this.drawableList[i].data]);
		}
		
		ctx.restore();

	},
	parseFile : function() {
		this.readHeader();
		while(true) {
			var iType = this.is.readInt();
			var size = this.is.readInt();
			if (iType == 14) break; //END_OF_RECORD
			
			var params = this.is.readRaw(size - 8);
			this.playMetaRecord(iType, params);
		}
	},

	readHeader : function() {
		var iType = this.is.readInt();
		if (iType != 1) {
			throw "invalid magic number";
		}
		this.header.iSize = this.is.readInt();
		this.header.rclBoundsLeft = this.is.readInt();
		this.header.rclBoundsTop = this.is.readInt();
		this.header.rclBoundsRight = this.is.readInt();
		this.header.rclBoundsBottom = this.is.readInt();
		this.header.rclFrameLeft = this.is.readInt();
		this.header.rclFrameTop = this.is.readInt();
		this.header.rclFrameRight = this.is.readInt();
		this.header.rclFrameBottom = this.is.readInt();
		this.header.dSignature = this.is.readInt();
		this.header.nVersion = this.is.readInt();
		this.header.nBytes = this.is.readInt();
		this.header.nRecords = this.is.readInt();
		this.header.nHandles = this.is.readShort();
		this.is.readShort(); // sReserved
		this.header.nDescription = this.is.readInt();
		this.header.offDescription = this.is.readInt();
		this.header.nPalEntries = this.is.readInt();
		this.header.szlDeviceCx = this.is.readInt();
		this.header.szlDeviceCy = this.is.readInt();
		this.header.szlMillimetersCx = this.is.readInt();
		this.header.szlMillimetersCy = this.is.readInt();
		
		this.is.skip(this.header.iSize - this.header.offDescription);
		
		this.header.width = (this.header.rclBoundsRight - this.header.rclBoundsLeft) * this.header.szlDeviceCx/this.header.szlMillimetersCx;
		this.header.height = (this.header.rclBoundsBottom - this.header.rclBoundsTop) * this.header.szlDeviceCy/this.header.szlMillimetersCy;
	},
	
	playMetaRecord : function(iType, params) {
		var paramIs = new HexInputStream(params);

		switch (iType) {
		case 95: // EMR_EXTCREATEPEN
		{
			var ihPen = paramIs.readInt();
			this.objectMap[ihPen] = {iType:iType, params:params};
		}
			break;
		case 39: // EMR_CREATEBRUSHINDIRECT
		{
			var ihBrush = paramIs.readInt();
			this.objectMap[ihBrush] = {iType:iType, params:params};
		}
			break;
		case 82: // EMR_EXTCREATEFONTINDIRECTW
		{
			var ihFont = paramIs.readInt();
			this.objectMap[ihFont] = {iType:iType, params:params};
		}
			break;
		case 37: // EMR_SELECTOBJECT
		{
			var selectIndex = paramIs.readUnsignedShort();
			var record = this.objectMap[selectIndex];
			if (record == null) {
				console.log("select null object");
				break;
			}
			var recordParamIs = new HexInputStream(record.params);
			switch (record.iType) {
			case 95: // select pen
			{
				recordParamIs.readInt();
				recordParamIs.readInt(); // offBmi
				recordParamIs.readInt(); // cbBmi
				recordParamIs.readInt(); // offBits
				recordParamIs.readInt(); // cbBits

				penStyle = recordParamIs.readInt();
				switch (penStyle & 0x00000f00) {
				case 0x00000000:
					this.cap = "round";
					break;
				case 0x00000100:
					this.cap = "square";
					break;
				case 0x00000200:
					this.cap = "butt";
					break;
				}
				switch (penStyle & 0x0000f000) {
				case 0x00000000:
					this.join = "round";
					break;
				case 0x00001000:
					this.join = "bevel";
					break;
				case 0x00002000:
					this.join = "miter";
					break;
				}
				this.penStyle = penStyle & 0x0000000f;
				
				this.penWidth = recordParamIs.readInt();
				this.brushStyle = recordParamIs.readInt();
				var cr = recordParamIs.readByte();
				var cg = recordParamIs.readByte();
				var cb = recordParamIs.readByte();
				recordParamIs.readByte();
				this.penColor = Color.rgb(cr, cg, cb);
			}
				break;
			case 39: // select brush
			{
				recordParamIs.readInt();
				this.brushStyle = recordParamIs.readInt();
				var cr = recordParamIs.readByte();
				var cg = recordParamIs.readByte();
				var cb = recordParamIs.readByte();
				recordParamIs.readByte();
				this.brushColor = Color.rgb(cr, cg, cb);
			}
				break;
			case 82: // select font
			{
			}
				break;
			default:
				 console.log("select unknown object:" + record.iType);
			}
		}
			break;
		case 40: // EMR_DELETEOBJECT
		{
			var deleteIndex = paramIs.readUnsignedShort();
			this.objectMap[deleteIndex] = undefined;
		}
		break;
		case 90: // EMR_POLYPOLYLINE16
		case 91: // EMR_POLYPOLYGON16
		{
			paramIs.readInt(); // rclBounds
			paramIs.readInt();
			paramIs.readInt();
			paramIs.readInt();
			var numPolys = paramIs.readInt();
			var numTotalPoints = paramIs.readInt();
			var numPoints = [];
			var i,j;
			for (i = 0; i < numPolys; i++) {
				numPoints[i] = paramIs.readInt();
			}
			this.drawableList.push({
				func : function(data, points) {
					var ctx = this.ctx;
					ctx.save();
					ctx.beginPath();
				}
			});

			for (i = 0; i < numPolys; i++) {
				var pointList = [];
				for (j = 0; j < numPoints[i]; j++) {
					pointList.push({x:paramIs.readShort(), y:paramIs.readShort()});
				}
				
				this.drawableList.push({
					func : function(data, points) {
						var ctx = this.ctx;
						ctx.moveTo(data.pointList[0].x, data.pointList[0].y);
						for (var i = 1; i < data.pointList.length; i++) {
							ctx.lineTo(data.pointList[i].x, data.pointList[i].y);
						}
						if (data.iType == 91) ctx.closePath();
					},
					data : {
						iType : iType,
						pointList : pointList,
						brushColor : this.brushColor,
						penColor : this.penColor,
						penWidth : this.penWidth,
						brushStyle : this.brushStyle,
						penStyle : this.penStyle,
						cap : this.cap,
						join : this.join,
						miter : this.miter
					}
				});
			}

			this.drawableList.push({
				func : function(data, points) {
					var ctx = this.ctx;
					if (data.iType == 91 && data.brushStyle != 1) { //!BS_NULL
						ctx.fillStyle = data.brushColor;
						if (points) {
							for (var j in points) {
								if (ctx.isPointInPath(points[j].x, points[j].y)) {
									points[j].hit = true;
								}
							}
						} else {
							ctx.fill();
						}
					}
					if (data.penStyle != 5) {//！PS_NULL
						ctx.strokeStyle = data.penColor;
						ctx.lineWidth = data.penWidth;
						ctx.lineCap = data.cap;
						ctx.lineJoin = data.join;
						ctx.lineMiter = data.miter;
						if (!points) {
							ctx.stroke();
						}
					}
					ctx.restore();
				},
				data : {
					iType : iType,
					pointList : pointList,
					brushColor : this.brushColor,
					penColor : this.penColor,
					penWidth : this.penWidth,
					brushStyle : this.brushStyle,
					penStyle : this.penStyle,
					cap : this.cap,
					join : this.join,
					miter : this.miter
				}
			});
		}
			break;
		case 58: // SETMITERLIMIT
		{
			this.miter = paramIs.readInt();
		}
			break;
		case 62: // EMR_FILLPATH
		case 63: // EMR_STROKEANDFILLPATH
		case 64: // EMR_STROKEPATH
		{
			this.drawableList.push({
				func : function(data, points) {
					var ctx = this.ctx;
					if (data.iType != 64 && data.brushStyle != 1) { //!BS_NULL
						ctx.fillStyle = data.brushColor;
						if (points) {
							for (var j in points) {
								if (ctx.isPointInPath(points[j].x, points[j].y)) {
									points[j].hit = true;
								}
							}
						} else {
							ctx.fill();
						}
					}
					if (data.penStyle != 5) {//！PS_NULL
						ctx.strokeStyle = data.penColor;
						ctx.lineWidth = data.penWidth;
						ctx.lineCap = data.cap;
						ctx.lineJoin = data.join;
						ctx.lineMiter = data.miter;
						if (!points) {
							ctx.stroke();
						}
					}
				},
				data : {
					iType : iType,
					brushColor : this.brushColor,
					penColor : this.penColor,
					penWidth : this.penWidth,
					brushStyle : this.brushStyle,
					penStyle : this.penStyle,
					cap : this.cap,
					join : this.join,
					miter : this.miter
				}
			});
		}
			break;
		case 59: // EMR_BEGINPATH
			this.drawableList.push({
				func : function(data) {
					this.ctx.beginPath();
				}
			});
			break;
		case 27: // EMR_MOVETOEX
		{
			var x = paramIs.readInt();
			var y = paramIs.readInt();
			this.drawableList.push({
				func : function(data) {
					this.ctx.moveTo(data.x, data.y);
				},
				data : {
					x : x,
					y : y
				}
			});
		}
			break;
		case 60: // EMR_ENDPATH
			break;
		case 61: // EMR_CLOSEFIGURE
			this.drawableList.push({
				func : function(data) {
					this.ctx.closePath();
				}
			});
			break;
		case 88: // EMR_POLYBEZIERTO16
		{
			paramIs.readInt(); // rclBounds
			paramIs.readInt();
			paramIs.readInt();
			paramIs.readInt();
			var numPoints = paramIs.readInt();
			var pointList = [];
			for (var i = 0; i < numPoints; i++) {
				pointList.push({x:paramIs.readShort(), y:paramIs.readShort()});
			}
			this.drawableList.push({
				func : function(data) {
					for (var i = 0; i < data.pointList.length; i+=3) {
						var pt1 = data.pointList[i];
						var pt2 = data.pointList[i+1];
						var pt3 = data.pointList[i+2];
						this.ctx.bezierCurveTo(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
					}
				},
				data : {
					pointList : pointList
				}
			});
		}
			break;
		case 36: // EMR_MODIFYWORLDTRANSFORM
		{
			var eM11 = paramIs.readFloat();
			var eM12 = paramIs.readFloat();
			var eM21 = paramIs.readFloat();
			var eM22 = paramIs.readFloat();
			var eDx =paramIs.readFloat();
			var eDy = paramIs.readFloat();
			var iMode = paramIs.readInt();
			
			this.drawableList.push({
				func : function(data) {
					this.ctx.save();
					this.ctx.transform(data.eM11, data.eM12, data.eM21, data.eM22, data.eDx, data.eDy);
				},
				data : {
					eM11 : eM11,
					eM12 : eM12,
					eM21 : eM21,
					eM22 : eM22,
					eDx : eDx,
					eDy : eDy
				}
			});
		}
			break;
		case 35: // EMR_SETWORLDTRANSFORM
		{
			this.drawableList.push({
				func : function(data) {
					this.ctx.restore();
				}
			});
		}
			break;
		case 9: // EMR_SETWINDOWEXTEX
			// ignore
			break;
		case 10: // EMR_SETWINDOWORGEX
			// ignore
			break;
		case 12: // EMR_SETVIEWPORTORGEX
			// ignore
			break;
		case 11: // EMR_SETVIEWPORTEXTEX
			// ignore
			break;
		case 17: // EMR_SETMAPMODE
			// ignore
			break;
		case 18: // EMR_SETBKMODE
			// ignore
			break;
		case 19: // EMR_SETPOLYFILLMODE
			// ignore
			break;

		case 67: // EMR_SELECTCLIPPATH
			// ignore
			break;
		case 81: // EMR_STRETCHDIBITS
			// ignore
			break;
		case 6: // EMR_POLYLINETO
			// ignore
			break;
		case 33: // EMR_SAVEDC
			// ignore
			break;
		case 34: // EMR_SAVEDC
			// ignore
			break;
		default:
			console.log(iType);
			break;
		}
	}
	
}

