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

namespace EdgeCode
{
    /// <summary>
    /// RT-edge API/フレームワーク実装クラス
    /// </summary>
    public class edgeCode
    {

        #region クラス定義
        
        ////
        /// サービス内部状態
        //
        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 SemaphoreSlim hOutTrigSem;   // 出力サービストリガセマフォハンドル	[TODO] : 必要に応じて実装

            // コンストラクタ
            public MYSTATE()
            {
                State = false;
                Run = false;
                Error = false;
                Live = 0;
                StartTime = 0;
                Mode = EDGECONST.MODE_AUTO;
                Cycle = 1000;
                hOutTrigSem = null;
            }
        }

        #endregion


        #region フィールド


        ////
        /// edge_APIクラス インスタンス化 
        //
        public edge_API EGAPI = null;                               //edge APIのインスタンス
        public MYSTATE STATE = null;                                //サービス状態クラスのインスタンス
        ////
        /// スレッド変数 
        //
        private CancellationTokenSource mThreadAutoTS = null;       //自動更新用スレッド       
        private CancellationTokenSource mThreadOutServiceTS = null; //出力スレッドキャンセル処理用変数
        ////
        /// メイン処理終了用コールバック関数
        //
        public delegate void CallbackMainClose();
        public CallbackMainClose mInstMainClose = null;

                /// <summary>
        /// 読み込みTag
        /// </summary>
        public string InputTagName = null;

        /// <summary>
        /// 値更新イベント
        /// </summary>
        public event ReflreshInputValueEventHandler mRefleshEvent;
        /// <summary>
        /// イベントハンドラ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public delegate void ReflreshInputValueEventHandler(object sender, RefleshValueInputEventArgs e);

        #endregion


        #region コンストラクタ

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public edgeCode() 
        {
            EGAPI = new edge_API();
            STATE = new MYSTATE();
        }

        #endregion


        #region プライベートメソッド

        /// <summary>
        /// イベントの通知
        /// </summary>
        /// <param name="value"></param>
        private void NofifyRefleshValue(uint value)
        {
            mRefleshEvent?.Invoke(this, new RefleshValueInputEventArgs(value));
        }

        /// <summary>
        /// 出力サービスを1回キックする
        /// </summary>
        private void KickOutput()
        {
            try
            {
                STATE.hOutTrigSem.Release();
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
            }
        }


        #endregion


        #region パブリックメソッド

