///<reference path="all.ts"/>
module jg {
	/**
	 * Tileでの描画に利用するチップセットをあらわすクラス。
	 * 通常、ユーザがこのクラスを直接利用することは無い。
	 */
	export class ChipSet {
		/** 本クラスが管理する画像 */
		image: any;
		/** チップの折り返し位置。4x5のチップセット画像であれば4。 */
		sep: number;
		/** このチップセットを親元であるTile */
		tile: Tile;
		/** チップのオフセット番号。Tileクラス側が利用する */
		chipOffset: number;

		/**
		 * コンストラクタ
		 * @param tile 親元Tile
		 * @param image 対象の画像
		 */
		constructor(tile:Tile, image:any) {
			this.image = image;
			this.tile = tile;
			this.sep = Math.floor(this.image.width / this.tile.tileWidth);
		}

		/**
		 * このChipSetで管理しているチップ数を取得する
		 */
		count():number {
			return Math.round((this.image.width * this.image.height) / (this.tile.tileWidth * this.tile.tileHeight));
		}

		/**
		 * 描画する
		 * @param c 描画対象コンテキスト
		 * @param x 描画X座標
		 * @param y 描画Y座標
		 * @param chip 描画するチップ番号
		 */
		draw(c:CanvasRenderingContext2D, x:number, y:number, chip:number) {
			var tw = this.tile.tileWidth;
			var th = this.tile.tileHeight;
			c.drawImage(
				this.image,
				(chip % this.sep) * tw,
				Math.floor(chip / this.sep) * th,
				tw,
				th,
				x * tw,
				y * th,
				tw,
				th
			);
		}

		/**
		 * このChipSetのマップチップを個別に、Spriteの配列として取得する
		 */
		getChips():Sprite[] {
			var len = this.count();
			var sprite = new Sprite(this.image);
			var buf:BufferedRenderer = new BufferedRenderer(sprite);
			var ret:Sprite[] = [];
			var w = this.tile.tileWidth;
			var h = this.tile.tileHeight;
			var area = {
				x: 0,
				y: 0,
				width: w,
				height: h
			}
			buf.renderUnit(sprite);

			for (var i=0; i<len; i++) {
				ret.push(buf.createSprite(
					{
						x: (i % this.sep) * w,
						y: Math.floor(i / this.sep) * h,
						width: w,
						height: h
					},
					area,
					area
				));
			}
			return ret;
		}
	}

	/**
	 * オートタイル用ChipSet
	 */
	export class AutoTileChipSet extends ChipSet {
		/**
		 * 座標位置のチップを取得する便利メソッド。Tile管理外座標である場合-1を返す
		 * @param x 取得対象x座標
		 * @param y 取得対象y座標
		 */
		map(x:number, y:number) {
			if (x < 0 || y < 0 || x >= this.tile.size.width || y >= this.tile.size.height)
				return -1;
			return this.tile.data[x][y];
		}

		/**
		 * 描画する
		 * @param c 描画対象context
		 * @param x 描画X座標
		 * @param y 描画Y座標
		 * @param chip 描画するチップ番号。現状オートタイルは1画像1チップのみであるため、本パラメータは利用しない
		 */
		draw(c:CanvasRenderingContext2D, x:number, y:number, chip:number) {
			var tw = this.tile.tileWidth;
			var th = this.tile.tileHeight;
			var tw2 = Math.floor(this.tile.tileWidth/2);
			var th2 = Math.floor(this.tile.tileHeight/2);
			for (var i=0; i < 4; i++) {
				for (var j=0; j < 4; j++) {
					var ox = x + (Math.floor((i + 1) / 2) - 1);
					var oy = y + (Math.floor((j + 1) / 2) - 1);
					var n = this.map(ox, oy);
					if (n == -1)
						continue;

					var tx = ox + (i % 2 == 0 ? 1 : -1);
					var ty = oy + (j % 2 == 0 ? 1 : -1);
					var sel;
					var v = this.map(tx, oy);
					var h = this.map(ox, ty);
					var vh = this.map(tx, ty);
					sel = 0;
					if (h == n)
						sel++;
					if (v == n)
						sel+=2;
					if (sel == 3 && vh == n)
						sel++;

					c.drawImage(
						this.image,
						(sel % this.sep) * tw + tw2 * (i % 2 == 0 ? 1 : 0),
						Math.floor(sel / this.sep) * th + th2 * (j % 2 == 0 ? 1 : 0),
						tw2,
						th2,
						x * tw + tw2 * (i-1),
						y * th + th2 * (j-1),
						tw2,
						th2
					);
				}
			}
		}
	}

