using Edge.Core.Parser.BinaryParser.MessageEntity;
using Edge.Core.Parser.BinaryParser.Util;
using Edge.Core.Processor.Dispatcher.Attributes;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.IO.Ports;

namespace Edge.Core.Processor.Communicator
{

    [MetaPartsDescriptor(
        "lang-zh-cn:串口(SerialPort)通讯器lang-en-us:SerialPort communicator",
        "lang-zh-cn:基于串口(SerialPort)技术的通讯器lang-en-us:Serial port based communicator")]
    public class ComPortCommunicator<T> : ICommunicator<byte[], T> where T : MessageTemplateBase
    {
        //static ILog logger = log4net.LogManager.GetLogger("Communicator");
        private ILogger logger = NullLogger.Instance;//NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("Communicator");
        private object syncObject = new object();
        private readonly System.IO.Ports.SerialPort comPort;
        private bool disposed = false;

        #region Wayne grib Echo function

        /// <summary>
        /// Wayne grib board can enable the echo function, that is the comport returned mirro bytes as what 
        /// you send to it.
        /// </summary>
        private bool echoEnabled;
        private byte[] previousWriteBytes;

        #endregion

        /// <summary>
        /// flag for this communicator has been simply called for started or not.
        /// </summary>
        private int isCommunicatorStarted = 0;

        /// <summary>
        /// flag for COM PORT ever received a clean cut bytes message.
        /// 0 for never received, 1 for received.
        /// </summary>
        private int isValidDataEverReceived = 0;

        public IMessageCutter<byte[]> MessageCutter { get; }
        private Edge.Core.Parser.BinaryParser.ParserBase parser;

        public event EventHandler<CommunicatorErrorMessageReadEventArg> OnErrorMessageRead;
        public event EventHandler<CommunicatorEventArg<byte[], T>> OnRawDataWriting;
        public event EventHandler<CommunicatorEventArg<byte[], T>> OnDataReceived;

        /// <summary>
        /// fired only on first valid and clean cut bytes message received from remote peer via COM PORT.
        /// </summary>
        public event EventHandler OnConnected;
        public event EventHandler OnDisconnected;

        /// <summary>
        /// indicates if actively pulling data from comport enabled
        /// </summary>
        private bool aggresivePullComPortEnabled = false;
        /// <summary>
        /// for actively pulling data from comport rather than wait for OnDataReceived callback.
        /// we do see case that comport delayed to report data in.
        /// </summary>
        private Thread pullingSerialPortBufferLoop;
        private int onPullingComPortBuffer = 0;
        public string Identity { get; set; }
        public ComPortCommunicator(IMessageCutter<byte[]> binaryMsgCutter, Edge.Core.Parser.BinaryParser.ParserBase parser,
            string comPortName,
            int baudRate,
            int parity,
            int dataBit,
            int stopBits, IServiceProvider serviceProvider) : this(binaryMsgCutter, parser, comPortName, baudRate, parity, dataBit, stopBits, "false", "false", null, serviceProvider)
        {
        }

        public ComPortCommunicator(IMessageCutter<byte[]> binaryMsgCutter, Edge.Core.Parser.BinaryParser.ParserBase parser,
            string comPortName,
            int baudRate,
            int parity,
            int dataBit,
            int stopBits, string echoEnabled, IServiceProvider serviceProvider)
            : this(binaryMsgCutter, parser, comPortName, baudRate, parity, dataBit, stopBits, echoEnabled, "false", null, serviceProvider)
        {
        }

        public ComPortCommunicator(IMessageCutter<byte[]> binaryMsgCutter, Edge.Core.Parser.BinaryParser.ParserBase parser,
            string comPortName,
            int baudRate,
            int parity,
            int dataBit,
            int stopBits, string echoEnabled, string aggresivePullComPortEnabled, IServiceProvider serviceProvider)
            : this(binaryMsgCutter, parser, comPortName, baudRate, parity, dataBit, stopBits, echoEnabled, aggresivePullComPortEnabled, "null", serviceProvider)
        {
        }

