using Aitex.Core.RT.DataCenter;
using Aitex.Core.RT.Device;
using Aitex.Core.RT.Event;
using Aitex.Core.RT.Log;
using Aitex.Core.RT.OperationCenter;
using Aitex.Core.RT.SCCore;
using Aitex.Core.Util;
using MECF.Framework.Common.Communications;
using System;
using System.Collections.Generic;

namespace MECF.Framework.RT.EquipmentLibrary.HardwareUnits.Servo.NAIS
{
    public class NAISServo : BaseDevice, IConnection, IDevice
    {
        private NAISServoConnection _connection;

        private bool _isOn;

        private bool _activeMonitorStatus;

        private int _errorCode;

        private RD_TRIG _trigPumpOnOff = new RD_TRIG();
        private R_TRIG _trigError = new R_TRIG();
        private R_TRIG _trigOverTemp = new R_TRIG();

        private R_TRIG _trigWarningMessage = new R_TRIG();
        private string _lastError = string.Empty;
        private R_TRIG _trigCommunicationError = new R_TRIG();
        private R_TRIG _trigRetryConnect = new R_TRIG();

        private PeriodicJob _thread;

        private LinkedList<HandlerBase> _lstHandler = new LinkedList<HandlerBase>();

        private object _locker = new object();

        private bool _enableLog = true;

        private string _scRoot;
        public bool Status { get; set; }

        private string[] StatusStringList = new string[] { "normal", "running fault", "stop fault" };

        public string Address
        {
            get; set;
        }

        public bool IsConnected
        {
            get
            {
                return _connection != null && _connection.IsConnected;
            }
        }
        public bool AlarmStatus { get; set; }
        public bool PositionComplete { get; set; }
        public bool MotorBusy { get; set; }

        public bool IsBusy { get; set; }
        public bool IsStbOff { get; set; } //StbOff消息是否发送

        public bool Connect()
        {
            return true;
        }

        public bool Disconnect()
        {
            return true;
        }

        public NAISServo(string module, string scRoot, string name) : base(module, name, name, name)
        {

            _scRoot = scRoot;
            _activeMonitorStatus = true;
        }

        public void Query()
        { 
            lock (_locker)
            {
                _lstHandler.AddLast(new NAISServoQueryHandler(this, "QueryAlarm", 0x01, 0x01, 0x00, 0xA1, 0x00, 0x01));
                _lstHandler.AddLast(new NAISServoQueryHandler(this, "QueryPosition", 0x01, 0x01, 0x00, 0xA2, 0x00, 0x01));
                _lstHandler.AddLast(new NAISServoQueryHandler(this, "QueryMotorStatus", 0x01, 0x01, 0x01, 0x40, 0x00, 0x01));
            }
        }
        public void QueryPosition()
        {
            _lstHandler.AddLast(new NAISServoSetHandler(this, "QueryPosition", 0x01, 0x01, 0x00, 0xA2, 0x00, 0x01));
        }
        public void QueryMotorStatus()
        {
            _lstHandler.AddLast(new NAISServoSetHandler(this, "QueryMotorStatus", 0x01, 0x01, 0x01,0x40, 0x00, 0x01));
        }
        public void SetServoOn()
        {
            IsBusy = true;
            _lstHandler.Clear();
            lock (_locker)
            {
                _lstHandler.AddLast(new NAISServoSetHandler(this, "Set Servo On", 0x01, 0x05, 0x00, 0x60, 0xff, 0x00));
            }
        }
        public void SetBlockHome()
        {
            IsBusy = true;
            _lstHandler.Clear();
            lock (_locker)
            {
                _lstHandler.AddLast(new NAISServoSetHandler(this, "Set BlockNo High", 0x01, 0x06, 0x44, 0x14, 0x00, 0x00));
            }
        }
        public void SetBlockNoHigh()
        {
            IsStbOff = false;
            IsBusy = true;
            _lstHandler.Clear();
            lock (_locker)
            {
                _lstHandler.AddLast(new NAISServoSetHandler(this, "Set BlockNo High", 0x01, 0x06, 0x44, 0x14, 0x00, 0x01));
            }
        }
        public void SetBlockNoLow()
        {
            IsStbOff = false;
            IsBusy = true;
            _lstHandler.Clear();
            lock (_locker)
            {
                _lstHandler.AddLast(new NAISServoSetHandler(this, "Set BlockNo Low", 0x01, 0x06, 0x44, 0x14, 0x00, 0x02));
            }
        }
        public void SetStbOn()
        {
            IsStbOff = false;
            IsBusy = true;
            _lstHandler.Clear();
            lock (_locker)
            {
                _lstHandler.AddLast(new NAISServoSetHandler(this, "Set Stb On", 0x01, 0x05, 0x01, 0x20, 0xff, 0x00));
            }
        }
        public void SetStbOff()
        {
            IsStbOff = true;
            IsBusy = true;
            _lstHandler.Clear();
            lock (_locker)
            {
                _lstHandler.AddLast(new NAISServoSetHandler(this, "Set Stb Off", 0x01, 0x05, 0x01, 0x20, 0x00, 0x00));
            }
        }

