///<reference path="all.ts"/>
module jg {
	/**
	 * キャラクタの移動情報
	 */
	export interface CharacterMoveInfo {
		/** 移動元X座標 */
		x: number;
		/** 移動元Y座標 */
		y: number;
		/** 移動先X座標 */
		dx: number;
		/** 移動先Y座標 */
		dy: number;
		/** 移動に要する時間。歴史的な理由でフレーム数を意味するfという名前になっている */
		f: number;
		/** これまでかかった時間 */
		t: number;
	}

	/**
	 * 移動完了イベントパラメータ
	 */
	export interface CharacterMovedEventArgs {
		/** 次の移動先 */
		nextMove?: string;
	}

	/**
	 * キャラクタをあらわすクラス。
	 * 足踏み、方向、移動といった基本的な概念を持っている。
	 */
	export class Character extends FrameSprite {
		/** 移動中かどうか */
		moving: boolean;
		/** 現在の移動情報 */
		moveInfo: CharacterMoveInfo;
		/** 次の移動先を表す文字列 */
		nextMove: string;
		/** 移動開始イベント。CharacterMoveInfoをパラメータとして持つ */
		beginMove: Trigger;
		/** 移動完了イベント。CharacterMovedEventArgsをパラメータとして持つ */
		moved: Trigger;
		/** キャラクター番号 */
		charaSeq: number;
		/** 画像のキャラクタ列数 */
		charaCol: number;
		/** 足踏みのアニメーションフレーム数 */
		animeCnt: number;
		/** 一度の移動で移動するピクセル数 */
		movePixel: number;
		/** 移動に要する時間 */
		moveTime: number;
		/** キャラクタの方向とキャラチップの番号の相対表。{[key:Angle]: number; }形式で指定 */
		angleSeq: any;
		/** 現在の方向 */
		currentAngle: Angle;

		/**
		 * コンストラクタ
		 * @param image キャラクタの画像。複数のキャラクタが一つになった画像を指定することも可能
		 * @param width キャラクターの横幅
		 * @param height キャラクターの縦幅
		 * @param wait 足踏みに要する時間をミリ秒単位で指定。省略時は200ミリ秒
		 */
		constructor(image:any, width:number, height:number, wait?:number) {
			super(image, width, height, wait);

			this.moving = false;
			if (! wait)
				wait = 200;

			this.animeCnt = 2;
			this.charaSeq = 0;
			this.charaCol = 1;
			this.movePixel = 64;
			this.moveTime = 300;

			this.animate();
		}

		/**
		 * キャラクターが移動していない場合、左に移動させる
		 * @param stackNext 移動が出来ず、前の移動からある程度時間が経っていたら、次の移動先にこの方向をセットする
		 */
		moveLeft(stackNext?:boolean) {
			if (this.move(-this.movePixel, 0, this.moveTime)) {
				this.angle(Angle.Left);
				return true;
			}
			if (stackNext && this.moveInfo.t*2 >= this.moveInfo.f)
				this.nextMove = "Left";
			return false;
		}

		/**
		 * キャラクターが移動していない場合、右に移動させる
		 * @param stackNext 移動が出来ず、前の移動からある程度時間が経っていたら、次の移動先にこの方向をセットする
		 */
		moveRight(stackNext?:boolean) {
			if (this.move(this.movePixel, 0, this.moveTime)) {
				this.angle(Angle.Right);
				return true;
			}
			if (stackNext && this.moveInfo.t*2 >= this.moveInfo.f)
				this.nextMove = "Right";
			return false;
		}

		/**
		 * キャラクターが移動していない場合、上に移動させる
		 * @param stackNext 移動が出来ず、前の移動からある程度時間が経っていたら、次の移動先にこの方向をセットする
		 */
		moveUp(stackNext?:boolean) {
			if (this.move(0, -this.movePixel, this.moveTime)) {
				this.angle(Angle.Up);
				return true;
			}
			if (stackNext && this.moveInfo.t*2 >= this.moveInfo.f)
				this.nextMove = "Up";
			return false;
		}

		/**
		 * キャラクターが移動していない場合、下に移動させる
		 * @param stackNext 移動が出来ず、前の移動からある程度時間が経っていたら、次の移動先にこの方向をセットする
		 */
		moveDown(stackNext?:boolean) {
			if (this.move(0, this.movePixel, this.moveTime)) {
				this.angle(Angle.Down);
				return true;
			}
			if (stackNext && this.moveInfo.t*2 >= this.moveInfo.f)
				this.nextMove = "Down";
			return false;
		}

		/**
		 * キャラクターを移動させる
		 * @param x 移動先X座標
		 * @param y 移動先Y座標
		 * @param f 移動に要する時間
		 */
		move(x:number, y:number, f:number) {
			if (this.moving)
				return false;

			this.moving = true;
			this.moveInfo = {
				x: this.x,
				y: this.y,
				dx: this.x + x,
				dy: this.y + y,
				f: f,
				t: 0
			}
			if (this.beginMove)
				this.beginMove.fire(this.moveInfo);

			if (this.moving)
				this.start();

			return true;
		}

		/**
		 * 毎フレーム更新処理のイベントハンドラ
		 * @param t  経過時間
		 */
		update(t:number) {
			if (this.moving) {
				this.moveInfo.t += t;
				if (this.moveInfo.t < this.moveInfo.f) {
					this.moveTo(
						this.moveInfo.x + Math.round((this.moveInfo.dx - this.moveInfo.x) / this.moveInfo.f * this.moveInfo.t),
						this.moveInfo.y + Math.round((this.moveInfo.dy - this.moveInfo.y) / this.moveInfo.f * this.moveInfo.t)
					);
				} else {
					this.moveTo(this.moveInfo.dx, this.moveInfo.dy);
					this.endMove();
				}
			}
		}

		/**
		 * 移動完了処理を行う
		 */
		endMove() {
			this.moving = false;
			this.stop();
			var e:CharacterMovedEventArgs = {}
			if (this.nextMove) {
				e.nextMove = this.nextMove;
				delete this.nextMove;
			}
			if (this.moved)
				this.moved.fire(e);
			if (e.nextMove)
				this["move"+e.nextMove]();
		}

		/**
		 * 方向を変更する
		 * @param angle 変更後の方向
		 */
		angle(angle:Angle) {
			this.currentAngle = angle;
			var rowP = Math.floor(this.charaSeq / this.charaCol) * 4;

			switch (angle) {
			case Angle.Up:
				rowP += (this.angleSeq ? this.angleSeq[Angle.Up] : 3);
			break;
			case Angle.Down:
				rowP += (this.angleSeq ? this.angleSeq[Angle.Down] : 0);
			break;
			case Angle.Left:
				rowP += (this.angleSeq ? this.angleSeq[Angle.Left] : 1);
			break;
			case Angle.Right:
				rowP += (this.angleSeq ? this.angleSeq[Angle.Right] : 2);
			break;
			}

			var f = this.animeCnt * (this.charaSeq % this.charaCol) + this.charaCol * this.animeCnt * rowP;
			this.frame = [];
			if (this.animeCnt % 2 == 1) {
				for (var i=0; i<this.animeCnt; i++)
					this.frame.push(i+f);
				for (var i=this.animeCnt-2; i>0; i--)
					this.frame.push(i+f);
			} else {
				for (var i=0; i<this.animeCnt; i++)
					this.frame.push(i+f);
			}
			this.changeFrame();
		}
	}
}
