require("debug.lua");
if (DEBUG_FUNCTION)
then
	require("101_common_oop.lua");
	require("111_logger.lua");
	require("151_common_environment.lua");
	require("152_global_instance.lua");
end


----------------------------------------------------------------------------------------------------------
-- 
-- 引数取れる create_thread 拡張 割と重要。
-- 
----------------------------------------------------------------------------------------------------------
-- 退避
--[[
org_create_thread = create_thread;

-- 引数つき create_thread
function create_thread(...)
	local arguments = {...};
	org_create_thread(functionInvoker, arguments);
end

-- 引数引渡しの関数ラッパー
function functionInvoker(arguments)
	local invokeTarget = unpack(arguments, 1, 1);
	assert( type(invokeTarget) == "function", "第一引数が関数じゃありません。");
	invokeTarget(unpack(arguments, 2));
end
]]--

----------------------------------------------------------------------------------------------------------
-- 
-- スレッド起動・同期制御クラス
-- 
-- yield()コマンドに依存しない形でスレッドの同期処理を行います。
-- 実行される処理が無限ループしてるといつまでたっても帰ってきません。
-- なので一時的な処理分散のためスレッドを利用したい場合に向いています。
--
--	⇒どうも本物のスレッド使ってない模様。
--	　実質シングルスレッドのような気がするので意味がないっちゃない。
--	　create_thread()に引数が取れるようになることを期待して…準備工事。
--
--	覚書
--		メソッド定義の「:」で定義されるFunctionは
--		暗黙変数「self」を第一引数にとる。
--
--		coroutine.yield() と yield()は別物。
--
--		こいつのスレッド制御は Lua標準の coroutine でやってる。
--
--		どうもこいつの呼び出し先で wait() or yield() をやってると
--		帰ってこなくなるので基本演算処理にとどめるべき。
--		（多分waitなどの内部でcoroutine.yield()してないから。）
--
--		なぜか coroutine.yield() しないほうが早い件…わからん
--
--
----------------------------------------------------------------------------------------------------------
SynchronizableThreadExecutor=extends(NamedObject);

	----------------------------------------------------------------------------------------------------------
	-- コンストラクタ
	--	引数
	--		name			: 制御インスタンスの名前
	----------------------------------------------------------------------------------------------------------
	function SynchronizableThreadExecutor:constructor(name)

		self.name=name;				-- 制御インスタンスの名前
		self.executeTargets={};		-- 実行・制御対象インスタンス
		self.executeThreads={};		-- 実行・制御対象のスレッド

	end

	----------------------------------------------------------------------------------------------------------
	-- 処理を追加登録します。
	--	引数
	--		executeTarget	: 実行対象クラスインスタンス
	--						  実行対象クラスインスタンスは以下のメソッドを実装している必要があります。
	--							call() 			実処理。
	--						  一応、上記メソッドを実装したラッパークラスSynchronizableThreadを 
	--						  用意してあるので、そちらを利用することをお勧めします。
	----------------------------------------------------------------------------------------------------------
	function SynchronizableThreadExecutor:add(executeTarget)
		table.insert(self.executeTargets, executeTarget);
	end

	----------------------------------------------------------------------------------------------------------
	-- 登録された処理を別スレッドで実行します。
	-- 全スレッドの終了を待ちたい場合は、本メソッド呼び出し後に必ず
	-- awaitTermination()を呼び出してください。
	----------------------------------------------------------------------------------------------------------
	function SynchronizableThreadExecutor:invokeAll()
		local executeFunction = nil;
		local thread = nil;
		for key, executeTarget in pairs(self.executeTargets)
		do
			-- そのクラスインスタンスが持つ「call」メソッドをとる。
			-- executeTarget:call() 自体をとるイメージ。
			executeFunction = executeTarget["call"];

			-- create_threadだと引数渡せないので coroutine で自前実装。
			-- coroutine.resumeでメソッドに「self」用引数を渡してる。
			thread = coroutine.create(executeFunction);
			table.insert(self.executeThreads, thread);
			coroutine.resume(thread, executeTarget);
			--coroutine.yield();
			--create_thread(executeFunction);
		end
	end

	----------------------------------------------------------------------------------------------------------
	-- 別スレッドで起動された処理の完了を待ちます。
	--	引数
	--		limitFrame		:処理を待つ最大Frame数
	--	戻り値
	--		limitFrame以内に全スレッドが終了した場合true, 
	--		終了しなかった場合false
	----------------------------------------------------------------------------------------------------------
	function SynchronizableThreadExecutor:awaitTermination(limitFrame)
		local startTime = battle_time;
		local elapsedTime = 0;
		while (not self:isTerminatedAll())
		do
			if (elapsedTime > limitFrame)
			then
				logger:warn("SynchronizableThreadExecutor", "スレッド処理が指定期間内に終わりませんでした。");
				break;
			end
			-- 経過Frame取得 緋想天依存のbattle_timeで計測。
			elapsedTime = battle_time - startTime;
			--coroutine.yield();
			logger:trace("SynchronizableThreadExecutor", "計算中 :" .. elapsedTime);
		end
		logger:debug("SynchronizableThreadExecutor", "計算にかかった時間 :" .. elapsedTime);
		return self:isTerminatedAll();
	end

	----------------------------------------------------------------------------------------------------------
	-- 別スレッドで起動された処理が全て終了している場合true
	----------------------------------------------------------------------------------------------------------
	function SynchronizableThreadExecutor:isTerminatedAll()
		local terminatedAll = true;
		for key, executeThread in pairs(self.executeThreads)
		do
			terminatedAll = terminatedAll and (coroutine.status(executeThread) == "dead");
		end
		return terminatedAll;
	end

