module y4d_thread.gametask;

private import std.gc;
private import std.outofmemory;
private import std.thread;

private import y4d_draw.screen;
private import y4d_aux.stringconv;
private import ytl.fastlist;

private import yamalib.log.log;

///	ゲーム用のタスクの基底クラス
/**
	GameTaskControllerで用いるためのタスク基底クラス
<PRE>
int main(){
	GameTaskController controller = new GameTaskController(100);

	//	ここではタスクを直接ここに書いているが、実際は
	//	何らかのクラス、あるいは事前に生成してpoolしておいたものを用いる。
	controller.addTask(
		new SimpleGameTask(
			delegate int (Object o){
				static int i =0;
				++i;
				printf("%d\n",i);
				if (i==5) {
					GameTaskController c = cast(GameTaskController)o;
					c.terminate(); // コントローラーに終了通知
				}
				return 0; // 非0を返せば、このタスクは消される
			}
		)
		, 123 // task priority
	);

	while (!controller.isEnd()) {
		controller.print();	//	debug用
		controller.callTask(controller);
		//	ここではcontrollerを引数として渡しているが、
		//	実際は、画面まわりや入力まわりなどタスク側で必要となるものを
		//	集積させたクラスを渡すべき
	}
	return 0;
}
</PRE>
*/
class GameTaskBase {

	///	呼び出されるべきタスク
	/**
		派生クラス側で、これをオーバーライドして使う。
		この引数には、 GameTaskController.callTask 呼び出し時に
		渡したパラメータが入る。

		非0を返せば、このタスクは消される。
	*/
	abstract int task(Object o);

	abstract int onMove(Object o);

	abstract int onDraw(Object o);
	
	/**
		このタスクを破棄するときに、呼び出す
		必ずしも実装することはないが、GCを呼び出すときにメモリリークを回避するために
		オブジェクトの参照を潰しておくほうがよい
	*/
	void destroy() {
		// TODO 必要であれば実装すること
		Log.print("%s#destroy", this.toString());
	}

	///	デバッグ用にタスク名を返す関数
	/**
		必要ならばオーバーライドして使うと良い。
	*/
	char[] getTaskName() { return cast(char[]) "no name task"; }

	///	デバッグ用にこのタスクの情報を返す
	/**
		必要なせば、オーバーライドして使うと良い。
	*/
	char[] getTaskInfo() { return cast(char[]) "no info"; }

	this() {}
}

///	ゲーム用タスクの簡単な例
/**
	コンストラクタでdelegateを設定しておけば、
	それがtaskになるようなゲーム用タスク。
	Javaのような無名クラスみたく使えばいいんじゃ？
*/
class SimpleGameTask : GameTaskBase {

	this(int delegate(Object) dg) { taskDelegate = dg; }

	int task(Object o) { return taskDelegate(o); }

private:
	int delegate(Object)taskDelegate;
}

///	ゲーム用のタスクコントローラー
/**
	タスクの管理を行ないます。
	タスク( GameTaskBase の派生クラス)を管理するために使います。<BR>

	タスクには優先順位をつけおけば、指定した優先順位において
	呼び出されます。<BR>

	※　実装メモ <BR>

	タスクのlistを持っていてそれを呼び出す。
	ただし、listはnewを行なうので、速度低下を招く可能性がある。
	大量のタスク(100～1000個単位)を扱うときにはこういう実装は
	良くないと思われる。<BR>

	よって少なくともタスクのinsert,removeに際してnewは行なわない
	ようなlist管理の方法が問われることになる。
	これには fastlist テンプレートを用いている。そちらも参照にすると良い。<BR>

	サンプルについては、 GameSceneController の説明のものを参考にどうぞ。
*/

alias fastlist!(GameTaskBase) GameTaskList;
class GameTaskController {
	///	タスクリスト
	/**
		タスクコントローラーのデバッガを作るときなど、何かの時に使うと良い。
	*/


	/// 最大タスク数の設定(必須)
	/**
		listのnewを行なわないような実装になっているので、
		最大タスク数を事前に設定し、その分だけ
		確保しておかなければならない。
	*/
	void	setTaskMax(int n){
		taskList.setMax(n);
	}

	///	すべてのタスクを呼び出す
	/**
		タスクプライオリティ(実行優先順位)に従い、
		すべてのタスクを呼び出します。<BR>

		ここで引数として渡しているObject型は、
		GameTaskBase の task メソッドの引数として渡されます。<BR>

		terminateメンバが呼び出されると、この関数は非0を返すように
		なります。
	*/
	int		callTask(Object o){
		foreach(inout GameTaskList.Node e;taskList){
			if (e.element.task(o))
				e.dead = true; // こいつ殺す
		}
		taskList.collect(); // 死にタスクを回収しておく。
		return bEnd;
	}

