using System;
using System.Threading.Tasks;
using RTedge;   // RT-edge system API
using System.Runtime.CompilerServices;
using System.Threading;

namespace EdgeCode
{
    /// <summary>
    /// RT-edge API/フレームワーク 各種値定義クラス
    /// </summary>
    public class EDGECONST
    {
        /// <summary>
        /// 各種定義追加
        /// </summary>

        public const string TAG_PREFIX = "SERVICE.";            // サービスプロパティタグ名先頭に付与する文字列
        public const bool SERVICE_UP = true;                    // 起動中
        public const bool SERVICE_DOWN = false;                 // 停止中
        public const int MODE_AUTO = 1;                         // 動作モード 入出自動サイクル
        public const int MODE_SEMIAUTO = 2;                     // 動作モード 入力サイクル＋出力デマンド
        public const int MODE_MANUAL = 3;                       // 動作モード 入出力デマンド
        public const int DEFAULT_MODE = MODE_SEMIAUTO;          // 動作モード デフォルト値
        public const int DEFAULT_CYCLE = 1;                     // 動作サイクル デフォルト値
        public const int DEFAULT_INPRIO = 148;                  // Edgeシステムへの入力サービススレッドプライオリティ デフォルト値
        public const int DEFAULT_OUTPRIO = 149;                 // Edgeシステムからの出力サービススレッドプライオリティ デフォルト値
        ////
        /// サービス名
        //
        public string SERVICENAME;                              // サービス名 ※ECIファイル(XML)から取得
        ////
        /// サービスインジケータ名
        //        
        public string IND_MYSERV_STATUS;                        // サービス起動
        public string IND_MYSERV_ERROR;                         // サービスエラー発生中
        public string IND_MYSERV_RUN;		                    // サービス実行中
        public string IND_MYSERV_LIVE;                          // サービス実行カウンタ
        ////
        /// サービスプロパティタグ名
        //        
        public string PROP_MYSERV_MODE;                         // サービス動作モード
        public string PROP_MYSERV_CYCLE;                        // サービス動作サイクル(ms)
        public string PROP_MYSERV_INPRIO;                       // Edgeシステムへの入力サービススレッドプライオリティ
        public string PROP_MYSERV_OUTPRIO;                      // Edgeシステムからの出力サービススレッドプライオリティ	
        ////
        /// サービス提供タグ名
        //  [TODO] : 任意のタグ追加 (XMLで定義することを推奨)
        // public static string TAG_MYSERV_XXX = "XXX";
        ////
        /// サービス固有メッセージ
        //  [TODO] : 任意のメッセージID追加(ユーザメッセージ 20000～29999番指定可)
        // public static int EM_SERVICE_XXX = 20000;    //任意の番号
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="Name"></param>
        public EDGECONST()
        { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="Name"></param>
        public void SET_EDGECONST(string Name)
        {
            // NOTE: サービス名から各タグ名称を生成します。
            SERVICENAME = Name;
            IND_MYSERV_STATUS = TAG_PREFIX + SERVICENAME + ".Status";           // サービス起動
            IND_MYSERV_ERROR = TAG_PREFIX + SERVICENAME + ".Error";             // サービスエラー発生中
            IND_MYSERV_RUN = TAG_PREFIX + SERVICENAME + ".Run";                 // サービス実行中
            IND_MYSERV_LIVE = TAG_PREFIX + SERVICENAME + ".Live";               // サービス実行カウンタ

            PROP_MYSERV_MODE = TAG_PREFIX + SERVICENAME + ".Mode";              // サービス動作モード
            PROP_MYSERV_CYCLE = TAG_PREFIX + SERVICENAME + ".Cycle";            // Adviseタグ監視周期(ms)
            PROP_MYSERV_INPRIO = TAG_PREFIX + SERVICENAME + ".InPriority";      // Edgeシステムへの入力サービススレッドプライオリティ
            PROP_MYSERV_OUTPRIO = TAG_PREFIX + SERVICENAME + ".OutPriority";    // Edgeシステムからの出力サービススレッドプライオリティ	
        }
    }
    /// <summary>
    /// RT-edge API/フレームワーク実装クラス
    /// </summary>
    public class edgeCode
    {
        ////
        /// サービス内部状態
        //
        public class MYSTATE
        {
            public bool State;                  // インジケータ.エッジシステム接続状態
            public bool Run;                    // インジケータ.サービス実行状態
            public bool Error;                  // インジケータ.サービス異常状態
            public UInt32 Live;                 // インジケータ.サービス実行カウンタ
            public UInt64 StartTime;            // インジケータ.サービス開始時のタイムスタンプ
            public byte Mode;                   // プロパティ.サービス実行モード
            public UInt32 Cycle;                // プロパティ.サービス実行サイクル(ms)	[TODO] : 必要に応じて実装
            public byte InPriority;             // プロパティ.サービス(入力)プライオリティ	[TODO] : 必要に応じて実装
            public byte OutPriority;            // プロパティ.サービス(出力)プライオリティ	[TODO] : 必要に応じて実装
            public SemaphoreSlim hInTrigSem;    // 入力サービストリガセマフォハンドル	[TODO] : 必要に応じて実装
            public SemaphoreSlim hOutTrigSem;   // 出力サービストリガセマフォハンドル	[TODO] : 必要に応じて実装
            //[TODO] : 任意のステータス追加
            public MYSTATE()
            {
                State = false;
                Run = false;
                Error = false;
                Live = 0;
                StartTime = 0;
                Mode = 0;
                Cycle = 1000;
                InPriority = 149;
                OutPriority = 148;
                hInTrigSem = null;
                hOutTrigSem = null;
            }
        }

