using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Project.StateMachine
{
    public partial class StateMachine : IDisposable
    {
        public DateTime UpdateTime;
        public TimeSpan RunSpeed = new TimeSpan(0);
        private Boolean bLoop = true;
        private DateTime StepStartTime;
        private byte _messageOption;

        private System.Threading.Thread worker;
        private Boolean firstRun = false;

        public Boolean WaitFirstRun = false;



        public StateMachine()
        {
            WaitFirstRun = false;
            UpdateTime = DateTime.Now;
            _messageOption = 0xFF;   //모든메세지가 오도록 한다.
            worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop))
            {
                IsBackground = false
            };
            StepStartTime = DateTime.Parse("1982-11-23");
        }

        ~StateMachine()
        {
            Dispose(false);
        }

        #region "Dispose"

        private bool disposed = false;
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                if (disposing)
                {
                    // Dispose managed resources.
                }

                Stop();
                // unmanaged resources here.
                // If disposing is false,
                // only the following code is executed.
                disposed = true;
            }
        }

        #endregion

        public void Stop()
        {
            if (bLoop != false) bLoop = false;
            //if (worker.IsAlive)
            //	if (worker.Join(1000) == false)
            //		worker.Abort();
        }
        public void Start()
        {
            if (worker.ThreadState == System.Threading.ThreadState.Stopped)
            {
                worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop))
                {
                    IsBackground = false
                };
            }
            bLoop = true;
            worker.Start();
        }

        private Boolean _isthreadrun;
        public Boolean IsThreadRun { get { return _isthreadrun; } }
        public int LoopCount { get { return _loopCount; } }
        public DateTime LastLoopTime { get { return _lastLoopTime; } }

        private int _loopCount = 0;
        private DateTime _lastLoopTime = DateTime.Now;

        void Loop()
        {
            _isthreadrun = true;
            if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", "Start");

            try
            {
                while (bLoop)
                {
                    _loopCount++;
                    _lastLoopTime = DateTime.Now;

                    // 루프 동작 확인용 로그 (10초마다)
                    if (_loopCount % 10000 == 0)
                    {
                        RaiseMessage("SM-LOOP", $"Loop alive - Count:{_loopCount}, bLoop:{bLoop}, Step:{Step}");
                    }

                    RunSpeed = DateTime.Now - UpdateTime;   //이전업데이트에서 현재 시간의 오차 한바퀴 시간이 표시됨
                    UpdateTime = DateTime.Now;

                    //항상 작동하는 경우
                    try
                    {
                        var spsHandler = SPS;
                        if (spsHandler != null)
                        {
                            var spsStartTime = DateTime.Now;
                            var spsTask = System.Threading.Tasks.Task.Run(() =>
                            {
                                try
                                {
                                    spsHandler(this, new EventArgs());
                                }
                                catch (Exception ex)
                                {
                                    RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}");
                                }
                            });

                            // 타임아웃 1초
                            if (!spsTask.Wait(1000))
                            {
                                var elapsed = (DateTime.Now - spsStartTime).TotalSeconds;
                                RaiseMessage("SM-TIMEOUT", $"SPS 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}");
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}");
                    }

                    //작동스텝이 변경되었다면 그것을 알림 처리한다.
                    if (GetNewStep != Step)
                    {
                        if (GetMsgOpt(EMsgOpt.STEPCHANGE))
                            RaiseMessage("SM-STEP", string.Format("Step Changed {0} >> {1}", Step, GetNewStep));

                        StepApply();
                        firstRun = true;
                        StepStartTime = DateTime.Now;
                    }
                    else
                    {
                        //팝업메세지의 종료를 기다리는 식에서 문제가 생긴다
                        firstRun = WaitFirstRun;
                    }


                    //동작중에 발생하는 이벤트
                    var runningHandler = Running;
                    if (runningHandler != null)
                    {
                        try
                        {
                            var runningStartTime = DateTime.Now;
                            var runningTask = System.Threading.Tasks.Task.Run(() =>
                            {

                                //try
                                //{
                                runningHandler(this, new RunningEventArgs(Step, firstRun, StepRunTime));
                                //}
                                //catch (Exception ex)
                                //{
                                //	RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}");
                                //}


                            });

                            // 타임아웃 2초
                            if (!runningTask.Wait(2000))
                            {
                                var elapsed = (DateTime.Now - runningStartTime).TotalSeconds;
                                RaiseMessage("SM-TIMEOUT", $"Running 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}, FirstRun:{firstRun}");
                            }
                        }
                        catch (Exception ex)
                        {
                            RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}");
                        }
                    }
                    System.Threading.Thread.Sleep(1);
                }
            }
            catch (Exception ex)
            {
                RaiseMessage("SM-FATAL", $"Loop Fatal Exception: {ex.Message}\n{ex.StackTrace}");
            }
            finally
            {
                _isthreadrun = false;
                if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", $"Stop - LoopCount:{_loopCount}");
            }
        }


        /// <summary>
        /// 상태머신의 작동상태
        /// </summary>
        public eSMStep Step { get { return _step; } }

        /// <summary>
        /// 상태머신값을 숫자로 반환 합니다. (20이하는 시스템상태이고 그 이상은 사용자 상태)
        /// </summary>
        public byte StepValue
        {
            get
            {
                return (byte)this._step;
            }
        }

        /// <summary>
        /// 현재 진행 스텝을 강제로 변경합니다.
        /// 일반적인 상태변화는 setNewStep 을 사용하세요.
        /// </summary>
        /// <param name="step"></param>
        public void SetCurrentStep(ERunStep step)
        {
            RaiseMessage("SM-STEP", string.Format("강제 스텝 변경 {0}->{1}", _runstepo, step));
            _runstepo = step;
            _runstepn = step;
        }

        public void SetNewStep(eSMStep newstep_, bool force = false)
        {
            if (Step != newstep_)
            {
                if (force)
                {
                    RaiseMessage("SM-STEP", string.Format("강제스텝전환 {0} -> {1}", Step, _newstep));
                    OldStep = _step;
                    _step = newstep_;
                    _newstep = newstep_;

                    // 비동기로 이벤트 발생 (블로킹 방지)
                    var handler = StepChanged;
                    if (handler != null)
                    {
                        var args = new StepChangeEventArgs(OldStep, newstep_);
                        try { handler(this, args); }
                        catch { /* 이벤트 핸들러 예외 무시 */ }
                    }
                }
                else
                {
                    if (_newstep != newstep_)
                    {
                        //바뀌도록 예약을 한다.
                        _newstep = newstep_;
                        if (GetMsgOpt(EMsgOpt.STEPCHANGE))
                            RaiseMessage("SM-STEP", string.Format("Step Reserve {0} -> {1}", Step, _newstep));
                    }
                    else
                    {
                        //예약은 되어있는데 아직 바뀐것은 아니다.
                        if (GetMsgOpt(EMsgOpt.STEPCHANGE))
                            RaiseMessage("SM-STEP", string.Format("Step Already Reserve {0} -> {1}", Step, _newstep));
                    }
                }

            }
        }

        #region "runstep"
        private int _runStepSeq = 0;
        public int RunStepSeq { get { return _runStepSeq; } }

        public Stack<ERunStep> BackupRunStep = new Stack<ERunStep>();

        private DateTime runStepStartTime = DateTime.Parse("1982-11-23");
        private ERunStep _runstepn = ERunStep.READY;
        private ERunStep _runstepo = ERunStep.READY;
        public ERunStep RunStep { get { return _runstepo; } }
        public ERunStep RunStepNew { get { return _runstepn; } }
        public void SetNewRunStep(ERunStep newStep)
        {
            if (_runstepn != newStep)
            {
                // Pub.log.Add(string.Format("set new run step {0}->{1}", _runstepn, newStep));
                _runstepn = newStep;
            }
        }
        public void ApplyRunStep()
        {
            if (_runstepn != _runstepo)
            {
                //Pub.log.Add(string.Format("apply new run step {0}->{1}", _runstepo, _runstepn));
                _runStepSeq = 0;
                _runstepo = _runstepn;
                UpdateRunStepSeq();// runStepStartTime = DateTime.Now;
            }
        }
        public TimeSpan GetRunSteptime { get { return DateTime.Now - runStepStartTime; } }
        public void SetStepSeq(byte value)
        {
            _runStepSeq = value;
            if (GetMsgOpt(EMsgOpt.NORMAL))
            {
                if (_runStepSeq != value)   //변화가잇는겨웅에만 처리 220628
                    RaiseMessage("STEPSEQ", string.Format("Step Sequence Jump {0} to {1}", _runStepSeq, value));
            }

        }
        public void UpdateRunStepStartTime()
        {
            runStepStartTime = DateTime.Now;
            // Pub.log.Add("Update RunStep Start Time");
        }
        public void UpdateRunStepSeq(int incvalue = 1)
        {
            _runStepSeq += incvalue;
            UpdateRunStepStartTime();
            //   Pub.log.Add(string.Format("스텝({0}) 시퀀스증가 신규값={1}", runStep, _runStepSeq));
        }
        public void ResetRunStepSeq()
        {
            _runStepSeq = 1;
            UpdateRunStepStartTime();
        }

        /// <summary>
        /// runstep 시퀀스값을 1로 설정하고 시작시간도 업데이트 합니다
        /// 기본 스텝상태를 READY로 변경 합니다
        /// </summary>
        public void ClearRunStep()
        {
            _runStepSeq = 1;
            runStepStartTime = DateTime.Now;
            _runstepn = ERunStep.READY;
            _runstepo = ERunStep.READY;
        }
        #endregion


        public eSMStep GetNewStep
        {
            get
            {
                return _newstep;
            }
        }
        private eSMStep _newstep = eSMStep.NOTSET;
        public eSMStep OldStep = eSMStep.NOTSET; //171214
        private eSMStep _step;

        /// <summary>
        /// newstep 의 값을 step 에 적용합니다.
        /// </summary>
        private void StepApply()
        {
            var ostep = _step;
            OldStep = _step; _step = _newstep;

            // 비동기로 이벤트 발생 (블로킹 방지)
            var handler = StepChanged;
            if (handler != null)
            {
                var args = new StepChangeEventArgs(ostep, _step);
                try { handler(this, args); }
                catch { /* 이벤트 핸들러 예외 무시 */ }
            }
        }    //171214

        /// <summary>
        /// 메세지 출력옵션을 변경 합니다.
        /// </summary>
        /// <param name="opt"></param>
        /// <param name="value"></param>
        public void SetMsgOpt(EMsgOpt opt, Boolean value)
        {
            byte pos = (byte)opt;
            if (value)
                _messageOption = (byte)(_messageOption | (1 << pos));
            else
                _messageOption = (byte)(_messageOption & ~(1 << pos));
        }
        public void SetMegOptOn() { _messageOption = 0xFF; }
        public void SetMsgOptOff() { _messageOption = 0; }
        public Boolean GetMsgOpt(EMsgOpt opt)
        {
            byte pos = (byte)opt;
            return (_messageOption & (1 << pos)) > 0;
        }
        public TimeSpan StepRunTime
        {
            get
            {
                if (IsThreadRun == false || bLoop == false || StepStartTime.Year == 1982) return new TimeSpan(0);
                else return DateTime.Now - StepStartTime;
            }
        }

        public Boolean bPause = false;

        /// <summary>
        /// 상태머신이 실제 동작중인지 확인합니다.
        /// 검사상태 : Step = Run, IsThreadRun, bPause = false
        /// </summary>
        public Boolean IsRun
        {
            get
            {
                if (Step == eSMStep.RUN && _isthreadrun && bPause == false) return true;
                else return false;
            }
        }
    }
}