        [ParamsJsonSchemas("ComPortCommunicatorCtorSchema")]
        public ComPortCommunicator(IMessageCutter<byte[]> binaryMsgCutter, Edge.Core.Parser.BinaryParser.ParserBase parser,
            string comPortName,
            int baudRate,
            int parity,
            int dataBit,
            int stopBits, string echoEnabled, string aggresivePullComPortEnabled, string customLoggerFileName, IServiceProvider services)
        {
            this.Identity = comPortName;
            if (services != null)
            {
                var loggerFactory = services.GetRequiredService<ILoggerFactory>();
                this.logger = loggerFactory.CreateLogger("Communicator");
                if (!string.IsNullOrEmpty(customLoggerFileName))
                    if (customLoggerFileName == "*")
                        this.logger = loggerFactory.CreateLogger("DynamicPrivate_Comm_" + comPortName);
                    else
                        this.logger = loggerFactory.CreateLogger("DynamicPrivate_Comm_" + customLoggerFileName);
            }

            if (string.IsNullOrEmpty(echoEnabled)) this.echoEnabled = false;
            else this.echoEnabled = echoEnabled.ToUpper().Trim() == "TRUE";

            if (string.IsNullOrEmpty(aggresivePullComPortEnabled)) this.aggresivePullComPortEnabled = false;
            else this.aggresivePullComPortEnabled = aggresivePullComPortEnabled.ToUpper().Trim() == "TRUE";

            this.MessageCutter = binaryMsgCutter;
            this.parser = parser;
            if (this.MessageCutter != null)
            {
                this.MessageCutter.OnInvalidMessageRead += (____, ______) => logger.LogInformation(this.comPort.PortName + " Invalid data read, detail: " + (______?.Message ?? ""));
                this.MessageCutter.OnMessageCut += (s, _) =>//串口消息都会先进到这里
                {
                    if (logger.IsEnabled(LogLevel.Debug))
                        logger.LogDebug(this.comPort.PortName + " <---Incoming: 0x" + MessageCutter.Message.ToHexLogString());
                    if (0 == Interlocked.CompareExchange(ref this.isValidDataEverReceived, 1, 0))
                    {
                        var safe0 = this.OnConnected;
                        safe0?.Invoke(this, null);
                    }

                    var safe = this.OnDataReceived;
                    var eventArg = new CommunicatorEventArg<byte[], T>();
                    try
                    {
                        eventArg.Message = this.parser.Deserialize(MessageCutter.Message.ToArray()) as T;
                        if (logger.IsEnabled(LogLevel.Debug))
                            logger.LogDebug("      Parsed: " + eventArg.Message.ToLogString());
                        safe?.Invoke(this, eventArg);
                    }
                    catch (Exception ex)
                    {
                        try
                        {
                            logger.LogError(this.comPort.PortName + " Exception in Parsing target bytes message: 0x"
                                + MessageCutter.Message.ToHexLogString() + "\r\n detail: \r\n" + ex.ToString());
                        }
                        catch
                        {
                            logger.LogError(this.comPort.PortName + " Exception in Parsing a bytes message, detail: \r\n"
                                + ex.ToString());
                        }
                    }
                };
            }

            this.comPort = new SerialPort(comPortName, baudRate, (Parity)Enum.Parse(typeof(Parity), parity.ToString()), dataBit, (StopBits)Enum.Parse(typeof(StopBits), stopBits.ToString()))
            {
                ReadTimeout = 1000,
                WriteTimeout = 1000
            };

            if (this.aggresivePullComPortEnabled)
            {
                logger.LogInformation(this.comPort.PortName + "   Aggresive Pull ComPort is Enabled!");

                this.comPort.ReceivedBytesThreshold = 1;
                this.pullingSerialPortBufferLoop = new Thread(() =>//串口打开，会定时读取串口消息调用OnMessageCut事件
                {
                    while (!this.disposed)
                    {
                        if (this.comPort == null || !this.comPort.IsOpen)
                        {
                            // wait port to open 
                            Thread.Sleep(500);
                            continue;
                        }

                        //200ms is our most devices polling interval
                        Thread.Sleep(200);
                        this.PullPortDataBuffer();
                    }
                });
                this.pullingSerialPortBufferLoop.Start();
            }
        }

