using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Collections;
using COMM;
using System.Security.Cryptography.X509Certificates;
using AR;

namespace arDev
{
    public enum eNarumiTurn
    {
        None = 0,
        LeftIng,
        Left,
        RightIng,
        Right,
    }
    public class NarumiTurnInfo
    {
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
        public string Message { get; set; }
        public TimeSpan Runtime
        {
            get
            {
                if (End.Year < 2000) return DateTime.Now - Start;
                if (End < Start) return DateTime.Now - Start;
                else return End - Start;
            }
        }
        public eNarumiTurn State { get; set; }
        //public eNarumiTurn StateNew { get; set; }
        public NarumiTurnInfo()
        {
            Start = new DateTime(1982, 11, 23);
            End = new DateTime(1982, 11, 23);
            State = eNarumiTurn.None;
        }
        public bool IsTurning
        {
            get
            {
                return State == eNarumiTurn.LeftIng || State == eNarumiTurn.RightIng;
            }
        }
    }
    public class NarumiCommandTime
    {
        public DateTime Time { get; set; }
        public ushort count { get; set; }
        public NarumiCommandTime(ushort cnt)
        {
            Time = DateTime.Now;
            this.count = cnt;
        }
    }

    /// <summary>
    /// error 이상은모두 처리불가한 오류 조건을 의미한다
    /// </summary>
    public enum eNarumiCommandResult : byte
    {
        Fail = 0,
        Success = 1,
        Wait = 2,
        Error = 100,
        Timeout,

    }
    public enum eNarmiPBSSensor : byte
    {
        notset = 0,
        on = 1,
        off = 2
    }

    public partial class Narumi
    {
        public eNarmiPBSSensor PBSSensor = eNarmiPBSSensor.notset;


        public NarumiTurnInfo TurnInformation { get; set; } = null;
        Dictionary<string, NarumiCommandTime> SendCommandFailList { get; set; } = new Dictionary<string, NarumiCommandTime>();

        public eNarumiCommandResult AGVMoveSet(BunkiData opt)
        {
            var param = opt.ToString();
            return AddCommand(eAgvCmd.MoveSet, param);
        }

        public eNarumiCommandResult AGVMoveManual(ManulOpt opt, Speed spd, Sensor ss)
        {
            var param = opt.ToString() + spd.ToString()[0] + ((int)ss).ToString();
            return AddCommand(eAgvCmd.ManualMove, param);
        }
        public eNarumiCommandResult AGVMoveRun(eRunOpt opt = eRunOpt.NotSet)
        {
            System.Text.StringBuilder sb = new StringBuilder();
            if (opt == eRunOpt.Backward) sb.Append("B");
            else if (opt == eRunOpt.Forward) sb.Append("F");
            else sb.Append("0");
            sb.Append("000");
            return AddCommand(eAgvCmd.MoveStart, sb.ToString());
        }

        public eNarumiCommandResult AGVSetSpeed(eSetSpeed item, int speed)
        {
            string cmd = "SS";
            if (item == eSetSpeed.Rotation) cmd = "SRS";
            else cmd += item.ToString()[0];
            cmd += speed.ToString("0000");
            return AddCommand(cmd);
        }
        public eNarumiCommandResult AGVMoveStop(string Reason, eStopOpt opt = eStopOpt.Stop)
        {
            System.Text.StringBuilder sb = new StringBuilder();
            if (opt == eStopOpt.MarkStop) sb.Append("MS");
            else if (opt == eStopOpt.MarkRotateLeft) sb.Append("TL");
            else if (opt == eStopOpt.MarkRotateRight) sb.Append("TR");
            else if (opt == eStopOpt.MarkChangeDirection) sb.Append("BS");
            else sb.Append("0S");
            sb.Append("00");
            sb.Append("0000");   //재기동시간

            //동작중이면 이 멈춤 명령을 기록으로 남긴다 230116
            if (this.system1.agv_run)
                RaiseMessage(MessageType.Normal, $"stop command from {Reason}");
            var retval = AddCommand(eAgvCmd.MoveStop, sb.ToString());
            //if (retval == eNarumiCommandResult.Success && opt == eStopOpt.MarkStop)
            //    VAR.BOOL[eVarBool.NEXTSTOP_MARK] = true;

            return retval;
        }
        public eNarumiCommandResult AGVCommand(string cmd, string data)
        {
            return AddCommand(cmd + data);
        }

        public eNarumiCommandResult LiftControl(LiftCommand cmd)
        {
            return AddCommand(eAgvCmd.LiftControl, cmd.ToString());
        }
        public eNarumiCommandResult AGVCharge(int chargetID, bool on, int waittime = 3)
        {
            if (on)
                return AddCommand(eAgvCmd.ChargeOn, chargetID.ToString("0000"));
            else
                return AddCommand(eAgvCmd.ChargeOf, chargetID.ToString("0000"));
        }
        public eNarumiCommandResult SetBackturnTime(int time)
        {
            return AddCommand(eAgvCmd.BackTrunResumeTime, time.ToString("0000"));
        }
        public eNarumiCommandResult SetGateOutOffTime(int time)
        {
            return AddCommand(eAgvCmd.GateoutTime, time.ToString("0000"));
        }