	/// onMoveTask だけ呼び出す
	int	callTaskOnMove(Object o) {
		try {
			int i = 0;
			foreach(inout GameTaskList.Node e;taskList){
				try {
					if ( e.element is null ) {
						Log.printLook("Task Is null");
						continue;
					}

					if (e.element.onMove(o)) {
						e.dead = true; // こいつ殺す
					}
				} catch (Exception e) {
					Log.printError("Exception %s#e.element.onMove(o) : loop=[%d] [%s] [%s]\n", this.toString(), i, e.toString(), e.msg);
					throw e;
				}
				++i;
			}

			taskList.collect(); // 死にタスクを回収しておく。
			return bEnd;
		} catch (Exception e) {
			Log.printError("Exception %s#callTaskOnMove :[%s]:[%s]\n", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}

	/// onDrawTask だけ呼び出す
	int	callTaskOnDraw(Object o) {
		try {
			int i = 0;
			foreach(inout GameTaskList.Node e;taskList){
				try {
					if ( e.element is null ) {
						Log.printLook("Task Is null");
						continue;
					}
					
					if (e.element.onDraw(o)) {
						e.dead = true; // こいつ殺す
					}
				} catch (Exception e) {
					Log.printError("Exception %s#e.element.onDraw(o) : loop=[%d] [%s] [%s]\n", this.toString(), i, e.toString(), e.msg);
					throw e;
				}
				++i;
			}
			
			taskList.collect(); // 死にタスクを回収しておく。
			return bEnd;
		} catch (Exception e) {
			Log.printError("Exception %s#callTaskOnDraw :[%s]:[%s]\n", this.toString(), e.toString(), e.msg);
			throw e;
		}
	}


	///	生成したタスクをタスクリストに登録する
	/**
		new したCTaskControlBase派生クラスを渡してチョ
		プライオリティは、タスクの優先度。0だと一番優先順位が高い。
		万が一に備えてマイナスの値を渡しても良いようにはなっている。<BR>

		空きがなくてタスクが生成できなかったときは非0が返る。
		(通常、有ってはならないのだが)
	*/
	int		addTask(GameTaskBase task,int priority){
		return taskList.add(task,priority);
	}

	///	タスクの削除。
	/**
		優先度を指定して、そのタスクを消滅させる。
		自分で自分のタスクを削除することも出来る。
		回収は、次のcallTaskのときに行なわれる
	*/
	void	killTask(int priority){
		taskList.kill(priority);
	}

	///	タスクの一括削除
	/**
		優先度を指定してのタスクの一括削除。
		自分のタスクが含まれても構わない。<BR>

		nStartPriority～nEndPriorityまでを削除する。
		(nEndPriorityも削除対象に含む。)
	*/
	void	killTask(int nStartPriority,int nEndPriority){
		taskList.kill(nStartPriority,nEndPriority);
	}

	///	プライオリティを変更
	/**
		プライオリティを変更する。
		callTaskで呼び出されているタスクが自分自身のプライオリティを
		変更しても構わない。（ように設計されている）
	*/
	void	changePriority(int beforePriority,int afterPriority){
		taskList.kill(beforePriority,afterPriority);
	}

	///	指定したプライオリティのタスクを得る
	/**
		もし指定したプライオリティのタスクがなければnullが戻る。
		プライオリティに対応するタスクが唯一であるような設計をしている
		場合に使うと便利。
	*/
	GameTaskBase getTask(int priority) {
		return taskList.getElement(priority);
	}

	///	指定したプライオリティのタスクがあるかどうか調べる
	/**
		タスクコントローラーみたいなものを用意して、そのタスクが
		存在しなければ次のタスクを生成する、というような使いかたをすれば
		便利じゃないかと．．。
	*/
	bool isExistTask(int priority) {
		return taskList.isExistElement(priority);
	}

	///	終了する場合に呼び出す
	/**
		これを呼び出すと、callTaskの戻り値が非0になります
	*/
	void terminate() { bEnd = true; }

	///	終了するのか？
	/**
		terminateを呼び出されたあとかを判定する
	*/
	bool isEnd() { return bEnd; }

	///	すべてのタスクを表示する
	/**
		これはデバッグ用。

		@todo	グラフィカルなデバッグ環境が欲しいなぁ．．(´Д｀)
	*/
	void	print(){
		bool bFirst = true;
		foreach(GameTaskList.Node e;taskList){
			bFirst = false;

			printf("task priority : %.*s : address %.*s < %.*s\n",
			  StringConv.toConvHelpperU(e.priority,10,8,' '),
			  StringConv.toConvHelpperU(*cast(uint*)(&e.element),16,8,'0'),
			//	uintに直接castできないのかぁ..(´Д｀)
				e.element.getTaskName()
			);
		}
		if (bFirst) { printf("タスク無し\n"); }
	}

	this() {
		taskList = new GameTaskList;
	}

	///	最大タスク数を設定するコンストラクタ
	/**
		引数で渡したタスク数は、 setTaskMax 呼び出しに使われます。
	*/
	this(int nMaxTask) {
		taskList = new GameTaskList;
		setTaskMax(nMaxTask);
	}

	///	タスクリストの取得
	/**
		タスクコントローラーのデバッガを作るときなど、何かの時に使うと良い。
	*/
	GameTaskList	getTaskList() { return taskList; }

private:
	//	終了するならば非0
	bool	bEnd;
	GameTaskList	taskList;	//	タスクリスト
	/*
		見ての通り、ほとんどのメソッドは fastlistに委譲してある。
		fastlist は、ObjectPoolなどにはもってこいのクラスである。
	*/
}

///	taskを生成するためのfactory
/**
	他にtaskをpoolしておく仕組みはObjectPoolテンプレートを用いると良い。
*/
class GameTaskFactoryBase {
	///	factory
	/**
		指定されたプライオリティのタスクを生成して返すfactoryを
		オーバーライドして書く。<BR>

		newで生成しなくとも、事前に生成しておいたpoolから
		割り当てても良い。
	*/
	abstract GameTaskBase createTask(int priority);
}

///	taskを用いたシーンコントローラー
/**
	いわゆる、シーンコントローラー。
	逐次的なタスク実行を実現する。<BR>

	例)
	タスクAを呼び出して、そのあとB,Cを呼び出すことを
	シーンコントローラーに要求。
	タスクBは、その処理のなかでシーンコントローラーにタスクD,Eを
	呼び出すことを要求。

	結果として、タスクは、A,B,D,E,Cと呼び出される。

	以下、サンプル。
<PRE>
//	そのゲームで使う構造体各種よせ集め
class GameInfo {
	GameTaskController gameTaskController;
	GameSceneController gameSceneController;

	//	他、いろいろ持たせる
}

enum Task { Task0,Task1,Task2,Task3,Task4 };

class Task0 : GameTaskBase {
	void task (Object o) {
		c++;
		if (c==5) {
			((GameInfo)o).gameSceneController.jumpScene(Task.Task1);
		}
		printf("Task0\n");
	}
private:
	int c;
}

class Task1 : GameTaskBase {
	void task(Object o) {
		c++;
		if (c==5) {
			((GameInfo)o).gameSceneController.callScene(Task.Task2);
		} else if (c==10) {
			((GameInfo)o).gameSceneController.exitScene();
			//	この呼び出し以降、このTask1.taskは呼び出されない

			//	ゲーム自体を終了させるならば、タスクコントローラーに
			//	要求する必要がある。
			//	例)
			((GameInfo)o).gameTaskController.terminate();
		}
		printf("Task1\n");
	}
private:
	int c;
}

class Task2 : GameTaskBase {
	void task (Object o) {
		c++;
		if (c==5) {
			static int[] a = [ Task.Task4, Task.Task3 ];
			((GameInfo)o).gameSceneController.pushScene(a);
			//	pushしてもすぐに呼び出されるわけではない。
			//	returnSceneしてはじめて呼び出される
			((GameInfo)o).gameSceneController.returnScene();
		}
		printf("Task2\n");
	}
private:
	int c;
}

class Task3 : GameTaskBase {
	void task (Object o) {
		c++;
		if (c==5) {
			((GameInfo)o).gameSceneController.returnScene();
		}
		printf("Task3\n");
	}
private:
	int c;
}

class Task4 : GameTaskBase {
	void task (Object o) {
		c++;
		if (c==5) {
			((GameInfo)o).gameSceneController.returnScene();
		}
		printf("Task4\n");
	}
private:
	int c;
}

class myGameSceneFactory : GameTaskFactoryBase {
	GameTaskBase createTask(int priority) {
		switch (priority) {
		case Task.Task0 : return new Task0;
		case Task.Task1 : return new Task1;
		case Task.Task2 : return new Task2;
		case Task.Task3 : return new Task3;
		case Task.Task4 : return new Task4;
		}
	}
}

int main(){

	GameInfo info = new GameInfo;
	info.gameTaskController = new GameTaskController(100);
	info.gameSceneController = new GameSceneController(int.min);
	info.gameSceneController.setGameTaskFactory(new myGameSceneFactory);

	//	シーンコントローラーもタスクの一部なのでタスクコントローラーに
	//	登録しておく。
	info.gameTaskController.addTask = info.gameSceneController;

	//	最初に実行するタスクをシーンコントローラーに指定
	info.gameSceneController.jumpScene(Task.Task0);

	while (!info.gameTaskController.isEnd()) {
		info.gameTaskController.callTask(info);
	}
	/+
		あるいは単に
	while (!info.gameTaskController.callTask(info)) {

	}
	と書いても良い。
	+/

	return 0;
}
</PRE>
*/
class GameSceneController : GameTaskBase {

	override int onMove(Object o) {
		if (isEnd()) return 1;

		GameTaskBase task = getNowTask();
		if (!task) {
			//	タスクがすべて終了しているので
			//	次のタスクを実行しなければならない。
			int now = popTaskNo();

			task = getGameTaskFactory().createTask(now);
//			task.setPriority(no);
			tasklist ~= task;

			// isEndをfalse通過しているわけでstacklist.length!=0
			Log.print("onMove#Create New Task:%d",now);
		}

		task.onMove(o);
		return 0;
	}

	override int onDraw(Object o) {
		if (isEnd()) return 1;

		GameTaskBase task = getNowTask();
		if (!task) {
			//	タスクがすべて終了しているので
			//	次のタスクを実行しなければならない。
			int now = popTaskNo();
			// isEndをfalse通過しているわけでstacklist.length!=0

			task = getGameTaskFactory().createTask(now);
//			task.setPriority(no);
			tasklist ~= task;
			
			Log.printLook("%s#onDraw : Create New Task:%d. But onDraw method is not exec.",now);
			return 0;
		}

		task.onDraw(o);
		return 0;
	}

	///	GameTaskBaseからのオーバーライド
	override int	task(Object o){
		if (isEnd()) return 1;

		GameTaskBase task = getNowTask();
		if (!task) {
			//	タスクがすべて終了しているので
			//	次のタスクを実行しなければならない。
			int now = popTaskNo();
			// isEndをfalse通過しているわけでstacklist.length!=0
			Log.print("task#Create New Task:%d",now);

			task = getGameTaskFactory().createTask(now);
//			task.setPriority(no);
			tasklist ~= task;
		}

		return task.task(o);
	}

	//--- シーンの制御系

	///	次のシーンに飛ぶ
	void	jumpScene(int nPriority){
		//	現在実行中のシーンを削除
		if (getNowTask()) {	popTask(); }
		pushTaskNo(nPriority);
		//	次にtaskが呼び出されたときにnewされるでしょー
		Log.print("GameSceneController#jumpScene");
		Log.print("tasklist.length : %d",tasklist.length);
		Log.print("stacklist.length : %d",stacklist.length);
	}

	///	呼び出し元のシーンに戻る
	/**
		このときに、pushSceneでスタック上にシーンが積まれていれば、
		そちらを実体化する。
	*/
	void	returnScene(){
		popTask();
		if (stacklist.length){
			int no = popTaskNo();
			GameTaskBase task = getGameTaskFactory().createTask(no);
//			task.setPriority(no);
			tasklist ~= task;
			Log.print("%s#returnScene : create task from stacklist", this.toString());
		}
		Log.print("%s#returnScene", this.toString());
		Log.print("tasklist.length : %d",tasklist.length);
		Log.print("stacklist.length : %d",stacklist.length);
	}

	///	他のシーンを呼び出す(現在実行中のタスク自体は破棄しない)
	/**
		現在実行中のシーン破棄はされません。
		ただし、task関数のなかでは(現在実行中のシーンへreturnSceneなどで
		制御が戻ってくるまでは)呼び出されなくなります。
	*/
	void	callScene(int nPriority){
		GameTaskBase task = getGameTaskFactory().createTask(nPriority);
//		task.setPriority(nPriority);
		tasklist ~= task;
		Log.print("%s#callScene : TASK_ID %d", this.toString(), nPriority);
		Log.print("tasklist.length : %d",tasklist.length);
		Log.print("stacklist.length : %d",stacklist.length);
	}

	void	callScene(int nPriority, inout int delegate() dg){
		IThreadCaller task = cast(IThreadCaller) getGameTaskFactory().createTask(nPriority);
		task.setDelegate(dg);
		tasklist ~= cast(GameTaskBase) task;
	}

	void	callScene(int nPriority, ProgressiveThread th){
		IThreadCaller task = cast(IThreadCaller) getGameTaskFactory().createTask(nPriority);
		task.setThread(th);
		tasklist ~= cast(GameTaskBase) task;
	}

	///	指定したタスクを指定した順番で呼び出す
	/**
		指定したタスクをシーンスタックに積みます。
		(returnSceneされたときに逆順で呼び出します)

		A,B,Cと呼び出したいならば、
			static int [] a = { Cのpriority,Bのpriority,Aのpriority };
			controller.pushScene(a);
		こんな感じ。
	*/
	void	pushScene(int[] a){
		foreach(int i;a)
			stacklist ~= i;
	}

	///	シーンをすべて破棄
	/**
		これを呼び出すと isEnd が true を返すようになります。
	*/
	void	exitScene(){
		try {
			y4d_draw.texture.Texture.getAllTextureBufferSize();
			foreach(inout GameTaskBase task; tasklist) {
				task.destroy();
				Log.print("Task Destroy : %s", task.toString());
				task = null;
			}
			stacklist = null;
			tasklist = null;
			
			Log.printLook("std.gc.fullCollect() START");
			// GCを行うには全てのスレッドが停止している必要がある
			Thread.pauseAll();
			std.gc.fullCollect();
			std.gc.minimize();
			Thread.resumeAll();
			Log.printLook("std.gc.fullCollect() END");
		} catch (OutOfMemoryException e) {
			Log.printError("OutOfMemory %s#exitScene : [%s][%s]", toString(), e.toString(), e.msg);
		} catch (Exception e) {
			Log.printError("Exception %s#exitScene : [%s][%s]", toString(), e.toString(), e.msg);
		}
	}

	///	現在実行しているシーンを取得
	GameTaskBase getNowTask()
	{
		if (tasklist.length == 0) return null;
		return tasklist[tasklist.length-1];
	}

	///	呼び出すべきシーンがもうないのか？
	bool isEnd() {
		return cast(bool) (stacklist.length==0 && tasklist.length==0);
	}

	///	GameTaskFactoryBaseを設定して使う(必須)
	/**
		このシーンタスクコントローラーから呼び出しうるシーン(task)の
		createのみをサポートしてあれば良い。
	*/
	void	setGameTaskFactory(GameTaskFactoryBase c)
		{ gameTaskFactory = c; }

	///	GameTaskFactoryBaseを取得する
	GameTaskFactoryBase getGameTaskFactory()
		{ return gameTaskFactory; }
		
	/// コンストラクタ
	this() {
	}

protected:
	GameTaskController gameTaskController;
	GameTaskFactoryBase gameTaskFactory;

private:
	/*
		逐次実行の実現のためには、スタックさえあればそれで良い。
	*/
	int[] stacklist;			//	予約キュー
	GameTaskBase[] tasklist;	//	タスクのcallstack
	int popTaskNo() {
		int no = stacklist[length-1];
		stacklist.length = stacklist.length-1;
		return no;
	}
	void pushTaskNo(int nPriority) { stacklist ~= nPriority; }
	void popTask() {
		if (tasklist.length == 0) return ;
		try {
			tasklist[tasklist.length-1].destroy();
			tasklist[tasklist.length-1] = null;
			tasklist.length = tasklist.length-1;
/+			
			Log.printLook("std.gc.fullCollect() START");
			Thread.pauseAll();
			std.gc.fullCollect();
			std.gc.minimize();
			Thread.resumeAll();
			Log.printLook("std.gc.fullCollect() END");
+/
		} catch (OutOfMemoryException e) {
			Log.printError("OutOfMemory %s#exitScene : [%s][%s]", toString(), e.toString(), e.msg);
		} catch (Exception e) {
			Log.printError("Exception %s#popTask : [%s][%s]", toString(), e.toString(), e.msg);
		}
	}
}


/// 進捗表示できるクラスのためのインターフェース
interface ProgressiveThread {
	/// 進捗の値を返却
	int getProgress();
	/// スレッドで処理したい動作を記述したデリゲードを返却
	int delegate() getDelegate();
	/// ローディングスレッド中の描画処理のゲリゲードを返却
	int delegate(Screen) getDrawDelegate();
	
}

/// スレッドを設定することができるタスクの設定
interface IThreadCaller {
	void setDelegate(inout int delegate() dg);
	void setThread(ProgressiveThread th);
}