        ////
        /// edge_APIクラス インスタンス化 
        //
        public edge_API EGAPI = null;                               //edge APIのインスタンス
        public MYSTATE STATE = null;                                //サービス状態クラスのインスタンス
        public EDGECONST EGCONST = null;                            // 各種定義値
        ////
        /// スレッド変数 
        //
        private CancellationTokenSource mThreadAutoTS = null;       //自動更新用スレッド       
        private CancellationTokenSource mThreadInServiceTS = null;  //入力スレッドキャンセル処理用変数
        private CancellationTokenSource mThreadOutServiceTS = null; //出力スレッドキャンセル処理用変数
        ////
        /// メイン処理終了用コールバック関数
        //
        public delegate void CallbackMainClose();
        public CallbackMainClose mInstMainClose = null;
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="myMsgHndlers">メッセージハンドラー関数を指定</param>
        public edgeCode() 
        {
            EGAPI = new edge_API();
            STATE = new MYSTATE();
            EGCONST = new EDGECONST();
        }
        /// <summary>
        /// 後処理処理(デストラクタ)
        /// [TODO] : 必要に応じて変更
        /// </summary>
        public void Release()
        {
            try
            {
                if (mThreadInServiceTS != null)     //入力スレッド終了処理
                {
                    mThreadInServiceTS.Cancel();    //キャンセル状態をセットし、スレッド処理で抜ける
                    mThreadInServiceTS = null;
                }

                if (mThreadOutServiceTS != null)    //出力スレッド終了処理
                {
                    mThreadOutServiceTS.Cancel();   //キャンセル状態をセットし、スレッド処理で抜ける
                    mThreadOutServiceTS = null;
                }

                if (mThreadAutoTS != null)          //自動更新スレッド終了処理
                {
                    mThreadAutoTS.Cancel();         //キャンセル状態をセットし、スレッド処理で抜ける
                    mThreadAutoTS = null;
                }
                
                if (STATE.hInTrigSem != null)
                    STATE.hInTrigSem.Dispose();
                if (STATE.hOutTrigSem != null)
                    STATE.hOutTrigSem.Dispose();
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
            }
        }
        