	/**
	 * マップを描画するクラス。
	 * 本クラスは高速描画のためdrawOptionを無効化しており、回転などを行いたい場合は一度Sprite化してから行う必要がある点に注意
	 */
	export class Tile extends E {
		/** 一つ一つのチップの横幅 */
		tileWidth:number;
		/** 一つ一つのチップの縦幅 */
		tileHeight:number;
		/** 全チップセットを表す配列。これを直接操作しないように。 */
		chips:ChipSet[];
		/** チップ番号とChipSetを相対させるための便利Map。直接操作しないように。 */
		chipMap: ChipSet[];
		/** 合計のチップ数。直接操作しないように。 */
		chipCount:number;
		/** 描画済みマップを保持しておくためのバッファ用canvas */
		canvas: HTMLCanvasElement;
		/** マップデータ。[x][y]形式で格納 */
		data: number[][];
		/**
		 * このクラスが管理するマップのサイズ。
		 * 常にtile.data.length == tile.size.width、tile.data[0].length == tile.size.heightであるべき値。
		 * ビルドをES5にする際はプロパティにして自動更新する形にした方がいいかも
		 */
		size:CommonSize;

		/**
		 * コンストラクタ
		 * @param image 利用するチップセット画像。nullやundefinedを指定することで省略可
		 * @param tileWidth 一つ一つのチップの横幅
		 * @param tileHeight 一つ一つのチップの縦幅
		 */
		constructor(image:any, tileWidth:number, tileHeight:number) {
			super();
			this.tileWidth = tileWidth;
			this.tileHeight = tileHeight;
			this.chips = [];
			this.chipMap = [];
			this.chipCount = 0;
			if (image)
				this.addChipSet(image);
			this.x = 0;
			this.y = 0;
			this.disableTransform = true;
		}

		/**
		 * チップセットを追加する
		 * @param image 追加するチップセット画像
		 * @param opt オプション。{autoTile: true}を指定すると、オートタイルのチップセットが指定可能
		 */
		addChipSet(image:HTMLImageElement, opt?:any) {
			var chipset;
			if (opt) {
				if (opt.autoTile) {
					chipset =new AutoTileChipSet(this, image);
				}
			}

			if (! chipset)
				chipset = new ChipSet(this, image);			

			chipset.chipOffset = this.chipCount;
			this.chips.push(chipset);
			var cnt = chipset.count();
			var cnt2 = this.chipCount + cnt;
			for (var i=this.chipCount; i<cnt2; i++)
				this.chipMap[i] = chipset;
			this.chipCount = cnt2;
		}

		_clear(width:number, height:number) {
			this.size = {
				width: width,
				height: height
			}
			this.width = this.tileWidth * width;
			this.height = this.tileHeight * height;
		}

		/**
		 * マップデータをすべて0で初期化する
		 * @param width 横幅（チップ数）
		 * @param height 縦幅（チップ数）
		 */
		clear(width?:number, height?:number) {
			if (! width)
				width = this.size.width;
			if (! height)
				height = this.size.height;
			this._clear(width, height);
			this.data = [];
			for (var x=0; x<width; x++) {
				this.data[x] = [];
				for (var y=0; y<height; y++)
					this.data[x][y] = 0;
			}
			this.refresh();
		}