        public bool Write(T message)
        {
            byte[] rawData = null;
            try
            {
                rawData = this.parser.Serialize(message);
            }
            catch (Exception exxx)
            {
                logger.LogError(this.comPort.PortName + " Serialize " + (message?.GetType().Name ?? "")
                    + " exceptioned in Write(...), will ignore, detail: \r\n" + exxx);
                return false;
            }

            var safe = this.OnRawDataWriting;
            var arg = new CommunicatorEventArg<byte[], T>() { Data = rawData, Message = message, Continue = true };
            safe?.Invoke(this, arg);
            if (!arg.Continue || !this.comPort.IsOpen) return false;
            try
            {
                lock (this.syncObject)
                {
                    if (logger.IsEnabled(LogLevel.Debug))
                        logger.LogDebug(this.comPort.PortName + " Outgoing--->: " + message.GetType().Name + " " + message.ToLogString() + "\r\n     0x" + rawData.ToHexLogString());
                    this.comPort.Write(rawData, 0, rawData.Length);
                    if (logger.IsEnabled(LogLevel.Trace))
                        logger.LogTrace(this.comPort.PortName + " Outgoing done");
                    this.previousWriteBytes = rawData;
                }
                return true;
            }
            catch (TimeoutException timeoutExp)
            {
                logger.LogError(this.comPort.PortName + " Write(...) timed out, detail: " + timeoutExp);
            }

            return false;
        }

        public bool Write(T message, object extraControlParameter)
        {
            throw new NotImplementedException();
        }

        public void Dispose()
        {
            try
            {
                this.comPort.Close();
                this.disposed = true;
                this.isValidDataEverReceived = 0;
            }
            catch (Exception exxx)
            { }
            finally
            {
                this.isCommunicatorStarted = 0;
            }
        }

        public Task<bool> Start()
        {
            if (0 == Interlocked.CompareExchange(ref this.isCommunicatorStarted, 1, 0))
            {
                this.comPort.DataReceived += Port_DataReceived;
                this.comPort.ErrorReceived += (s, a) =>
                {
                    logger.LogError(this.comPort?.PortName ?? "" + " raised an Comport ErroReceived event with type: " + a.EventType);
                };
                try
                {
                    this.comPort.Open();
                    return Task.FromResult(true);
                }
                catch (Exception exxx)
                {
                    logger.LogError("Open ComPort exceptioned: " + exxx);
                    throw;
                    return Task.FromResult(false);
                }

            }

            return Task.FromResult(false);
        }

        private void PullPortDataBuffer()
        {
            if (0 == Interlocked.CompareExchange(ref this.onPullingComPortBuffer, 1, 0))
                try
                {
                    var buffer = new List<byte>();
                    while (this.comPort.BytesToRead > 0)
                    {
                        if (this.echoEnabled && this.previousWriteBytes != null && this.previousWriteBytes.Length > 0)
                        {
                            for (int i = 0; i < this.previousWriteBytes.Length; i++)
                            {
                                var c = this.comPort.ReadByte();
                                if (logger.IsEnabled(LogLevel.Trace))
                                    logger.LogTrace(this.comPort.PortName + ", RAW read(echo): 0x" + c.ToString("X").PadLeft(2, '0'));
                            }

                            this.previousWriteBytes = null;
                            continue;
                        }

                        var b = this.comPort.ReadByte();
                        if (logger.IsEnabled(LogLevel.Trace))
                            logger.LogTrace(this.comPort.PortName + ", RAW read: 0x" + b.ToString("X").PadLeft(2, '0'));
                        buffer.Add((byte)b);
                    }

                    //logger.LogDebug("Read data: " + buffer.ToHexLogString());

                    if (this.MessageCutter != null)
                        this.MessageCutter.Feed(buffer.ToArray());
                }
                catch (Exception ex)
                {
                    logger.LogError($"{this.comPort.PortName}, Exception in PullPortDataBuffer():{Environment.NewLine} {ex}");
                }
                finally
                {
                    this.onPullingComPortBuffer = 0;
                }
            else
            {
                if (logger.IsEnabled(LogLevel.Debug))
                    logger.LogDebug(this.comPort.PortName + " concurrent enter PullPortDataBuffer");
            }
        }

        private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            this.PullPortDataBuffer();
        }
    }
}