        public eNarumiCommandResult TurnGDSCenterScope(UInt16 time)
        {
            if (time > 2000) time = 2000;
            return AddCommand(eAgvCmd.TurnGDSCenterScope, time.ToString("0000"));
        }
        public eNarumiCommandResult AGVMoveLeft180Turn()
        {
            return AddCommand(eAgvCmd.TurnLeft);
        }
        public eNarumiCommandResult AGVMoveRight180Turn()
        {
            return AddCommand(eAgvCmd.TurnRight);
        }
        public eNarumiCommandResult AGVMoveBack180Turn(bool leftTurn)
        {
            var dir = leftTurn ? "L" : "R";
            return AddCommand(eAgvCmd.BackAndTurn, dir);
        }

        public eNarumiCommandResult AGVErrorReset()
        {
            return AddCommand(eAgvCmd.ErrorReset, "FFFF");
        }
        public eNarumiCommandResult AGVTowerLamp(bool on)
        {
            return AddCommand(eAgvCmd.TowerLamp, (on ? "I" : "O"));
        }
        public eNarumiCommandResult AGVSetAddress(int value)
        {
            return AddCommand($"SAD{value:0000}");
        }
        public eNarumiCommandResult AGVSetPanID(string value)
        {
            value = value.PadLeft(4, '0');
            return AddCommand($"SPN{value}");
        }
        public eNarumiCommandResult AGVSetChannel(string value)
        {
            value = value.PadLeft(4, '0');
            return AddCommand($"SCH{value}");
        }

        public eNarumiCommandResult AGVSetTagReinputTime(int value)
        {
            return AddCommand($"STT{value:0000}");
        }

        ///// <summary>
        ///// 전송에 성공한 명령시간
        ///// </summary>
        ////public Dictionary<String, DateTime> LastCommandOKTime { get; set; }
        protected bool SendCommand(string cmdline)
        {
            bool ret = true;
            ACKData = string.Empty;
            var fullcmd = MakeCheckSum(cmdline);
            if (WriteData(fullcmd) == false) ret = false;
            System.Threading.Thread.Sleep(1);
            return ret;
        }

        ManualResetEvent mrecmd = new ManualResetEvent(true);
        public eNarumiCommandResult AddCommand(string cmd, int waitms = 1)
        {
            if (mrecmd.WaitOne(waitms) == false)
            {
                //다른명령을 처리하는 중이므로 대기상태로 반환한다
                return eNarumiCommandResult.Wait;
            }

            //다른신호를 처리하지 못하게한다.
            mrecmd.Reset();

            try
            {
                if (SendCommandFailList.ContainsKey(cmd) == false)
                {
                    //실패기록이 없다
                    var ret = SendCommand(cmd);
                    if (ret == false)
                    {
                        SendCommandFailList.Add(cmd, new NarumiCommandTime(1));
                        return eNarumiCommandResult.Fail;
                    }
                    else return eNarumiCommandResult.Success;
                }
                else
                {
                    //실패기록이 존재한다.
                    //1.동일 명령이 아니면 바로 전송한다
                    var precmd = SendCommandFailList[cmd];

                    //동일명령의 실패기록이 존재한다.
                    //2초간의 간격을 둔다
                    var ts = DateTime.Now - precmd.Time;
                    if (ts.TotalSeconds < 2)
                    {
                        precmd.Time = DateTime.Now;
                        SendCommandFailList[cmd] = precmd;
                        return eNarumiCommandResult.Wait; //대기한다
                    }
                    else
                    {
                        //오류가 누적되었다.
                        var ret = SendCommand(cmd);
                        if (ret == false)
                        {
                            precmd.Time = DateTime.Now;
                            precmd.count += 1;
                            SendCommandFailList[cmd] = precmd;

                            //5회연속 실패했다면 타임아웃처리한다
                            if (precmd.count > 5)
                            {
                                //타임아웃
                                return eNarumiCommandResult.Timeout;
                            }
                            else
                            {
                                //실패
                                return eNarumiCommandResult.Fail;
                            }
                        }
                        else
                        {
                            //전송이성공했으니 키를 제거한다.
                            SendCommandFailList.Remove(cmd);
                            return eNarumiCommandResult.Success;
                        }
                    }

                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"narumi addCommand error : {ex.Message}");
                return eNarumiCommandResult.Error;
            }
            finally
            {
                mrecmd.Set();
            }
        }

        /// <summary>
        /// 턴정보를 설정한다
        /// </summary>
        /// <param name="newstate"></param>
        void TurnInformationSetting(eNarumiTurn newstate)
        {
            if (TurnInformation == null) TurnInformation = new NarumiTurnInfo();
            TurnInformation.Start = DateTime.Now;
            TurnInformation.End = new DateTime(1982, 11, 23);
            if (TurnInformation.State != newstate)
            {
                TurnInformation.Message = $"Change {TurnInformation.State} -> {newstate}";
                TurnInformation.State = newstate;
                RaiseMessage(MessageType.Normal, TurnInformation.Message);
            }
        }