		/**
		 * マップデータを生成する
		 * @param data データの二次元配列。[x][y]型である点に注意
		 * @param width マップの横幅（チップ数）。省略時はdata.lengthが利用される。なおこのパラメータにtrueを指定すると、widthとheightを省略しtranposeをtrueにしたものとして扱われる
		 * @param height マップの縦幅（チップ数）。省略時はdata[0].lengthが利用される
		 * @param transpose 行列を反転させるかどうか
		 */
		generate(data:number[][], width?:any, height?:number, transpose?:boolean) {
			if (width === true) {
				transpose = true;
				width = data.length;
				height = data[0].length;
			}
			if (! width)
				width = data.length;
			if (! height)
				height = data[0].length;

			this.data = transpose ? JGUtil.transpose(data) : data;
			
			this._clear(width, height);
			this.refresh();
		}

		/**
		 * バッファを作り直した上で再描画を行い、更新済みフラグを立てる
		 */
		refresh() {
			this.canvas = window.createCanvas(this.width, this.height);
			var c = this.canvas.getContext("2d");

			for (var x=0; x<this.size.width; x++)
				for (var y=0; y<this.size.height; y++)
					this.drawChip(x, y, false, c);

			this.updated();
		}

		/**
		 * 一マス描画する
		 * @param x 描画対象X座標（チップ数）
		 * @param y 描画対象Y座標（チップ数）
		 * @param clear trueを指定すると描画前にクリアする。半透明チップ描画の場合は必須
		 * @param context 描画対象context。省略時はthis.canvasから自動取得。複数チップを描画する場合、呼び出し元で指定してあげた方が速い
		 */
		drawChip(x:number, y:number, clear?:boolean, context?:CanvasRenderingContext2D) {
			if (context === undefined)
				context = this.canvas.getContext("2d");

			if (clear)
				context.clearRect(
					x * this.tileWidth,
					y * this.tileHeight,
					this.tileWidth,
					this.tileHeight
				);

			if (this.data[x][y] < 0)
				return;
			var cs = this.chipMap[this.data[x][y]];
			cs.draw(context, x, y, this.data[x][y]-cs.chipOffset);
		}

		/**
		 * 描画する
		 * @param context 描画対象context
		 */
		draw(context:CanvasRenderingContext2D) {
			if (! this.canvas)
				return;
			var parent = this.parent ? this.parent : this;
			var scroll = parent.scroll ? parent.scroll : {x: 0, y: 0};
			var src:CommonArea = {
				x: -scroll.x,
				y: -scroll.y,
				width: parent.width,
				height: parent.height
			};
			var dist:CommonArea = {
				x: -scroll.x,
				y: -scroll.y,
				width: parent.width,
				height: parent.height
			};
			if (src.x < 0) {
				src.width += src.x;
				if (src.width <= 0)
					return;
				dist.x -= src.x;
				dist.width += src.x;
				src.x = 0;
			} else if ((src.x+src.width) > this.width) {
				var p = ((src.x+src.width) - this.width);
				src.width -= p;
				if (src.width <= 0)
					return;
				dist.width -= p;
			}
			if (src.y < 0) {
				src.height += src.y;
				if (src.height <= 0)
					return;
				dist.y -= src.y;
				dist.height += src.y;
				src.y = 0;
			} else if ((src.y+src.height) > this.height) {
				var p = ((src.y+src.height) - this.height);
				src.height -= p;
				if (src.height <= 0)
					return;
				dist.height -= p;
			}

			context.drawImage(
				this.canvas,
				src.x,
				src.y,
				src.width,
				src.height,
				dist.x,
				dist.y,
				dist.width,
				dist.height
			);
		}

		/**
		 * このクラスが管理するすべてのマップチップをSpriteの配列で取得する
		 */
		getChips():Sprite[] {
			var ret:Sprite[] = [];
			var len = this.chips.length;
			for (var i=0; i<len; i++)
				ret = ret.concat(this.chips[i].getChips());
			return ret;
		}
	}
}