var ENTITY_OPTIONS_DEFAULT_VALUES = {
	rotate: 0,
	translate: {x: 0, y: 0},
	transform: {m11: 1, m12: 0, m21: 0, m22: 1, dx: 0, dy: 0},
	scale: {x: 1, y: 1},
	globalAlpha: undefined,
	font: undefined,
	fillStyle: undefined,
	strokeStyle: undefined,
	lineCap: undefined,
	lineJoin: undefined,
	lineWidth: undefined,
	miterLimit: undefined,
	shadowBlur: undefined,
	shadowColor: undefined,
	shadowOffsetX: undefined,
	shadowOffsetY: undefined,
	strokeStyle: undefined,
	textAlign: undefined,
	textBaseline: undefined,
	globalCompositeOperation: undefined
};
(function() {
	var canvas:HTMLCanvasElement = <HTMLCanvasElement>document.createElement("canvas");
	var context = canvas.getContext("2d");
	for (var p in ENTITY_OPTIONS_DEFAULT_VALUES) {
		if (ENTITY_OPTIONS_DEFAULT_VALUES[p] == undefined) {
			ENTITY_OPTIONS_DEFAULT_VALUES[p] = context[p];
		}
	}
})();

class E {
	x: number;
	y: number;
	width: number;
	height: number;
	_tl: Timeline;
	scene:Scene;
	parent:E;
	active_queue:Function[];
	started:bool;
	isUpdated:bool;
	disableTransform:bool;
	entities:E[];
	pointCapture:bool;
	pointDown: Trigger;
	pointUp: Trigger;
	pointMove: Trigger;
	//rotate: number
	//translate: {x: number, y: number}
	//transform: {m11: number, m12: number, m21: number, m22: number, dx: number, dy: number}
	//scale: {x: number, y: number}
	//fillStyle: string(color)
	//font: string
	//globalAlpha: number
	//lineCap: string(butt, round, square)
	//lineJoin: string(bevel, round, miter)
	//lineWidth: number
	//miterLimit: number
	//shadowBlur: number
	//shadowColor: string(color)
	//shadowOffsetX: number
	//shadowOffsetY: number
	//strokeStyle: string(color)
	//textAlign: string(start, end, left, right, center)
	//textBaseline: string(top, hanging, middle, alphabetic, ideographic, bottom)
	//globalCompositeOperation: string(source-atop, source-in, source-out, source-over (default), destination-atop, destination-in, destination-out, destination-over, lighter, copy, xor, vendorName-operationName)
	options:Object;
	orderDraw:Function;
	filter:ImageFilter.FilterChain;
	scroll:CommonOffset;

	constructor() {}

	enablePointingEvent() {
		this.pointCapture = true;
		if (! this.pointDown)
			this.pointDown = new Trigger();
		if (! this.pointUp)
			this.pointUp = new Trigger();
		if (! this.pointMove)
			this.pointMove = new Trigger();
	}

	disablePointingEvent() {
		delete this.pointCapture;
	}

	removeDrawOption(name:string) {
		if (! this.options)
			return;

		if (this.options[name] !== undefined)
			delete this.options[name];
		var cnt = 0;
		for (var i in this.options) {
			cnt++;
			break;
		}
		if (! cnt)
			delete this.options;
	}

	setDrawOption(name:string, value:any) {
		if (! this.options)
			this.options = {}
		this.options[name] = value;
		this.updated();
	}

	getDrawOption(name:string):any {
		if (! this.options || this.options[name] == undefined)
			return ENTITY_OPTIONS_DEFAULT_VALUES[name];
		return this.options[name];
	}

	moveTo(x:number, y: number) {
		this.x = x;
		this.y = y;
		this.updated();
	}

	moveBy(x:number, y:number) {
		this.x += x;
		this.y += y;
		this.updated();
	}

	scrollTo(x:number, y: number) {
		this.scroll = {
			x: x,
			y: y
		}
		this.updated();
	}

	scrollBy(x:number, y:number) {
		if (! this.scroll)
			this.scroll = {x: 0, y: 0}
		this.scroll.x += x;
		this.scroll.y += y;
		this.updated();
	}

	activate() {
		if (this.active_queue) {
			var f:Function;
			while (f = this.active_queue.shift())
				f.call(this);

			delete this.active_queue;
		}
		if (this.entities) {
			for (var i=0; i<this.entities.length; i++) {
				if (! this.entities[i].scene) {
					this.entities[i].scene = this.scene;
					this.entities[i].activate();
				}
			}
		}
	}

	addActiveQueue(f:Function) {
		if (this.scene) {
			f.call(this);
			return;
		}
		if (! this.active_queue)
			this.active_queue = [];
		this.active_queue.push(f);
	}

	appendTo(scene:Scene, layerName?:string) {
		scene.append(this, layerName);
	}

	remove() {
		if (this.parent)
			this.parent.removeChild(this);
		else
			throw "Can not remove layer. (use scene.deleteLayer)";
	}