        /// <summary>
        /// 턴이진행중일경우에는 상태를 해제한다
        /// </summary>
        void TurnInformationSettingCancel()
        {
            if (TurnInformation == null) return;
            if (TurnInformation.State == eNarumiTurn.LeftIng || TurnInformation.State == eNarumiTurn.RightIng)
            {
                TurnInformation.End = DateTime.Now;
                TurnInformation.State = eNarumiTurn.None;
                TurnInformation.Message = "Turningstatus cancled";
                RaiseMessage(MessageType.Normal, TurnInformation.Message);
            }
        }


        protected eNarumiCommandResult AddCommand(eAgvCmd command, BunkiData param)
        {
            return AddCommand(command, param.ToString());
        }

        /// <summary>
        /// CBR커맨드를 사용하여 옵션값을 전송 합니다
        /// 11.분기명령 = CBR
        /// </summary>
        /// <param name="param"></param>
        /// <param name="Repeat"></param>
        protected eNarumiCommandResult AddCommand(BunkiData param)
        {
            return AddCommand(eAgvCmd.MoveSet, param.ToString());
        }

        protected eNarumiCommandResult AddCommand(eAgvCmd command, string param = "")
        {
            string cmdString;
            eNarumiCommandResult retval = eNarumiCommandResult.Error;
            switch (command)
            {
                case eAgvCmd.ErrorReset:
                    cmdString = $"SFR{param}";
                    retval = AddCommand(cmdString);
                    break;
                case eAgvCmd.TowerLamp:
                    cmdString = $"CBZ" + $"0299{param}0000";
                    retval = AddCommand(cmdString);
                    break;
                case eAgvCmd.MoveStop:
                    cmdString = $"CST{param}";
                    system1.agv_run_manual = false;
                    retval = AddCommand(cmdString);
                    if (retval == eNarumiCommandResult.Success)
                        TurnInformationSettingCancel();
                    break;
                case eAgvCmd.MoveStart:
                    cmdString = $"CRN{param}";
                    retval = AddCommand(cmdString);
                    if (retval == eNarumiCommandResult.Success)
                        TurnInformationSettingCancel();
                    break;

                case eAgvCmd.ChargeOf:
                    cmdString = $"CBT{param}O0003"; ///0003=충전대기시간
                    retval = AddCommand(cmdString);
                    RaiseMessage(NarumiSerialComm.MessageType.Normal, "충전취소전송");
                    break;
                case eAgvCmd.ChargeOn:
                    cmdString = $"CBT{param}I0003"; ///0003=충전대기시간
                    retval = AddCommand(cmdString);
                    RaiseMessage(NarumiSerialComm.MessageType.Normal, "충전명령전송");
                    break;

                case eAgvCmd.TurnLeft:
                    cmdString = $"CTL0000";
                    retval = AddCommand(cmdString);
                    if (retval == eNarumiCommandResult.Success)
                    {
                        TurnInformationSetting(eNarumiTurn.LeftIng);
                    }
                    break;
                case eAgvCmd.TurnRight:
                    cmdString = $"CTR0000";
                    retval = AddCommand(cmdString);
                    if (retval == eNarumiCommandResult.Success)
                    {
                        TurnInformationSetting(eNarumiTurn.RightIng);
                    }
                    break;
                case eAgvCmd.BackAndTurn:
                    if (param.isEmpty()) param = "L";
                    cmdString = $"CTB000{param}";
                    retval = AddCommand(cmdString);
                    break;
                case eAgvCmd.CallCancle:
                    cmdString = $"CCL0{param}010000";
                    retval = AddCommand(cmdString);
                    break;


                case eAgvCmd.MoveSet:
                    cmdString = $"CBR{param}";
                    retval = AddCommand(cmdString);
                    if (retval == eNarumiCommandResult.Success)
                    {
                        if (param.Length > 3)
                        {
                            var pbschar = param[3];//.Substring(3, 1);
                            if (pbschar == '2') PBSSensor = eNarmiPBSSensor.on;
                            else PBSSensor = eNarmiPBSSensor.off;
                        }
                    }
                    break;
                case eAgvCmd.ManualMove:
                    system1.agv_run_manual = true;
                    cmdString = $"CRT{param}";
                    retval = AddCommand(cmdString);
                    if (retval == eNarumiCommandResult.Success)
                        TurnInformationSettingCancel();
                    break;

                case eAgvCmd.LiftControl:
                    cmdString = "CLF" + param.PadRight(6, '0');
                    retval = AddCommand(cmdString);
                    break;

                case eAgvCmd.CPUReset:
                    cmdString = "CRS0000";
                    retval = AddCommand(cmdString);
                    break;
                case eAgvCmd.TurnGDSCenterScope:
                    if (param.isEmpty()) param = "1000";
                    cmdString = $"SGS{param}";
                    retval = AddCommand(cmdString);
                    break;
                case eAgvCmd.BackTrunResumeTime:
                    cmdString = $"SST{param}";
                    retval = AddCommand(cmdString);
                    break;
                case eAgvCmd.GateoutTime:
                    cmdString = $"SGT{param}";
                    retval = AddCommand(cmdString);
                    break;
            }
            return retval;
        }

    }


}