        public void ClearOff()
        {
            _lstHandler.AddLast(new NAISServoSetHandler(this, "Clear Error Off", 0x01, 0x05, 0x00, 0x61, 0x00, 0x00));
        }

        public void ClearOn()
        {
            _lstHandler.AddLast(new NAISServoSetHandler(this, "Clear Error On", 0x01, 0x05, 0x00, 0x61, 0xFF, 0x00));
        }


        protected bool fAbort(object[] param)
        {
            lock (_locker)
            {
                IsBusy = false;
                _connection.ForceClear();
                _lstHandler.Clear();
            }
            return true;
        }
        public bool Initialize()
        {
            // string portName = SC.GetStringValue($"{_scRoot}.{Module}.{Name}.Address");
            string portName = SC.GetStringValue($"{Name}.Address");
            Address = portName;
            _enableLog = SC.GetValue<bool>($"{Name}.EnableLogMessage");

            _connection = new NAISServoConnection(portName);
            _connection.EnableLog(_enableLog);

            if (_connection.Connect())
            {
                EV.PostInfoLog(Module, $"{Module}.{Name} connected");
            }
            
            _thread = new PeriodicJob(1000, OnTimer, $"{Name} MonitorHandler", true);

            OP.Subscribe($"{Module}.{Name}.SetServoOn", (cmd, param) =>
            {
                Set("SetServoOn");
                EV.PostInfoLog(Module, $"{Name}Set Servo On.");
                return true;
            });
            OP.Subscribe($"{Module}.{Name}.SetBlockHome", (cmd, param) =>
            {
                Set("SetBlockHome");
                EV.PostInfoLog(Module, $"{Name}Set Block Home.");
                return true;
            });
            OP.Subscribe($"{Module}.{Name}.SetBlockUp", (cmd, param) =>
            {
                Set("SetBlockUp");
                EV.PostInfoLog(Module, $"{Name}Set Block No High.");
                return true;
            });
            OP.Subscribe($"{Module}.{Name}.SetBlockLow", (cmd, param) =>
            {
                Set("SetBlockLow");
                EV.PostInfoLog(Module, $"{Name}Set Block No Low.");
                return true;
            });
            OP.Subscribe($"{Module}.{Name}.SetStbOn", (cmd, param) =>
            {
                Set("SetStbOn");
                EV.PostInfoLog(Module, $"{Name}Set Stb On.");
                return true;
            });
            OP.Subscribe($"{Module}.{Name}.SetStbOff", (cmd, param) =>
            {
                Set("SetStbOff");
                EV.PostInfoLog(Module, $"{Name}Set Stb Off.");
                return true;
            });

            OP.Subscribe($"{Module}.{Name}.ClearOn", (cmd, param) =>
            {
                ClearOn();
                //Set("SetStbOff");
                EV.PostInfoLog(Module, $"{Name}Clear Error On.");
                return true;
            });
            OP.Subscribe($"{Module}.{Name}.SetAbort", (cmd, param) =>
            {
                fAbort(param);
                return true;
            });

            DATA.Subscribe($"{Module}.{Name}.AlarmStatus", () => AlarmStatus);
            DATA.Subscribe($"{Module}.{Name}.PositionComplete", () => PositionComplete);
            DATA.Subscribe($"{Module}.{Name}.MotorBusy", () => MotorBusy);
            ConnectionManager.Instance.Subscribe($"{Name}", this);
            //SetServoOn();
            return true;
        }
        public R_TRIG _trigSevroOn = new R_TRIG();
        public int _connecteTimes { get; set; }
        private bool OnTimer()
        {
            try
            {          
                _connection.MonitorTimeout();

                if (!_connection.IsConnected || _connection.IsCommunicationError)
                {
                    lock (_locker)
                    {
                        _trigSevroOn.RST = true;
                        IsBusy = false;
                        _lstHandler.Clear();  
                    }
                    _trigRetryConnect.CLK = !_connection.IsConnected;
                    //_connection.SetPortAddress(SC.GetStringValue($"{_scRoot}.{Name}.Address"));
                    // _connection.SetPortAddress(SC.GetStringValue($"{Name}.Address"));

                    _trigRetryConnect.CLK = !_connection.IsConnected;
                    if (_trigRetryConnect.Q)
                    {
                        //_connection.SetPortAddress(SC.GetStringValue($"{_scRoot}.{Name}.Address"));
                        if (!_connection.Connect())
                        {
                            EV.PostAlarmLog(Module, $"Can not connect with {_connection.Address}, {Module}.{Name}");
                        }
                    }
                    if (_connecteTimes++ < 3)
                        _connection.ForceClear();
                    else _connecteTimes = 4;
                   // if(_connection.IsConnected && !_connection.IsCommunicationError) SetServoOn();
                    return true;
                }
                _trigSevroOn.CLK = true;
                if (_trigSevroOn.Q) 
                    SetServoOn();
                HandlerBase handler = null;
                if (!_connection.IsBusy)
                {
                    lock (_locker)
                    {
                        if (_lstHandler.Count == 0 && _activeMonitorStatus &&!IsBusy)
                        {
                            Query();
                        }

                        if (_lstHandler.Count > 0)
                        {
                            handler = _lstHandler.First.Value;

                            _lstHandler.RemoveFirst();
                        }
                    }

                    if (handler != null)
                    {
                        _connection.Execute(handler);
                    }
                }
                return true;

            }
            catch (Exception ex)
            {
                LOG.Write(ex);
            }

            return true;
        }