        /// <summary>
        /// Edgeメッセージハンドラ
        /// [TODO] : 必要に応じてメッセージ・処理を追加
        /// </summary>
        /// <param name="senderName">メッセージ送信元</param>
        /// <param name="messNo">メッセージ番号</param>
        /// <param name="paramArray">メッセージパラメータ</param>
        /// <returns></returns>
        object WMLOCK = new object();
        public Int32 MsgHandler(string senderName, int messNo, byte[] paramArray)
        {
            try
            {
                int result = 0;
                lock (WMLOCK) //排他制御 頻繁にやり取りする場合用 不要の場合は削除
                {
                    switch (messNo) //メッセージ番号毎に処理
                    {
                        case (int)EGDEFINE.EGMSG.EM_SERVICE_RUN:    //サービス開始指令
                            result = StartService();
                            break;
                        case (int)EGDEFINE.EGMSG.EM_SERVICE_STOP:   // サービス終了指令
                            {
                                if (mInstMainClose != null)
                                    mInstMainClose();               // コールバック関数からは戻りません
                                else
                                    result = KillService();
                            }
                            break;
                        case (int)EGDEFINE.EGMSG.EM_SERVICE_PAUSE:  // サービス一時停止指令
                            result = PauseService();
                            break;
                        case (int)EGDEFINE.EGMSG.EM_SERVICE_UPDATE: // サービスアップデート指令
                            switch ((int)STATE.Mode)
                            {
                                case EDGECONST.MODE_AUTO:
                                    break;
                                case EDGECONST.MODE_SEMIAUTO:
                                    KickOutput();
                                    break;
                                case EDGECONST.MODE_MANUAL:
                                    KickInput();
                                    KickOutput();
                                    break;
                                default:
                                    break;
                            }
                            break;
                        //
                        // [TODO] : 任意のメッセージ処理を追加
                        //
                        default:
                            break;
                    }
                }
                return result;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }
        /// <summary>
        /// Edgeシステム入出力自動サイクルスレッド
        /// 必要が無ければ変更不要
        /// </summary>
        public async void ThreadAutoCycle(CancellationToken cancelToken)
        {
            try
            {
				await Task.Run(() =>
                {
                    try
                    {

                        while (true)
                        {
                            if (cancelToken.IsCancellationRequested)        // スレッドを抜ける処理
                                break;

                            ++STATE.Live;                                   // サービス実行カウンタ増加
                            GetProperty();                                  // サービスプロパティのリロード

                            if (STATE.Run)                                  // サービス実行状態なら処理する
                            {
                                switch (STATE.Mode)                         // サービス動作モードに応じて分岐
                                {
                                    case EDGECONST.MODE_AUTO:               // 自動サイクルモード時（入出力共に自動更新する）
                                        KickInput();                      	// Input処理実行
                                        KickOutput();						// Output処理実行
                                        break;
                                    case EDGECONST.MODE_SEMIAUTO:           // 半自動サイクル時（入力のみ自動更新、出力はEM_SERVICE_UPDATEで指令）
                                        KickInput();                      	// Input処理実行
                                        break;
                                    case EDGECONST.MODE_MANUAL:             // 手動サイクル時（入出力はEM_SERVICE_UPDATEで指令するので無処理）
                                        break;
                                }
                            }

                            UpdateIndicator();                              // サービスインジケータ更新
                            System.Threading.Thread.Sleep((int)STATE.Cycle);// サイクル待ち
                        }
                    }
                    catch (Exception ex)
                    {
                        LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                    }
                });
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
            }
        }
        /// <summary>
        /// Edgeシステムへのデータ入力
        /// [TODO] : 必要に応じて値を書き込むタグと値を指定
        /// </summary>
        public async void ThreadInService(CancellationToken cancelToken)
        {
            try
            {
                int result = 0;
                while (true)
                { 
                    await STATE.hInTrigSem.WaitAsync();
                    // キャンセルトークンが来たら終了
                    if (cancelToken.IsCancellationRequested)
                        break;
                    //タスク処理を開始する
                    try
                    {
                        //
                        //[TODO] : 必要に応じて任意のEgTagから値読込を実装
                        //例) 時間を取得
                        // 以下例で示す ts はEDGE_TIME_STAMPのインスタンス 
                        //result |= EgTagRead(TAG_MYSERV_XXXX1, &ts.time, sizeof(ts.time));			// 任意の値
                        //result |= EgTagRead(TAG_MYSERV_XXXX2, &ts.millisecond, , sizeof(ts.millisecond));	// システム時刻格納(ms)

                        STATE.Error = (result != 0);    // エラーインジケータを更新

                    }
                    finally
                    {
                        //Releaseはトリガ処理で行うためここでは呼ばない
                    }
                }
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
            }
        }
        /// <summary>
        /// Edgeシステムからのデータ出力
        /// [TODO] : 必要に応じて値を読み込むタグを指定
        /// </summary>
        public async void ThreadOutService(CancellationToken cancelToken)
        {
            try
            {
                int result = 0;
                while (true)
                {
                    await STATE.hOutTrigSem.WaitAsync();
                    // キャンセルトークンが来たら終了
                    if (cancelToken.IsCancellationRequested)
                        break;
                    //タスク処理を開始する
                    try
                    {
                        //
                        //[TODO] : 必要に応じて任意のEgTagから値読込を実装
                        //例) 時間を取得
                        // 以下例で示す ts はEDGE_TIME_STAMPのインスタンス 
                        //result |= EgTagRead(TAG_MYSERV_XXXX1, &ts.time, sizeof(ts.time));			// 任意の値
                        //result |= EgTagRead(TAG_MYSERV_XXXX2, &ts.millisecond, , sizeof(ts.millisecond));	// システム時刻格納(ms)

                        STATE.Error = (result != 0);	// エラーインジケータを更新
                    }
                    finally
                    {
                        //Releaseはトリガ処理で行うためここでは呼ばない
                    }
                }
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
            }
        }
        /// <summary>
        /// 入力サービスを1回キックする
        /// 必要が無ければ変更不要
        /// </summary>
        public void KickInput()
        {
            try
            {
                STATE.hInTrigSem.Release();
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
            }
        }
        /// <summary>
        /// 出力サービスを1回キックする
        /// 必要が無ければ変更不要
        /// </summary>
        public void KickOutput()
        {
            try
            {
                STATE.hOutTrigSem.Release();
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
            }
        }
        /// <summary>
        /// サービスタグ生成 ※すでに生成されている場合は無視して継続します
        /// [TODO] : 必要に応じて作成するタグを指定(タグの生成はXMLに定義し自動生成することを推奨)
        /// </summary>
        /// <returns></returns>
        public int CreateServiceTags()
        {
            try
            {
                int result = 0;
                // サービスタグ類の生成
                // [TODO] : 必要に応じて以下コードを修正
                //result |= EGAPI.EgTagCreate(EGCONST.TAG_MYSERV_XXX, (ushort)EGDEFINE.egTagDataType.UInt64, "", "sample tag");
                return result;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }
        /// <summary>
        /// サービスインジケータの更新 ※更新に失敗しても無視して継続します
        /// 必要が無ければ変更不要
        /// </summary>
        /// <returns>正常時０</returns>
        public int UpdateIndicator()
        {
            try
            {
                // インジケータタグはフレームワークによって自動生成済
                int result = 0;
                result |= EGAPI.EgTagWrite(EGCONST.IND_MYSERV_STATUS, STATE.State);   // サービス起動状態
                result |= EGAPI.EgTagWrite(EGCONST.IND_MYSERV_RUN, STATE.Run);        // サービス実行状態
                result |= EGAPI.EgTagWrite(EGCONST.IND_MYSERV_LIVE, STATE.Live);      // サービス実行カウンタ
                result |= EGAPI.EgTagWrite(EGCONST.IND_MYSERV_ERROR, STATE.Error);    // サービス異常状態
                return result;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }
        /// <summary>
        /// サービスプロパティの取得 ※取得できない時はデフォルト設定値を採用します
        /// [TODO] : 必要に応じて設定項目の増減指定
        /// </summary>
        /// <returns></returns>
        public int GetProperty()
        {
            try
            {
                // プロパティタグはXML定義可能　無いときはデフォルト設定値を採用します
                int result = 0;
                object value = new object();
                result = EGAPI.EgTagRead(EGCONST.PROP_MYSERV_MODE, ref value);    // サービス動作モードの取得
                if (result == 0)
                    STATE.Mode = (byte)value;
                else
                    STATE.Mode = EDGECONST.DEFAULT_MODE;				            // デフォルト

                result = EGAPI.EgTagRead(EGCONST.PROP_MYSERV_CYCLE, ref value);   // サービス処理サイクルの取得
                if (result == 0)
                    STATE.Cycle = (uint)value;
                else
                    STATE.Cycle = EDGECONST.DEFAULT_CYCLE;				            // デフォルト

                result = EGAPI.EgTagRead(EGCONST.PROP_MYSERV_INPRIO, ref value);  // 入力サービススレッドのプライオリティ指定
                if (result == 0)
                    STATE.InPriority = (byte)value;
                else
                    STATE.InPriority = EDGECONST.DEFAULT_INPRIO;                    // デフォルト

                result = EGAPI.EgTagRead(EGCONST.PROP_MYSERV_INPRIO, ref value);  // 出力サービススレッドのプライオリティ指定
                if (result == 0)
                    STATE.OutPriority = (byte)value;
                else
                    STATE.OutPriority = EDGECONST.DEFAULT_OUTPRIO;                  // デフォルト

                //
                // [TODO] : 任意のサービスプロパティを指定
                //
                return 0;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }
        /// <summary>
        /// サービス開始 ※入出力可能になります
        /// 必要が無ければ変更不要
        /// </summary>
        /// <returns>正常時０</returns>
        public int StartService()
        {
            try
            {
                STATE.Run = true;       // サービス実行
                UpdateIndicator();      // サービスインジケータ更新
                return 0;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }
        /// <summary>
        /// サービス一時停止 ※入出力されなくなります
        /// 必要が無ければ変更不要
        /// </summary>
        /// <returns></returns>
        public int PauseService()
        {
            try
            {
                STATE.Run = false;      // サービス一時停止
                UpdateIndicator();      // サービスインジケータ更新
                return 0;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }
        /// <summary>
        /// サービスの初期化
        /// </summary>
        /// <returns>正常時０</returns>
        public Int32 InitService()
        {
            try
            {
                Int32 result = 0;
                LOG("[LOG] Start Sample Service");
                edge_API.EDGECONFIG config = new edge_API.EDGECONFIG(); //設定情報の初期化
                config = EGAPI.EDGE_CONFIG_DEFAULT;                     //初期値設定

                config.mInstMessFunc = MsgHandler;                      //Edgeメッセージユーザハンドラの指定

                result = EGAPI.EgInit(config);                          // Edgeフレームワークの初期化
                if (result != 0)                                        // 正常終了以外の場合はコンソールにメッセージ出力
                    WARN(result);

                EGCONST.SET_EDGECONST(EGAPI.EGFW.ServiceRealName);      // 各タグ名生成

                result = GetProperty();                                 // サービスプロパティのロード処理
                if (result != 0)                                        // 正常終了以外の場合はコンソールにメッセージ出力
                    WARN(result);

                result = CreateServiceTags();                           // サービスインジケータの生成
                if (result != 0)                                        // 正常終了以外の場合はコンソールにメッセージ出力
                    WARN(result);

                STATE.hInTrigSem = new SemaphoreSlim(1, 1);             // 入力サービススレッド用トリガセマフォ
                STATE.hOutTrigSem = new SemaphoreSlim(1, 1);            // 出力サービススレッド用トリガセマフォ

                mThreadInServiceTS = new CancellationTokenSource();     //スレッド終了用token指定
                CancellationToken cInToken = mThreadInServiceTS.Token;    
                ThreadInService(cInToken);                              //入力スレッド処理作成始

                mThreadOutServiceTS = new CancellationTokenSource();    //スレッド終了用token指定
                CancellationToken cOutToken = mThreadOutServiceTS.Token;
                ThreadOutService(cOutToken);                            //出力スレッド処理作成始

                mThreadAutoTS = new CancellationTokenSource();          //スレッド終了用token指定
                CancellationToken cAutoToken = mThreadAutoTS.Token;
                ThreadAutoCycle(cAutoToken);                            //自動更新スレッド処理作成始

                STATE.State = EDGECONST.SERVICE_UP;                     // サービスアップ

                return result;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }
        /// <summary>
        /// サービス削除（プロセス停止）
        /// </summary>
        /// <returns>ん</returns>
        public int KillService()
        {
            try
            {
                PauseService();                         // サービス一時停止

                STATE.State = EDGECONST.SERVICE_DOWN;   //
                UpdateIndicator();				        // サービスインジケータ更新
                LOG("[LOG] End " + EGCONST.SERVICENAME + " Service");
                return 0;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }
        /// <summary>
        /// [DEBUG用] メッセージ出力
        /// </summary>
        /// <param name="message"></param>
        /// <param name="filePath"></param>
        /// <param name="lineNumber"></param>
        public void WARN(int reason,
                         [CallerFilePath] string filePath = "",
                         [CallerLineNumber] int lineNumber = 0)
        {
            try
            {
                Console.WriteLine(EGCONST.SERVICENAME + " service warning at file(" + filePath + ") ,line(" + lineNumber.ToString() +
                                   ")  ,reason 0x" + reason.ToString("x8"));
            }
            catch
            { }
        }
        /// <summary>
        /// [DEBUG用] メッセージ出力
        /// </summary>
        /// <param name="message"></param>
        public void LOG(string message)
        {
            try
            {
                Console.WriteLine(EGCONST.SERVICENAME + " : " + message);
            }
            catch
            { }
        }

    }
}
