//
//	clipcbkq.cs
//
//	コールバックキュー
//
//	CLiP - Common Library for P/ECE
//	Copyright (C) 2017 Naoyuki Sawa
//
//	* Wed Mar 08 22:12:10 JST 2017 Naoyuki Sawa
//	- 1st リリース。
//	- C言語版には静的コールバックキューと動的コールバックキューが有りましたが、C#版では動的コールバックキューのみとしました。
//	  それに伴って、C#版ではStaticCbkQue_New(),StaticCbkQue_Init(),DynamicCbkQue_New()を廃止して、CbkQue_New()に統一しました。
//	- C言語版とC#版ではメモリ管理の方法が違うので、C#版ではCbkQue_Free(),CbkQue_AddMem()を廃止しました。
//	  また、コールバックキュー構造体の中で、メモリプールを保持する必要も無くなりました。
//	- C言語版でのST_DynamicCbkQue.nodeTailに相当するフィールドは、C#版では必要無くなりました。
//	  .NET FrameworkのList<T>はList<T>.Clear()を呼び出してもメモリを開放しないようなので、毎回Clear()を呼び出しても毎回メモリ確保が生じる事は無いと思ったからです。
//	  □参照資料:「List<T>.Clearメソッド()」(https://msdn.microsoft.com/ja-jp/library/dwb5h52a(v=vs.110).aspx)
//	  │Capacity remains unchanged.
//	  │To reset the capacity of the List<T>, call the TrimExcess method or set the Capacity property directly.
//	* Fri Mar 10 22:30:11 JST 2017 Naoyuki Sawa
//	- CbkQue_InvokePri()にて、(優先度|登録順序)が同じノードを比較しても、エラー停止しないように変更しました。
//	  C言語版のqsort_r()の動作では、同じノードを比較する事は無いので、もし(優先度|登録順序)が同じノードを比較しようとしたら、バグと見なしてエラー停止していました。
//	  しかし、C#のList<>.Sort()の動作は、同じノードを比較する事が有るようです。(実験より)
//	  従って、CbkQue_InvokePri()にて、(優先度|登録順序)が同じノードを比較しても、エラー停止せず0を返すように変更しました。
//
using System;
using System.Collections.Generic;
namespace org.piece_me {
	public static partial class libclip {
		//*****************************************************************************
		//	構造体
		//*****************************************************************************
		//コールバックキュー	C言語版での動的コールバックキューに相当します。
		public class ST_CbkQue {
			public List<ST_CbkQueNode>	nodeArray;	//ノード配列
		}
		//-----------------------------------------------------------------------------
		//ノード
		public struct ST_CbkQueNode {
			public int			priOrd;		//[31:16]=優先度(符号付き),[15:0]=登録順序(符号無し)。[31:0]を符号付き(INT_MIN～INT_MAX)と見なして昇順にソートする。
			public Action<ST_CbkQue/*pCbkQue*/,int/*pri*/,object/*param*/>	fn;	//関数ポインタ
			public object			param;		//アプリケーション定義の任意のパラメータ
		}
		//*****************************************************************************
		//	グローバル関数
		//*****************************************************************************
		//コールバックキューを、動的に作成する。
		public static ST_CbkQue CbkQue_New() {
			ST_CbkQue pCbkQue;
			//構造体のメモリを確保する。
			pCbkQue = new ST_CbkQue();
			//ノード配列を作成する。
			pCbkQue.nodeArray = new List<ST_CbkQueNode>();
			//コールバックキューを返す。
			return pCbkQue;
		}
		//-----------------------------------------------------------------------------
		public static void CbkQue_Clear(ST_CbkQue pCbkQue) {
			//ノード配列を空に戻す。
			pCbkQue.nodeArray.Clear();
		}
		//-----------------------------------------------------------------------------
		public static void CbkQue_Add(ST_CbkQue pCbkQue, int pri/*SHRT_MIN～SHRT_MAX*/, Action<ST_CbkQue/*pCbkQue*/,int/*pri*/,object/*param*/> fn, object param) {
			int priOrd;
			ST_CbkQueNode pNode;
			//優先度(符号付き)の下位16ビットに、登録順序(符号無し)の下駄を履かせる。
			if(((pri < short.MinValue) || (pri > short.MaxValue)) ||
			   (pCbkQue.nodeArray.Count > ushort.MaxValue)) { throw new ApplicationException(); }
			priOrd = (pri << 16) | pCbkQue.nodeArray.Count;
			//今回登録するノードの内容を設定する。
			pNode.priOrd	= priOrd;
			pNode.fn	= fn;
			pNode.param	= param;
			//ノードを登録する。
			pCbkQue.nodeArray.Add(pNode);
		}
		//-----------------------------------------------------------------------------
		public static void CbkQue_Invoke(ST_CbkQue pCbkQue) {
			CbkQue_InvokePri(pCbkQue, short.MinValue, short.MaxValue);
		}
		//-----------------------------------------------------------------------------
		//登録されているノードのうち、優先度がpriMin以上,priMax以下のノードだけを呼び出す。
		public static void CbkQue_InvokePri(ST_CbkQue pCbkQue, int priMin/*SHRT_MIN～SHRT_MAX*/, int priMax/*SHRT_MIN～SHRT_MAX*/) {
			int priOrdMin, priOrdMax;
			//優先度(符号付き)の最大値,最小値の下位16ビットに、登録順序(符号無し)の下駄を履かせる。
			if(((priMin < short.MinValue) || (priMin > short.MaxValue)) ||
			   ((priMax < short.MinValue) || (priMax > short.MaxValue))) { throw new ApplicationException(); }	//ここで停止したら呼び出し側のバグです。
			priOrdMin = (priMin << 16) |               0;
			priOrdMax = (priMax << 16) | ushort.MaxValue;
			//登録されているノードを、(優先度|登録順序)によってソートする。
			// - (priOrdMin,priOrdMin+1,...,priOrdMax-1,priOrdMax,priOrdMax+1,...INT_MAX,INT_MIN,...,priOrdMin-1)という順になる。
			//    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~この範囲を呼び出す。
			pCbkQue.nodeArray.Sort(delegate(ST_CbkQueNode _x, ST_CbkQueNode _y) {
					uint x = (uint)(_x.priOrd - priOrdMin);	//(優先度|登録順序)をpriOrdMinベース,符号無しに変換する。
					uint y = (uint)(_y.priOrd - priOrdMin);	//(優先度|登録順序)をpriOrdMinベース,符号無しに変換する。
					if(x < y) { return -1; }		//符号無し比較
					if(x > y) { return  1; }		//符号無し比較
//{{2017/03/10変更:CbkQue_InvokePri()にて、(優先度|登録順序)が同じノードを比較しても、エラー停止しないように変更しました。
//					throw new ApplicationException();	//(優先度|登録順序)が同じノードは生じないはずです。もしここで停止したら当モジュールのバグです。
//↓2017/03/10変更:CbkQue_InvokePri()にて、(優先度|登録順序)が同じノードを比較しても、エラー停止しないように変更しました。
					return 0;
//}}2017/03/10変更:CbkQue_InvokePri()にて、(優先度|登録順序)が同じノードを比較しても、エラー停止しないように変更しました。
				});
			//(優先度|登録順序)の昇順に…
			foreach(ST_CbkQueNode pNode in pCbkQue.nodeArray) {
				//このノードの(優先度|登録順序)が、指定された(優先度|登録順序)の範囲を外れたら、抜ける。
				if((pNode.priOrd < priOrdMin) || (pNode.priOrd > priOrdMax)) { break; }
				// ~~~~~~~~~~~~~~~~~~~~~~~~~~こちらの条件も必要である事に注意せよ。(priMax>SHRT_MIN),かつ,(priMax=SHRT_MAX)が指定された場合、ソート後の並びが(pNode.priOrd>priOrdMax)を満たす前に、pNode.priOrdがINT_MAX⇒INT_MINに折り返す可能性が有る。
				//このノードの関数を呼び出す。
				pNode.fn.Invoke(pCbkQue, (pNode.priOrd >> 16), pNode.param);
				//                       ~~~~~~~~~~~~~~~~~~~~(優先度|登録順序)⇒優先度に戻して関数へ渡す。
			}
			//ここで自動的にCbkQue_Clear()を *呼び出さない* 事にしました。
			//毎回コールバックキューを登録するか,又は,一度登録したコールバックキューを何度も使い回すかは、アプリケーション次第だと思ったからです。
			//毎回コールバックキューを登録する場合は、アプリケーションが明示的にCbkQue_Invoke()の直後でCbkQue_Clear()を呼び出して下さい。
		}
	}
}
