using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Design;
using System.Reflection;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace arDev
{
    public partial class MasterK : IDisposable
    {
        #region "Variable"
        public bool bRunMonitor;  //쓰레드작동시그널
        public Boolean bAIMonitor=false; //배경쓰레드에서 아날로그값을 모니터링 합니다.\
        public Boolean bDIOMonitor=true; //배경쓰레드에서 dIO를 모니터링 할것인가?
        public string errorMessage = string.Empty;
        System.Diagnostics.Stopwatch aiMonWatch = new System.Diagnostics.Stopwatch();
        public long LoopDelay;
        protected byte wSTX = 0x05;
        protected byte wETX = 0x04;
        protected byte rSTX = 0x06;
        protected byte rETX = 0x03;
        protected int interchar_delay;
        protected Boolean bSyncRecvOK = false;// 메세지를 수신할 경우 설정됩니다.
        protected Boolean _isInit = false;// 보드가 초기화가 되었는지 확인합니다.
        public Boolean disposed = false;// Dipose 되었는가?
        protected byte _CommandCode = 0x00;// recv command code
        
        protected Boolean _bDlc = false;
        protected int _dlc = 0;
        protected Boolean _bStx = false;    //stx를 찾앗는지?
        protected Boolean _bEtx = false;    //etx를 찾앗는지?
       
        protected Boolean[] _valueI; //입력접점
        protected Boolean[] _valueO; //출력접점
        protected Boolean[] OldvalueI;
        protected Boolean[] OldvalueO;
       
        protected Thread thMonitor;  //배경쓰레드(상태체크 및 이벤트 발생)
        protected ManualResetEvent _mre;
        protected string _mreowner = string.Empty;
        protected System.Diagnostics.Stopwatch wat = new System.Diagnostics.Stopwatch();
        protected System.IO.Ports.SerialPort _device;
      
        public int DOCount { get; protected set; }
        public int DICount { get; protected set; }
        public double ThreadSpeed = 0;
       
        public sRecvMessage LastRecvMessage;
        public sSendMessage lastSendMessage;
        public DateTime lastSendTime;
        public byte[] lastSendBuffer;
        /// 
        /// device index
        /// 
        public byte IDX = 0;
        #endregion
        public MasterK()
        {
            //serial port
            _device = new System.IO.Ports.SerialPort();
            _device.BaudRate = 9600;
            _device.DtrEnable = false;
            _device.WriteTimeout = 2000;
            _device.ReadTimeout = 2000;
            _device.DiscardNull = true;
            //autoreset events
            _mre = new System.Threading.ManualResetEvent(true);
            bRunMonitor = false;
            
            LoopDelay = 150;
            //모든ls산전은 논리적으로 64개가 지정되어있다.
            this.DICount = 64;
            this.DOCount = 64;
            //변수초기화
            this._valueI = new bool[DICount];
            this._valueO = new bool[DOCount];
            this.OldvalueI = new bool[_valueI.Length];
            this.OldvalueO = new bool[_valueO.Length];
            //초기값설정(인풋)
            for (int i = 0; i < _valueI.Length; i++)
            {
                this._valueI[i] = false;
                this.OldvalueI[i] = false;
            }
            //초기값설정(아웃풋)
            for (int i = 0; i < _valueO.Length; i++)
            {
                this._valueO[i] = false;
                this.OldvalueO[i] = false;
            }
        }
        ~MasterK()
        {
            Dispose(false);
        }
        public void Close()
        {
            if (_device.IsOpen)
            {
                if (_mre.WaitOne(3000))
                {
                    _mre.Reset();
                    _mreowner = "Close";
                    _device.Close();
                    _mre.Set();
                }   
                else
                {
                    RaiseMessage(true, "close error timeout");
                }   
            }
        }
        /// 
        /// 개체를 해제합니다.
        /// 
        [Description("")]
        public virtual void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        // Protected implementation of Dispose pattern.
        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;
            if (disposing)
            {
                // Dispose managed resources.
            }
            StopMonitor();
            // Call the appropriate methods to clean up
            // unmanaged resources here.
            // If disposing is false,
            // only the following code is executed.
            // Note disposing has been done.
            disposed = true;
        }
        public string PortName
        {
            get
            {
                return _device.PortName;
            }
            set
            {
                _device.PortName = value;
            }
        }
        public Boolean Init(string port,int baud = 19200)
        {
            Close();
            errorMessage = string.Empty;
            _device.PortName = port;
            _device.BaudRate = baud;
           
            try
            {
                _device.Open();
                _device.DiscardInBuffer();
                _device.DiscardOutBuffer();
                _isInit = true;
                sw_ch = new System.Diagnostics.Stopwatch();
               interchar_delay  = GetIntercharDelay();
               interframe_delay = GetInterframeDelay();
            }
            catch (Exception ex)
            {
                _isInit = false;
                errorMessage = ex.Message;
                RaiseMessage(true, ex.Message);
            }
            return _isInit;
        }
      
        #region "Properties"
        /// 
        /// 입력핀 상태값을 확인합니다.
        /// 
        /// 0부터 시작하는 인덱스값
        /// 
        [Description("")]
        public Boolean INPUT(int arridx)
        {
            if (arridx < 0 || arridx > DICount - 1)
            {
                if (IOErrorMessage != null)
                    IOErrorMessage(this, new MessageEventArgs(true, "INPUT Command : ArrayIndex Error Value=" + arridx.ToString()));
                return false;
            }
            return this._valueI[arridx];
        }
        /// 
        /// 입력핀 상태를 모두 반환합니다.
        /// 
        [Description("")]
        public Boolean[] getInputArray
        {
            get
            {
                return (Boolean[])this._valueI.Clone();
            }
        }
        public byte getInputValueToByte(int startindex)
        {
            System.Collections.BitArray ba = new System.Collections.BitArray(this._valueI);
            byte[] value = new byte[8];
            ba.CopyTo(value, startindex);
            return value[0];
        }
        public Int64 getInputValue
        {
            get
            {
                System.Collections.BitArray ba = new System.Collections.BitArray(this._valueI);
                byte[] value = new byte[8];
                ba.CopyTo(value, 0);
                return BitConverter.ToInt64(value, 0);
            }
        }
        /// 
        /// 출력핀 상태를 모두 반환합니다. 
        /// 
        [Description("")]
        public Boolean[] getOutputArray
        {
            get
            {
                return (Boolean[])this._valueO.Clone();
            }
        }
        /// 
        /// 출력핀 상태 값을 확인합니다.
        /// 
        /// 0부터 시작하는 인덱스값
        /// 
        [Description("")]
        public Boolean OUTPUT(int arridx)
        {
            // get { return (Boolean[])this._valueO.Clone(); }
            if (arridx < 0 || arridx > DOCount - 1)
            {
                if (IOErrorMessage != null)
                    IOErrorMessage(this, new MessageEventArgs(true, "OUTPUT Command : ArrayIndex Error Value=" + arridx.ToString()));
                return false;
            }
            return this._valueO[arridx];
        }
        
        /// 
        /// read digital input port state
        /// 
        /// 
        [Description("")]        
        public string GetInputState()
        {
            System.Text.StringBuilder sb = new StringBuilder();
            for (int i = 0; i < OldvalueI.Length; i++)
                sb.Append(OldvalueI[i] ? "1" : "0");
            return sb.ToString();
        }
        /// 
        /// read digital output port state
        /// 
        /// 
        [Description("")]
        public string GetOutputState()
        {
            System.Text.StringBuilder sb = new StringBuilder();
            for (int i = 0; i < OldvalueO.Length; i++)
                sb.Append(OldvalueO[i] ? "1" : "0");
            return sb.ToString();
        }
        #endregion
        #region "Method"
        /// 
        /// 오류메세지 반환
        /// 
        /// 
        [Description("")]
        public string GetErrorMessage()
        {
            return this.errorMessage;
        }
        
        #region "raise Message"
        /// 
        /// 일반메세지륿 라생시킵니다.
        /// 
        /// 
        /// 
        [Description("")]
        public void RaiseMessage(Boolean isError, string message)
        {
            if (isError) errorMessage = message; //170920
            if (Message != null) Message(this, new MessageEventArgs(isError, message));
        }
        /// 
        /// 지정한 데이터로 바코드가 수신된것처럼 발생시킵니다.(Parser를 사용함)
        /// 
        /// 
        [Description("")]
        public void RaiseDataMessage(eMsgType dir, byte[] Data)
        {
            try
            {
                if (dir == eMsgType.Rx) Parse_RecvBuffer(Data);
                if (DataMessage != null) DataMessage(this, new DataEventArgs(dir,Data));
            }
            catch (Exception ex)
            {
                errorMessage = ex.Message;
                RaiseMessage(true, "RaiseDataMessage:" + ex.Message);
            }
        }
        #endregion
        
        /// 
        /// Input / Output 핀 상태를 지속적으로 읽는 쓰레드작업을 시작합니다.
        /// 
        [Description("")]
        public void RunMonitor(int loopDelay_ = 150)
        {
            this.LoopDelay = loopDelay_;
            if(!this.bRunMonitor)
            {
                this.bRunMonitor = true;
                this.thMonitor = new Thread(new ThreadStart(this.bwMonitor));
                this.thMonitor.IsBackground = true;
                this.thMonitor.Start();
            }
        }
        /// 
        /// INPUT/OUPUT 핀 상태를 모니터링하는 쓰레드를 종료시킵니다.
        /// 
        /// 
        [Description("")]
        public bool StopMonitor()
        {
            try
            {
                this.bRunMonitor = false;
                RaiseMessage(false, "Stop Monitor");
                return true;
            }
            catch (Exception ex)
            {
                RaiseMessage(true, ex.Message);
                return false;
            }
        }
        /// 
        /// 지정한 출력포트의 값을 반전 시킵니다.
        /// 
        /// 
        /// 
        [Description("")]
        public Boolean SetToggle(int arrIndex)
        {
            if (!IsInit) return false;
            if (arrIndex < 0 || (arrIndex + 1) >  DOCount)
                throw new Exception(string.Format("IDX Range Errror({0}~{1})", 0,  DOCount-1));
            Boolean currentValue = OUTPUT(arrIndex);
            return SetOutput(arrIndex, !currentValue);
        }
        /// 
        /// 출력을 설정합니다.
        /// 
        /// 
        /// 
        /// 
        /// 
        [Description("")]
        public Boolean SetOutput(int arrIndex, Boolean Value, int Duration = 0)
        {
            errorMessage = string.Empty;
            if (!IsInit)
            {
                //RaiseMessage(true, "SetOutput:No Init");
                return false;
            }
            //RaiseMessage(false, "Set Output Index=" + arrIndex.ToString() + ",Value=" + Value.ToString());
            if (arrIndex < 0 || (arrIndex + 1)  > DOCount)
            {
                RaiseMessage(true, "SetOutput:ArrIndex Error = " + arrIndex.ToString());
                return false;
            }
            if (!_mre.WaitOne(1000))
            {
                RaiseMessage(true, "SetOutput Wait on timeout");
                return false;
            }
            _mre.Reset();
            if (Duration < 0) Duration = 0;
            int Port = 0x40 + arrIndex; //출력포트는 64번부터 시작한다.
            string cmd = "00WSS0107%PX00" + Port.ToString("X2") + (Value ? "01":"00");
            
            //해당라인에 그값을 쓴다.
            Boolean bRet = true;
            try
            {
                var buffer = WriteSync(cmd);
                string strBuffer = System.Text.Encoding.Default.GetString(buffer);
                if (buffer == null)
                {
                    RaiseMessage(true, "SetOutput:No Response");
                    bRet= false;
                }
                else
                {
                    if(buffer[0] == 0x06)
                    {
                        if (Duration != 0)
                        {
                            var task = new Task(() =>
                            {
                                Thread.Sleep(Duration);
                                string cmd2 = "00WSS0107%PX00" + Port.ToString("X2") + (Value ? "00" : "01");
                                var buffer2 = WriteSync(cmd2);
                                if (buffer2 == null)
                                {
                                    RaiseMessage(true, "SetOutput:Duration toggle Error:No Reponse");
                                }
                            });
                            task.Start(); //비동기로 실행한다.
                        }
                    }
                    else
                    {
                        RaiseMessage(true, "SetOutput:Nak response");
                        bRet = false;
                    }
                    
                }
            }
            catch (Exception ex)
            {
                RaiseMessage(true, "SetOutput:"+ex.Message);
                bRet = false;
            }
            _mre.Set();
            return bRet;
        }
       
      
        /// 
        /// 이벤트를 강제 생성합니다.
        /// 
        /// 
        /// 
        /// 
        [Description("")]
        public void RaiseEvent(eIOPINDIR Direction, int ArrIDX, Boolean newvalue)
        {
            if (IOValueChanged != null)
            {
                //mre.WaitOne();
                //mre.Reset();
                //171226
                if (Direction == eIOPINDIR.INPUT)
                {
                    _valueI[ArrIDX] = newvalue;
                    OldvalueI[ArrIDX] = newvalue;
                }
                else
                {
                    _valueO[ArrIDX] = newvalue;
                    OldvalueO[ArrIDX] = newvalue;
                }
                try
                {
                    if (IOValueChanged != null)
                        IOValueChanged(this, new IOValueEventArgs(Direction, ArrIDX, !newvalue, newvalue));
                }
                catch (Exception ex)
                {
                    if (IOErrorMessage != null)
                        this.IOErrorMessage(this, new MessageEventArgs(true , string.Format( "{0},{1}:{2}:{3}",ex.Message,Direction,ArrIDX,newvalue)));
                }
                //mre.Set();
            }
        }
        public virtual void UserMonitorCode() { }
        
        private byte loopIndex = 0;
        /// 
        /// I/O상태를 모니터링 하여 내부 변수에 값을 저장합니다.
        /// 
        [Description("")]
        protected void bwMonitor()
        {
            DateTime lastaitime = DateTime.Now;
            DateTime lastmottime = DateTime.Now;
            System.Diagnostics.Stopwatch wat = new System.Diagnostics.Stopwatch();
            while (this.bRunMonitor)
            {
                wat.Restart();
                //170905
                if (!IsInit)
                {
                    aiMonWatch.Stop();
                    System.Threading.Thread.Sleep(1000);
                    continue;
                }
                else
                {
                    if (!aiMonWatch.IsRunning) aiMonWatch.Reset();
                }
                //read dio status
                if (loopIndex == 0)
                {
                    //RaiseMessage(false,"Run IOMonitor");
                    if (bDIOMonitor)
                        Read_RelayPort();
                    loopIndex += 1;
                }
                else
                {
                    //RaiseMessage(false, "Run AIMonitor");
                    UserMonitorCode();
                    loopIndex = 0;
                }
                
                wat.Stop();
                ThreadSpeed = wat.ElapsedMilliseconds;
                if(wat.ElapsedMilliseconds < LoopDelay)
                {
                    Thread.Sleep((int)(LoopDelay- wat.ElapsedMilliseconds));
                }
            }
            wat.Stop();            
        }
        #endregion
        #region "Send Method"
        //public Boolean Read_AIPort()
        //{
        //    if (!_mre.WaitOne(1000))
        //    {
        //        RaiseMessage(true, "Read_AIPort Wait on timeout");
        //        return false;
        //    }
        //    //RaiseMessage(false, "Read_AIPort");
        //    _mre.Reset();
        //    System.Threading.Thread.Sleep(25);
        //    _mre.Set();
        //    return true;
        //}
        public Boolean isValid
        {
            get
            {
                var ts = DateTime.Now - lastrecvTime;
                if (ts.TotalSeconds > 5) return false;
                else return true;
            }
        }
        private DateTime lastrecvTime = DateTime.Now;
        public Boolean Read_RelayPort()
        {
            if(!_mre.WaitOne(1000))
            {
                RaiseMessage(true, "Read_RelayPort Wait on timeout");
                return false;
            }
           // RaiseMessage(false, "Read_RelayPort");
            _mre.Reset();
            var packet = MakePacket(0, 'R', "SB","%PW000", 8); //"00RSB06%PW00008"
            var buffer = WriteSync(packet);
            if(buffer != null)
            {
                var frame = Parse_RecvBuffer(buffer);
                if(!frame.isError)
                {
                    this.Run_RelayPortData(ref frame);
                    lastrecvTime = DateTime.Now;    
                }
                    
                LastRecvMessage = frame;
            }
            _mre.Set();
            return buffer != null;
        }
        /// 
        /// 
        /// 
        /// 0~9
        /// R,W
        /// SB,SW ??
        /// %PW000
        /// 1
        /// HexString
        /// 
        public string MakePacket(byte Station,char Command,string CmdType,string Device,byte NumberofData,string data = "")
        {
            string buffer = Station.ToString("X2").PadLeft(2, '0') +
                Command +
                CmdType +
                Device.Length.ToString("X2").PadLeft(2, '0') +
                Device +
                NumberofData.ToString("X2").PadLeft(2, '0');
            if (data != "") buffer += data;
            return buffer;
        }
        #endregion
    }
}