        internal void NoteError()
        {

        }

        public void Monitor()
        {
            try
            {
                _connection.EnableLog(_enableLog);
                if (_connecteTimes < 4) return;
                _trigCommunicationError.CLK = _connection.IsCommunicationError;
                if (_trigCommunicationError.Q)
                {
                    EV.PostAlarmLog(Module, $"{Module}.{Name} communication error, {_connection.LastCommunicationError}");
                }
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
            }

        }

        public void Terminate()
        {
            try
            {
                if (_connection != null)
                {
                    _connection.Disconnect();
                    _connection = null;
                }
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
            }

        }

        public void Reset()
        {
            _connecteTimes = 0;
            _trigError.RST = true;
            _trigOverTemp.RST = true;
            _trigWarningMessage.RST = true;

            _connection.SetCommunicationError(false, "");
            _trigCommunicationError.RST = true;
            
            _enableLog = SC.GetValue<bool>($"{Name}.EnableLogMessage");

            _trigRetryConnect.RST = true;
            
        }

        public void SetActiveMonitor(bool active)
        {
            _activeMonitorStatus = active;
        }


        internal void NoteOnOff(bool isOn)
        {
            _isOn = isOn;
        }
        public R_TRIG _trigAlarm = new R_TRIG();
        public void PraseData(string name, byte[] buffer)
        {
            switch (name)
            {

                case "QueryAlarm":
                    {
                        AlarmStatus = buffer[3] == 0 ? false : true;
                        _trigAlarm.CLK = AlarmStatus;
                        if (_trigAlarm.Q)
                        {
                            EV.PostAlarmLog("PM","Servo Is Error State.");
                        }
                    }
                    break;
                case "QueryPosition":
                    {
                        PositionComplete = buffer[3] == 0 ? false : true;
                    }
                    break;
                case "QueryMotorStatus":
                    {
                        MotorBusy = buffer[3] == 0 ? false : true;
                    }
                    break;

            }
        }

        public void Set(string name)
        {
            lock (_locker)
            {
                _lstHandler.Clear();
                if (_connection.IsBusy) _connection.ForceClear();
                List<byte> cmddata=null;
                switch (name)
                {
                    case "SetServoOn":
                            cmddata = new List<byte>() { 0x01, 0x05, 0x00, 0x60, 0xff, 0x00};    
                        break;
                    case "SetBlockHome":
                            cmddata = new List<byte>() { 0x01, 0x06, 0x44, 0x14, 0x00, 0x00 };
                        break;
                    case "SetBlockUp":
                            cmddata = new List<byte>() { 0x01, 0x06, 0x44, 0x14, 0x00, 0x01 };   
                        break;                   
                    case "SetBlockLow":
                            cmddata = new List<byte>() { 0x01, 0x06, 0x44, 0x14, 0x00, 0x02 };
                        break;
        
                    case "SetStbOn":
                            cmddata = new List<byte>() { 0x01, 0x05, 0x01, 0x20, 0xff, 0x00 };
                        break;
                    case "SetStbOff":
                            cmddata = new List<byte>() { 0x01, 0x05, 0x01, 0x20, 0x00, 0x00 }; 
                        break;
                }
               
                byte[] checksum = ModRTU_CRC(cmddata.ToArray());
                cmddata.Add(checksum[0]);
                cmddata.Add(checksum[1]);

                _connection.SendMessage(cmddata.ToArray());
            }
        }
        

        public void SetErrorCode(int errorCode)
        {
            _errorCode = errorCode;
        }


        public void SetError(string reason)
        {
            _trigWarningMessage.CLK = true;
            if (_trigWarningMessage.Q)
            {
                EV.PostWarningLog(Module, $"{Module}.{Name} error, {reason}");
            }
        }
        private static byte[] ModRTU_CRC(byte[] buffer)
        {
            ushort crc = 0xFFFF;
            // var buf = System.Text.Encoding.UTF8.GetBytes(String.Join(Environment.NewLine, buffer));
            var buf = buffer;
            var len = buffer.Length;

            for (var pos = 0; pos < len; pos++)
            {
                crc ^= buf[pos]; // XOR byte into least sig. byte of crc

                for (var i = 8; i != 0; i--)
                    // Loop over each bit
                    if ((crc & 0x0001) != 0)
                    {
                        // If the LSB is set
                        crc >>= 1; // Shift right and XOR 0xA001
                        crc ^= 0xA001;
                    }
                    else // Else LSB is not set
                    {
                        crc >>= 1; // Just shift right
                    }
            }

            // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes)
            return BitConverter.GetBytes(crc);

        }


        private bool setDown = true;
        private void SetServoUpOrDown()
        { 
            
        }
    }
}