----------------------------------------------------------------------------------------------------------
-- 
-- スレッド起動・同期制御クラスに登録するためのラッパークラス
-- SynchronizableThreadExecutorと合わせて利用してください。
-- 
----------------------------------------------------------------------------------------------------------
SynchronizableThread=extends(NamedObject);

	----------------------------------------------------------------------------------------------------------
	-- コンストラクタ
	--	引数
	--		instance		: クラスインスタンスかfunctionそのもの
	--		methodName		: クラスインスタンスの場合、呼び出すメソッド名指定
	--		args			: メソッド or 関数に渡す引数, テーブル形式で。
	----------------------------------------------------------------------------------------------------------
	function SynchronizableThread:constructor(instance, methodName, args)
		self.name = "SynchronizableThread : " .. tostring(methodName);
		self.instance = instance;		-- クラスインスタンス or function
		self.methodName = methodName;	-- 実行対象メソッド名
		self.args = args;				-- 引数
	end

	----------------------------------------------------------------------------------------------------------
	-- コンストラクタで指定されたメソッド or 関数を呼び出します。
	----------------------------------------------------------------------------------------------------------
	function SynchronizableThread:call()
		local executeFunction = self:getExecuteFunction();
		if (self.args ~= nil)
		then
			if (type(self.args) == "table")
			then
				executeFunction(self.instance, unpack(self.args));
			else
				executeFunction(self.instance, self.args);
			end
		else
			executeFunction(self.instance);
		end
		--logger:trace("SynchronizableThread", "計算終わったよ:" .. self.methodName);
		--coroutine.yield();
	end

	----------------------------------------------------------------------------------------------------------
	-- 関数 or クラスインスタンスのメソッドを取得します。
	-- クラスインスタンスは実質tableオブジェクトなので
	-- rawget(table, index)でメソッドの実体を取り出します。
	----------------------------------------------------------------------------------------------------------
	function SynchronizableThread:getExecuteFunction()
		if (type(self.instance) == "table")
		then
			return self.instance[self.methodName];
		elseif (type(self.instance) == "function")
		then
			return self.instance;
		end
		-- 実行できる状態じゃないのでエラーにする。
		assert(false, "クラスインスタンスでも関数でもないから実行できまっせん。");
	end

----------------------------------------------------------------------------------------------------------
-- テスト用
----------------------------------------------------------------------------------------------------------
function main()
	test_thread();
	while (true)
	do
		yield();
	end
end
function test_thread()

	local testA = TestA.new("A");
	local testC = TestA.new("C");
	local executeThreadA = SynchronizableThread.new(testA, "testPrint", {"aaa", 22});
	local executeThreadB = SynchronizableThread.new(testB);
	local executeThreadC = SynchronizableThread.new(testC, "testPrint", "bbb");

	local executor = SynchronizableThreadExecutor.new("hoge");

	executor:add(executeThreadA);
	executor:add(executeThreadB);
	executor:add(executeThreadC);

	executor:invokeAll();
	print(executor:awaitTermination(1));

end

TestA=extends(NamedObject);

	function TestA:constructor(name)
		self.name=name;
	end

	function TestA:testPrint(str, ex)
		for i=1,5
		do
			print(self.name .. " Test じっこうするよー" .. tostring(str) .. tostring(ex));
		end
	end

function testB()
	for i=1,6
	do
		print("testBじっこうするよー");
	end
end