        /// <summary>
        /// サービスタグ生成 ※すでに生成されている場合は無視して継続します
        /// </summary>
        /// <returns></returns>
        public int CreateServiceTags()
        {
            try
            {
                int result = 0;
                // サービスタグ類の生成
                result |= EGAPI.EgTagCreate(EDGECONST.PROP_MYSERV_WRITETEST, (ushort)EGDEFINE.egTagDataType.UInt32, 4, "", "for write test 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(EDGECONST.IND_MYSERV_STATUS, STATE.State);   // サービス起動状態
                result |= EGAPI.EgTagWrite(EDGECONST.IND_MYSERV_RUN, STATE.Run);        // サービス実行状態
                result |= EGAPI.EgTagWrite(EDGECONST.IND_MYSERV_LIVE, STATE.Live);      // サービス実行カウンタ
                result |= EGAPI.EgTagWrite(EDGECONST.IND_MYSERV_ERROR, STATE.Error);    // サービス異常状態
                return result;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }
      
        /// <summary>
        /// サービスプロパティの取得 ※取得できない時はデフォルト設定値を採用します
        /// </summary>
        /// <returns></returns>
        public int GetProperty()
        {
            try
            {
                // プロパティタグはXML定義可能　無いときはデフォルト設定値を採用します
                int result = 0;
                object value = new object();
                result = EGAPI.EgTagRead(EDGECONST.PROP_MYSERV_MODE, ref value);    // サービス動作モードの取得
                if (result == 0)
                    STATE.Mode = (byte)value;
                else
                    STATE.Mode = EDGECONST.DEFAULT_MODE;				            // デフォルト

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

                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>
        /// <param name="inputTagName">読み込みTAG</param>
        /// <returns>正常時０</returns>
        public Int32 InitService(string inputTagName)
        {
            try
            {
                Int32 result = 0;
                LOG("[LOG] Start " + EDGECONST.SERVICENAME + " 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);

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

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

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

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

                mThreadAutoTS = new CancellationTokenSource();          //スレッド終了用token指定
                CancellationToken cAutoToken = mThreadAutoTS.Token;
                ThreadAutoCycle(cAutoToken);                            //自動更新スレッド処理作成始
                
                InputTagName = inputTagName;                            // 読み込みTagの保持

                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 " + EDGECONST.SERVICENAME + " Service");
                return 0;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }

        /// <summary>
        /// Edgeメッセージハンドラ
        /// </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:
                                    KickOutput();
                                    break;
                                default:
                                    break;
                            }
                            break;

                        default:
                            break;
                    }
                }
                return result;
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                return -1;
            }
        }


        /// <summary>
        /// 後処理処理(デストラクタ)
        /// </summary>
        public void Release()
        {
            try
            {
                if (mThreadOutServiceTS != null)    //出力スレッド終了処理
                {
                    mThreadOutServiceTS.Cancel();   //キャンセル状態をセットし、スレッド処理で抜ける
                    mThreadOutServiceTS = null;
                }

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

                if (STATE.hOutTrigSem != null)
                    STATE.hOutTrigSem.Dispose();
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
            }
        }

        /// <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(EDGECONST.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(EDGECONST.SERVICENAME + " : " + message);
            }
            catch
            { }
        }

        #endregion


        #region スレッド処理

        /// <summary>
        /// Edgeシステム入出力自動サイクルスレッド
        /// 必要が無ければ変更不要
        /// </summary>
        public async void ThreadAutoCycle(CancellationToken cancelToken)
        {
            try
            {
                await Task.Run(() =>
                {
                    try
                    {
                        //**********************************************************
                        // Invokeスレッド処理: サービス実行状態に応じて処理を実行
                        //**********************************************************
                        while (true)
                        {
                            if (cancelToken.IsCancellationRequested)        // スレッドを抜ける処理
                                break;

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

                            if (STATE.Run)                                  // サービス実行状態なら処理する
                            {
                                switch (STATE.Mode)                         // サービス動作モードに応じて分岐
                                {
                                    case EDGECONST.MODE_AUTO:               // 自動サイクルモード時
                                        KickOutput();						// Output処理実行
                                        break;
                                    case EDGECONST.MODE_SEMIAUTO:           // 半自動サイクル時（入力のみ自動更新、出力はEM_SERVICE_UPDATEで指令）
                                        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システムからのデータ出力
        /// </summary>
        public async void ThreadOutService(CancellationToken cancelToken)
        {
            try
            {
                int result = 0;

                //**********************************************************
                // スレッド処理: RT-edgeシステム(RTCD)からデータを取り出す(読込む)
                //**********************************************************
                while (true)
                {
                    await STATE.hOutTrigSem.WaitAsync();
                    // キャンセルトークンが来たら終了
                    if (cancelToken.IsCancellationRequested)
                        break;

                    //タスク処理を開始する
                    try
                    {
                        // 値の読み込み
                        object readValue = new object();
                        result = EGAPI.EgTagRead(InputTagName, ref readValue);

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

                        if(result == 0)                 // 読み取り成功時
                        {
                            // 通知
                            NofifyRefleshValue((uint)readValue);
                        }
                    }
                    catch (Exception ex)
                    {
                        LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
                    }
                    finally
                    {
                        //Releaseはトリガ処理で行うためここでは呼ばない
                    }
                }
            }
            catch (Exception ex)
            {
                LOG(System.Reflection.MethodBase.GetCurrentMethod().Name + " : " + ex.Message);
            }
        }

        #endregion

    }

    /// <summary>
    /// RT-edge API/フレームワーク 各種値定義クラス
    /// </summary>
    public static class EDGECONST
    {
        ////
        /// サービス名
        //
        public const string SERVICENAME = "IOSample";                                           // [TODO](*******必須*******) : exe拡張子を除くプログラムファイル名に変更
        ////
        /// サービスインジケータ名
        //        
        public const string IND_MYSERV_STATUS = "SERVICE." + SERVICENAME + ".Status";           // サービス起動
        public const bool SERVICE_UP = true;                                                    // 起動中
        public const bool SERVICE_DOWN = false;                                                 // 停止中
        public const string IND_MYSERV_ERROR = "SERVICE." + SERVICENAME + ".Error";             // サービスエラー発生中
        public const string IND_MYSERV_RUN = "SERVICE." + SERVICENAME + ".Run";		            // サービス実行中
        public const string IND_MYSERV_LIVE = "SERVICE." + SERVICENAME + ".Live";               // サービス実行カウンタ
        ////
        /// サービスプロパティタグ名
        //        
        public const string PROP_MYSERV_MODE = "SERVICE." + SERVICENAME + ".Mode";              // サービス動作モード
        public const int MODE_AUTO = 1;                                                         // 入出自動サイクル
        public const int MODE_SEMIAUTO = 2;                                                     // 入力サイクル＋出力デマンド
        public const int MODE_MANUAL = 3;                                                       // 入出力デマンド
        public const int DEFAULT_MODE = MODE_AUTO;                                              // デフォルト値
        public const string PROP_MYSERV_CYCLE = "SERVICE." + SERVICENAME + ".Cycle";            // サービス動作サイクル(ms)
        public const int DEFAULT_CYCLE = 500;
        public const string PROP_MYSERV_WRITETEST = "SERVICE." + SERVICENAME + ".WriteTest";    // 書き込みテスト用タグ
    }

    /// <summary>
    /// 値更新イベント引数クラス
    /// </summary>
    public class RefleshValueInputEventArgs : EventArgs
    {
        public uint Value { get; set; }
        public RefleshValueInputEventArgs(uint value)
        {
            Value = value;
        }
    }
}