	insert(entity:E, index: any) {
		if (! this.entities)
			throw "Can not call append of non-container entity";
		entity.scene = this.scene;
		entity.parent = this;
		if (typeof index != "number") {
			for (var i=0; i<this.entities.length; i++) {
				if (this.entities[i] == index) {
					index = i;
					break;
				}
			}

		}
		this.entities.splice(index, 0, entity);
		entity.activate();
	}

	append(entity:E) {
		if (! this.entities)
			throw "Can not call append of non-container entity";
		entity.scene = this.scene;
		entity.parent = this;
		this.entities.push(entity);
		if (this.scene)
			entity.activate();
	}

	removeChild(entity:E) {
		if (! this.entities)
			throw "Can not call removeChild of non-container entity";

		for (var i=0; i<this.entities.length; i++) {
			if (this.entities[i] == entity) {
				if (entity.entities) {
					var childEntity;
					while (childEntity = entity.entities.pop())
						entity.removeChild(childEntity);
				}
				this.entities.splice(i, 1);
				entity.destroy();
				return true;
			}
		}
		return false;
	}

	start() {
		if (this.started)
			return;
		this.started = true;
		if (this.scene)
			this.scene.game.update.handle(this, this.update);
		else
			this.addActiveQueue(function() {this.scene.game.update.handle(this, this.update);} );
	}

	stop() {
		if (! this.started)
			return;
		this.started = false;
		if (this.scene)
			this.scene.game.update.remove(this, this.update);
		else
			this.addActiveQueue(function() {this.scene.game.update.remove(this, this.update);});
	}

	startTimer(wait:number, method?:Function) {
		if (this.scene)
			this.scene.game.addTimer(wait, this, method ? method : this.interval);
		else
			this.addActiveQueue(function() {this.scene.game.addTimer(wait, this, method ? method : this.interval);});
	}

	stopTimer(wait:number, method?:Function) {
		if (this.scene)
			this.scene.game.removeTimer(wait, this, method ? method : this.interval)
		else
			this.addActiveQueue(function() {this.scene.game.removeTimer(wait, this, method ? method : this.interval);});
	}

	updated() {
		if (this.parent)
			this.parent.updated();
		else
			this.isUpdated = true;
	}

	isUpdate():bool {
		return this.isUpdated;
	}

	reflected() {
		this.isUpdated = false;
	}

	tl():Timeline {
		if (! this._tl)
			this._tl = new Timeline(this);
		return this._tl;
	}

	destroy() {
		if  (this._tl) {
			this._tl.clear();
			delete this._tl;
		}
		this.stop();
		if (this.scene) {
			this.scene.game.removeTimerAll(this);
			this.scene = null;
		}
		delete this.parent;
		if (this.entities) {
			var childEntity;
			while (childEntity = this.entities.pop())
				childEntity.destroy();
		}
		if (this.pointDown) {
			this.pointDown.destroy();
			delete this.pointDown;
		}
		if (this.pointUp) {
			this.pointUp.destroy();
			delete this.pointUp;
		}
		if (this.pointMove) {
			this.pointMove.destroy();
			delete this.pointMove;
		}
	}

	offset():CommonOffset {
		var parent_offset = this.parent ? this.parent.offset() : {x:this.scroll ? this.scroll.x : 0, y: this.scroll ? this.scroll.y : 0};
		return {
			x: this.x + parent_offset.x,
			y: this.y + parent_offset.y
		}
	}

	rect():Rectangle {
		var offset = this.offset();
		return new Rectangle(
			offset.x,
			offset.y,
			offset.x + this.width,
			offset.y + this.height
		);
	}

	hitTest(point: CommonOffset) {
		return this.rect().hitTest(point);
	}

	getDistance(point:CommonOffset):CommonOffset {
		var area:CommonArea = <CommonArea>point;
		if (area.width && area.height) {
			return {
				x: Math.abs((area.x+area.width/2) - (this.x+this.width/2)),
				y: Math.abs((area.y+area.height/2) - (this.y+this.height/2))
			};
		} else {
			return {
				x: Math.abs(point.x - (this.x+this.width/2)),
				y: Math.abs(point.y - (this.y+this.height/2))
			};
		}
	}

	getEntityByPoint(point: CommonOffset, force?:bool):E {
		if (this.entities) {
			for (var i=this.entities.length-1; i>=0; i--) {
				if (force || this.entities[i].pointCapture) {
					var p = this.entities[i].getEntityByPoint(point);
					if (p)
						return p;
				}
			}
		}
		if ((force || this.pointCapture) && this.hitTest(point))
			return this;

		return null;
	}

	createSprite() {
		var buffer = new BufferedRenderer({width:this.width, height:this.height});
		var x = this.x;
		var y = this.y;
		this.x = 0;
		this.y = 0;
		buffer.renderUnit(this);
		this.x = x;
		this.y = y;
		return buffer.createSprite();
	}

	update(t:number) {}

	interval() {}

	draw(context:CanvasRenderingContext2D) {}
}