//
//	clipact.cs
//
//	アクターオブジェクト
//
//	CLiP - Common Library for P/ECE
//	Copyright (C) 2017 Naoyuki Sawa
//
//	* Mon Mar 06 23:24:18 JST 2017 Naoyuki Sawa
//	- 1st リリース。
//	* Mon Mar 13 21:21:21 JST 2017 Naoyuki Sawa
//	- clipprp.csモジュールにユーティリティ関数を実装した事に伴い、アクターオブジェクトもプロパティテーブルに対応しました。
//	- ユーティリティマクロは、C#言語仕様上難しいので、未対応です。
//	  コルーチンは、C#版のucontextを実装出来ないので、未対応です。
//	* Sun May 14 23:23:22 JST 2017 Naoyuki Sawa
//	- ST_DirSeq構造体を変更した事に伴い、clipact.csモジュールも変更しました。
//	  2017/05/13にclipact.cに対して行った変更を、C#版にも適用したものです。
//
using System;
using System.Collections.Generic;
using System.Reflection;
namespace org.piece_me {
	public static partial class libclip {
		//*****************************************************************************
		//	構造体
		//*****************************************************************************
		//ActObj_FromFsmObj()の処理のために、ST_ActObjへのポインタを追加したST_FsmObj構造体です。
		//C言語版ではアドレス計算によってST_FsmObj⇒ST_ActObjへのポインタ変換を行っていましたが、C#版では同じ方法が使えないのでこうしました。
		//元々、FsmObj_Init()が自身でオブジェクトを生成せず渡されたオブジェクトを初期化すると言う仕様なのを利用した、上手い方法だと思います。
		private class ST_FsmObj_In_ActObj : ST_FsmObj {
			public ST_ActObj	pActObj;
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//ActObj_FromDirSeq()の処理のために、ST_ActObjへのポインタを追加したST_DirSeq構造体です。
		//C言語版ではアドレス計算によってST_DirSeq⇒ST_ActObjへのポインタ変換を行っていましたが、C#版では同じ方法が使えないのでこうしました。
		//元々、DirSeq_Init()が自身でオブジェクトを生成せず渡されたオブジェクトを初期化すると言う仕様なのを利用した、上手い方法だと思います。
		private class ST_DirSeq_In_ActObj : ST_DirSeq {
			public ST_ActObj	pActObj;
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		public class ST_ActObj {
			public GNode		node;
	//未対応	coroutine_t		co;
			public ST_FsmObj	stFsmObj;	//実際にはST_FsmObj_In_ActObjです。
			public ST_DirSeq	stDirSeq;	//実際にはST_DirSeq_In_ActObjです。
			public ST_PrpHdr	stPrpHdr;
			public object		aData;
		}
		//-----------------------------------------------------------------------------
		public struct ST_ActObj_Iter {
			public ST_ActObj	pNext;
		}
		//*****************************************************************************
		//	基本的な関数
		//*****************************************************************************
		public class ST_ActObj_New_Param : ICloneable{	//ActObj_New()にて複製する必要が有るので、ICloneableインターフェイスを実装します。
			//親オブジェクト
			public ST_ActObj	pParent;							//null可
	//未対応	//コルーチン
	//未対応	public Action<ST_ActObj/*pActObj*/,object/*coData*/>	coFunc;				//null可
	//未対応	public object		coData;								//(coFunc=null)ならば参照しない。
	//未対応	public int		coStackSize;							//(coFunc=null)ならば参照しない。
			//有限状態機械
			public VoidPtr		pFsmKey;							//null可
			public int		iFsmMap;							//(pFsmKey=null)ならば参照しない。
			public object		pFsmInitParam;							//(pFsmKey=null)ならば参照しない。
			//プロパティテーブル
			public VoidPtr		pPrpKey;							//null可
			public int		iPrpTbl;							//(pPrpKey=null)ならば参照しない。
			//演出シーケンサ
			public Action<ST_DirSeq/*pDirSeq*/,int/*iCh*/,int/*ActNo*/,bool/*bOverFlow*/>	fnAct;	//(nDirSeqCh=0)ならば参照しない。
			public int		nDirSeqCh;							//0可
			public int		nDirSeqCtx;							//0可					//{{2017/05/14追加:ST_DirSeq構造体を変更した事に伴い、clipact.csモジュールも変更しました。}}
			//アプリケーション定義のデータの型
			public Type		pDataType;							//null可
			//-----------------------------------------------------------------------------
			//ICloneable
			public object Clone() { return MemberwiseClone(); }
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		public static ST_ActObj ActObj_New(ST_ActObj_New_Param pParam/*null可*/) {
			ST_ActObj pActObj;
			ST_ActObj_New_Param stParam;
			if(pParam != null) {
				stParam = (ST_ActObj_New_Param)pParam.Clone();	//pParam引数が指定されたら、ローカル構造体にコピーする。(この後の処理で書き換える場合があるため。)
			} else {
				stParam = new ST_ActObj_New_Param();		//pParam引数が指定されなければ、全てのフィールドがクリアされた構造体が指定されたのと同じと見なす。
			}
			//アプリケーションがpFsmKeyを指定しなかった場合は、ダミーとして、何もしないステートを持つマップを使って初期化する事にしました。
			if(!stParam.pFsmKey) {
				//-------------------------------------
				//□FsmMap.txt
				//│Map0 State0
				//│     State0
				//│            default State0
				//│     end
				//│end
				//- - - - - - - - - - - - - - - - - - -
				//□RegFsmMap.txt
				//│FsmMap
				//│       Map0
				//│            State0
				//│                   default
				//│                           Fsm_State(3N+2) = State0
				//│                   end
				//│            end
				//│            Fsm_Init
				//│                   Fsm_InitState = State0
				//│            end
				//│       end
				//│FsmMap
				//- - - - - - - - - - - - - - - - - - -
				byte[] TBL_FsmMap_dummy = unchecked(new byte[] {
				0x80,
				0x00,0x00,
				(byte)((           0x0000)>>8),(byte)(           0x0000),0x00,0x00,0x00,	//Map0
				0x80,
				0x00,0x01,
				(byte)((           0x8000)>>8),(byte)(           0x8000),0x00,0x00,0x05,	//State0
				(byte)((         Fsm_Init)>>8),(byte)(         Fsm_Init),0x00,0x00,0x08,	//Fsm_Init
				0x80,
				0x00,0x00,
				(byte)((Fsm_Trans_default)>>8),(byte)(Fsm_Trans_default),0x00,0x00,0x00,	//Fsm_Trans_default
				0x40,
				0x00,0x00,
				(byte)((    Fsm_InitState)>>8),(byte)(    Fsm_InitState),0x00,0x00,0x00,	//Fsm_InitState = State0	Fsm_State(3N+2) = State0	バイナリ表現はどちらも'2 = 0'なので共通化出来る。
				});
				//-------------------------------------
				stParam.pFsmKey = TBL_FsmMap_dummy;
				stParam.iFsmMap = 0;	//Map0
			}
			//アプリケーションがpPrpKeyを指定しなかった場合は、ダミーとして、空のテーブルを使って初期化する事にしました。
			if(!stParam.pPrpKey) {
				//-------------------------------------
				//□PrpTbl.txt
				//│Table0
				//│    (empty)
				//│end
				//- - - - - - - - - - - - - - - - - - -
				//□RegPrpTbl.txt
				//│PrpTbl
				//│    Table0
				//│        len = 1-1	//本当は(len=0-1)としたいがレジストリ構造上出来ない。1bit(切り上げて1word)分無駄に確保してしまうが動作上は問題無い。
				//│    end
				//│end
				//- - - - - - - - - - - - - - - - - - -
				byte[] TBL_PrpTbl_dummy = unchecked(new byte[] {
				0x80,
				0x00,0x00,
				(byte)((           0x0000)>>8),(byte)(           0x0000), 0x00,0x00,0x00,	//Table0
				0x40,
				0x00,0x00,
				(byte)((          Prp_len)>>8),(byte)(          Prp_len), 0x00,0x00,0x00,	//len = 1-1
				});
				//-------------------------------------
				stParam.pPrpKey = TBL_PrpTbl_dummy;
				stParam.iPrpTbl = 0;	//Table0
			}
			//アクターオブジェクトのメモリを確保する。
			pActObj = new ST_ActObj();
			//親オブジェクトが指定されていたら…
			if(stParam.pParent != null) {
				//子ノードを作成する。
				pActObj.node = g_node_append_data(stParam.pParent.node, pActObj);
			//親オブジェクトが指定されていなければ…
			} else {
				//ルートノードを作成する。
				pActObj.node = g_node_new(pActObj);
			}
//{{C#版特有の処理
			//【C#版特有の処理】アプリケーション定義のデータを作成する。
			//アプリケーション定義のデータの型が指定されていたら…
			if(stParam.pDataType != null) {
				//指定された型のデフォルトコンストラクタを呼び出して、アプリケーション定義のデータを作成する。
				// - 指定された型にデフォルトコンストラクタが無い場合は、Type.InvokeMember()が例外を投入する。
				pActObj.aData = stParam.pDataType.InvokeMember(null, BindingFlags.CreateInstance, null, null, null);
			}
//}}C#版特有の処理
//{{2017/05/14追加:ST_DirSeq構造体を変更した事に伴い、clipact.csモジュールも変更しました。
//			//演出シーケンサを初期化する。
//			pActObj.stDirSeq = new ST_DirSeq_In_ActObj() { pActObj = pActObj };	//【C#版特有の処理】ST_DirSeq⇒ST_ActObjへのポインタを格納する。
//			DirSeq_Init(pActObj.stDirSeq, stParam.fnAct, stParam.nDirSeqCh);
//↓2017/05/14追加:ST_DirSeq構造体を変更した事に伴い、clipact.csモジュールも変更しました。
			//演出シーケンサを初期化する。
			pActObj.stDirSeq = new ST_DirSeq_In_ActObj() { pActObj = pActObj };	//【C#版特有の処理】ST_DirSeq⇒ST_ActObjへのポインタを格納する。
			DirSeq_Init(pActObj.stDirSeq, stParam.fnAct, stParam.nDirSeqCh, stParam.nDirSeqCtx);
//}}2017/05/14追加:ST_DirSeq構造体を変更した事に伴い、clipact.csモジュールも変更しました。
			//プロパティヘッダを初期化する。
			pActObj.stPrpHdr = PrpHdr_new(stParam.pPrpKey, stParam.iPrpTbl);
			//有限状態機械を初期化する。
			// - この処理を一番最後に行う必要が有る事に注意せよ。
			//   有限状態機械のstart関数の中でアクターオブジェクトを操作する可能性が有るので、その前にアクターオブジェクトの初期化が完了していなければいけない。
			pActObj.stFsmObj = new ST_FsmObj_In_ActObj() { pActObj = pActObj };	//【C#版特有の処理】ST_FsmObj⇒ST_ActObjへのポインタを格納する。
			FsmObj_Init(pActObj.stFsmObj, stParam.pFsmKey, stParam.iFsmMap, stParam.pFsmInitParam);
			//アクターオブジェクトを返す。
			return pActObj;
		}
		//-----------------------------------------------------------------------------
		//このアクターオブジェクトと、子アクターオブジェクトを開放する。
		public static void ActObj_Free(ST_ActObj pActObj) {
			ActObj_FreeChildren(pActObj);	//子アクターオブジェクトを解放する。
			g_node_destroy(pActObj.node);	//ノードを解放する。
		}
		//-----------------------------------------------------------------------------
		private static void ActObj_Free_func(GNode node, object data) {
			ST_ActObj pActObj = (ST_ActObj)node.data;
			ActObj_Free(pActObj);
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//子アクターオブジェクトを開放する。
		//このアクターオブジェクトは開放しない。
		public static void ActObj_FreeChildren(ST_ActObj pActObj) {
			g_node_children_foreach(pActObj.node, G_TRAVERSE_ALL, ActObj_Free_func, null);
		}
		//-----------------------------------------------------------------------------
		//親オブジェクトを取得する。
		//親オブジェクトが無ければ(=pFsmObjがルートノードならば)nullを返す。
		// - N-ary Treesの仕様では、親ノードにアクセスするには node.parent を直接参照するだけで良いのですが、
		//   ST_ActObjの場合 (pActObj.node.parent ? pActObj.node.parent.data : null) と長くなってしまい、
		//   アプリケーションの中に何度も書くのは煩わしいので、関数として定義する事にしました。
		// - N-ary Treesの機能を使って実現出来る事は、なるべく当モジュールの関数としては実装しない方針ですが、
		//   上記の理由で、これは、当モジュールの関数として実装しておく方が良いと判断しました。
		public static ST_ActObj ActObj_GetParent(ST_ActObj pActObj) {
			GNode parent = pActObj.node.parent;
			return (ST_ActObj)parent?.data;	//「return (ST_ActObj)((parent != null) ? parent.data : null);」と書いても同じです。
		}
		//-----------------------------------------------------------------------------
		//最初の子アクターオブジェクトを取得する。
		//<使用例>
		//│ST_ActObj_Iter Iter;
		//│ST_ActObj pChild;
		//│for(pChild=ActObj_FirstChild(pActObj,&Iter);
		//│    pChild;
		//│    pChild=ActObj_NextSibling(&Iter)){
		//│      ～  //ここでActObj_Free(pChild)を実行しても安全です。
		//│}
		public static ST_ActObj ActObj_FirstChild(ST_ActObj pActObj, out ST_ActObj_Iter pIter) {
			GNode node = g_node_first_child(pActObj.node);
			pIter.pNext = (ST_ActObj)(node?.data);	//「pIter.pNext = (ST_ActObj)((node != null) ? node.data : null);」と書いても同じです。
			return ActObj_NextSibling(ref pIter);
		}
		//-----------------------------------------------------------------------------
		//次の子アクターオブジェクトを取得する。
		public static ST_ActObj ActObj_NextSibling(ref ST_ActObj_Iter pIter) {
			ST_ActObj pActObj = pIter.pNext;
			if(pActObj != null) {
				GNode node = g_node_next_sibling(pActObj.node);
				pIter.pNext = (ST_ActObj)(node?.data);	//「pIter.pNext = (ST_ActObj)((node != null) ? node.data : null);」と書いても同じです。
			}
			return pActObj;
		}
		//-----------------------------------------------------------------------------
		//ST_ActObjの先頭アドレスから、stFsmObjフィールドのアドレスを取得する。
		public static ST_FsmObj ActObj_GetFsmObj(ST_ActObj pActObj) {
			return pActObj.stFsmObj;
		}
		//-----------------------------------------------------------------------------
		//stFsmObjフィールドのアドレスから、ST_ActObjの先頭アドレスを取得する。
		public static ST_ActObj ActObj_FromFsmObj(ST_FsmObj pFsmObj) {
			return ((ST_FsmObj_In_ActObj)pFsmObj).pActObj;
		}
		//-----------------------------------------------------------------------------
		//ST_ActObjの先頭アドレスから、stDirSeqフィールドのアドレスを取得する。
		public static ST_DirSeq ActObj_GetDirSeq(ST_ActObj pActObj) {
			return pActObj.stDirSeq;
		}
		//-----------------------------------------------------------------------------
		//stDirSeqフィールドのアドレスから、ST_ActObjの先頭アドレスを取得する。
		public static ST_ActObj ActObj_FromDirSeq(ST_DirSeq pDirSeq) {
			return ((ST_DirSeq_In_ActObj)pDirSeq).pActObj;
		}
		//-----------------------------------------------------------------------------
		//ST_ActObjの先頭アドレスから、stPrpHdrフィールドのアドレスを取得する。
		public static ST_PrpHdr ActObj_GetPrpHdr(ST_ActObj pActObj) {
			return pActObj.stPrpHdr;
		}
		//-----------------------------------------------------------------------------
		//ST_ActObjの先頭アドレスから、アプリケーション定義のデータのアドレスを取得する。
		public static object ActObj_GetData(ST_ActObj pActObj) {
			return pActObj.aData;
		}
		//-----------------------------------------------------------------------------
		private class ST_ActObj_ApplyTrans_func_Param {
			public int	iFsmTrans;
			public object	pParam;
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		private static bool ActObj_ApplyTrans_func(GNode node, object data) {
			ST_ActObj pActObj = (ST_ActObj)node.data;
			ST_ActObj_ApplyTrans_func_Param p = (ST_ActObj_ApplyTrans_func_Param)data;	//←ActObj_ApplyTransToChildren(),ActObj_ApplyTransToTree()
			ActObj_ApplyTrans(pActObj, p.iFsmTrans, p.pParam);
			return false;
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//このアクターオブジェクトの有限状態機械に、遷移を適用する。
		public static void ActObj_ApplyTrans(ST_ActObj pActObj, int iFsmTrans, object pParam) {
			FsmObj_ApplyTrans(ActObj_GetFsmObj(pActObj), iFsmTrans, pParam);
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//子アクターオブジェクトの有限状態機械に、遷移を適用する。
		//このアクターオブジェクト,及び,直接の子以外の子孫アクターオブジェクトの有限状態機械には、遷移を適用しない。
		public static void ActObj_ApplyTransToChildren(ST_ActObj pActObj, int iFsmTrans, object pParam) {
			ST_ActObj_ApplyTrans_func_Param p = new ST_ActObj_ApplyTrans_func_Param() { iFsmTrans = iFsmTrans, pParam = pParam };	//→ActObj_ApplyTrans_func()
			g_node_children_foreach(pActObj.node, G_TRAVERSE_ALL, delegate(GNode node, object data) { ActObj_ApplyTrans_func(node, data); }, p);
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//このアクターオブジェクト,及び,子孫アクターオブジェクトの有限状態機械に、遷移を適用する。
		public static void ActObj_ApplyTransToTree(ST_ActObj pActObj, int iFsmTrans, object pParam) {
			ST_ActObj_ApplyTrans_func_Param p = new ST_ActObj_ApplyTrans_func_Param() { iFsmTrans = iFsmTrans, pParam = pParam };	//→ActObj_ApplyTrans_func()
			g_node_traverse(pActObj.node, G_PRE_ORDER, G_TRAVERSE_ALL, -1/*depth*/, ActObj_ApplyTrans_func, p);
			//                            ~~~~~~~~~~~G_PRE_ORDER内でnode自身を削除出来るようになったので、G_POST_ORDERやG_LEVEL_ORDERよりも自然である、G_PRE_ORDERを使用する事にしました。
			//                                       G_PRE_ORDER内でnode自身を削除出来るようになった経緯については、gnode.h内の'Sat Aug 27 21:24:43 JST 2016'のコメントを参照して下さい。
		}
		//-----------------------------------------------------------------------------
		private static bool ActObj_ExecDirSeq_func(GNode node, object data) {
			ST_ActObj pActObj = (ST_ActObj)node.data;
			ActObj_ExecDirSeq(pActObj);
			return false;
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//このアクターオブジェクトの演出シーケンサを実行する。
		public static void ActObj_ExecDirSeq(ST_ActObj pActObj) {
			DirSeq_Exec(ActObj_GetDirSeq(pActObj));
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//子アクターオブジェクトの演出シーケンサを実行する。
		//このアクターオブジェクト,及び,直接の子以外の子孫アクターオブジェクトの演出シーケンサは実行しない。
		public static void ActObj_ExecDirSeqToChildren(ST_ActObj pActObj) {
			g_node_children_foreach(pActObj.node, G_TRAVERSE_ALL, delegate(GNode node, object data) { ActObj_ExecDirSeq_func(node, data); }, null);
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//このアクターオブジェクト,及び,子孫アクターオブジェクトの演出シーケンサを実行する。
		public static void ActObj_ExecDirSeqToTree(ST_ActObj pActObj) {
			g_node_traverse(pActObj.node, G_PRE_ORDER, G_TRAVERSE_ALL, -1/*depth*/, ActObj_ExecDirSeq_func, null);
			//                            ~~~~~~~~~~~G_PRE_ORDER内でnode自身を削除出来るようになったので、G_POST_ORDERやG_LEVEL_ORDERよりも自然である、G_PRE_ORDERを使用する事にしました。
			//                                       G_PRE_ORDER内でnode自身を削除出来るようになった経緯については、gnode.h内の'Sat Aug 27 21:24:43 JST 2016'のコメントを参照して下さい。
		}
		//*****************************************************************************
		//	ユーティリティ関数(有限状態機械)
		//*****************************************************************************
		public static void ActObj_ApplyTrans_l(ST_ActObj pActObj, int iFsmTrans, params object[] args) {
			ActObj_ApplyTrans_v(pActObj, iFsmTrans, args);
		}
		public static void ActObj_ApplyTrans_v(ST_ActObj pActObj, int iFsmTrans, object[] args) {
			ActObj_ApplyTrans(pActObj, iFsmTrans, args);
		}
		//-----------------------------------------------------------------------------
		public static void ActObj_ApplyTransToChildren_l(ST_ActObj pActObj, int iFsmTrans, params object[] args) {
			ActObj_ApplyTransToChildren_v(pActObj, iFsmTrans, args);
		}
		public static void ActObj_ApplyTransToChildren_v(ST_ActObj pActObj, int iFsmTrans, object[] args) {
			ActObj_ApplyTransToChildren(pActObj, iFsmTrans, args);
		}
		//-----------------------------------------------------------------------------
		public static void ActObj_ApplyTransToTree_l(ST_ActObj pActObj, int iFsmTrans, params object[] args) {
			ActObj_ApplyTransToTree_v(pActObj, iFsmTrans, args);
		}
		public static void ActObj_ApplyTransToTree_v(ST_ActObj pActObj, int iFsmTrans, object[] args) {
			ActObj_ApplyTransToTree(pActObj, iFsmTrans, args);
		}
		/****************************************************************************
		 *	ユーティリティ関数(プロパティテーブル)
		 ****************************************************************************/
		static bool ActObj_InitAttr_func(GNode node, object data) {
			ST_ActObj pActObj = (ST_ActObj)node.data;
			IEnumerable<int>[] aAttr = (IEnumerable<int>[])data;
			IEnumerable<int> aIncl = aAttr[0];
			IEnumerable<int> aExcl = aAttr[1];
			PrpHdr_init_attr_v(ActObj_GetPrpHdr(pActObj), aIncl, aExcl);
			return false;
		}
		//-----------------------------------------------------------------------------
		public static void ActObj_InitAttr_v(ST_ActObj pActObj, IEnumerable<int> aIncl, IEnumerable<int> aExcl) {
			ActObj_InitAttr_func(pActObj.node, new IEnumerable<int>[] { aIncl, aExcl });
		}
		//-----------------------------------------------------------------------------
		public static void ActObj_InitAttrToChildren_v(ST_ActObj pActObj, IEnumerable<int> aIncl, IEnumerable<int> aExcl) {
			g_node_children_foreach(pActObj.node, G_TRAVERSE_ALL, delegate(GNode node, object data) { ActObj_InitAttr_func(node, data); }, new IEnumerable<int>[] { aIncl, aExcl });
		}
		//-----------------------------------------------------------------------------
		public static void ActObj_InitAttrToTree_v(ST_ActObj pActObj, IEnumerable<int> aIncl, IEnumerable<int> aExcl) {
			g_node_traverse(pActObj.node, G_PRE_ORDER, G_TRAVERSE_ALL, -1/*depth*/, ActObj_InitAttr_func, new IEnumerable<int>[] { aIncl, aExcl });
			//                            ~~~~~~~~~~~G_PRE_ORDER内でnode自身を削除出来るようになったので、G_POST_ORDERやG_LEVEL_ORDERよりも自然である、G_PRE_ORDERを使用する事にしました。
			//                                       G_PRE_ORDER内でnode自身を削除出来るようになった経緯については、gnode.h内の'Sat Aug 27 21:24:43 JST 2016'のコメントを参照して下さい。
		}
	}
}
