using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;


namespace MegaRobo.C00225155.ControlDevices
{
    public enum SerialPortDataFormat
    {
        Char,
        Hex
    }

    public class SerialPortService : IDisposable
    {
        private SerialPort serialPort;

        private bool isCloseing = false;

        private int tryReadNumber = 3;

        public SerialConfigOptions SerialConfigOptions => new SerialConfigOptions
        {
            BaudRate = BaudRate,
            DataBits = DataBits,
            Handshake = Handshake,
            Parity = Parity,
            PortName = PortName,
            StopBits = StopBits
        };

        public Stream BaseStream => serialPort.BaseStream;

        public int BaudRate
        {
            get
            {
                return serialPort.BaudRate;
            }
            set
            {
                serialPort.BaudRate = value;
            }
        }

        public bool BreakState
        {
            get
            {
                return serialPort.BreakState;
            }
            set
            {
                serialPort.BreakState = value;
            }
        }

        public int BytesToRead => serialPort.BytesToRead;

        public int BytesToWrite => serialPort.BytesToWrite;

        public bool CDHolding => serialPort.CDHolding;

        public bool CtsHolding => serialPort.CtsHolding;

        public int DataBits
        {
            get
            {
                return serialPort.DataBits;
            }
            set
            {
                serialPort.DataBits = value;
            }
        }

        public bool DiscardNull
        {
            get
            {
                return serialPort.DiscardNull;
            }
            set
            {
                serialPort.DiscardNull = value;
            }
        }

        public bool DsrHolding => serialPort.DsrHolding;

        public bool DtrEnable
        {
            get
            {
                return serialPort.DtrEnable;
            }
            set
            {
                serialPort.DtrEnable = value;
            }
        }

        public Encoding Encoding
        {
            get
            {
                return serialPort.Encoding;
            }
            set
            {
                serialPort.Encoding = value;
            }
        }

        public Handshake Handshake
        {
            get
            {
                return serialPort.Handshake;
            }
            set
            {
                serialPort.Handshake = value;
            }
        }

        public bool IsOpen => serialPort?.IsOpen ?? false;

        public string NewLine
        {
            get
            {
                return serialPort.NewLine;
            }
            set
            {
                serialPort.NewLine = value;
            }
        }

        public Parity Parity
        {
            get
            {
                return serialPort.Parity;
            }
            set
            {
                serialPort.Parity = value;
            }
        }

        public byte ParityReplace
        {
            get
            {
                return serialPort.ParityReplace;
            }
            set
            {
                serialPort.ParityReplace = value;
            }
        }

        public string PortName
        {
            get
            {
                return serialPort.PortName;
            }
            set
            {
                serialPort.PortName = value;
            }
        }

        public int ReadBufferSize
        {
            get
            {
                return serialPort.ReadBufferSize;
            }
            set
            {
                serialPort.ReadBufferSize = value;
            }
        }

        public int ReadTimeout
        {
            get
            {
                return serialPort.ReadTimeout;
            }
            set
            {
                serialPort.ReadTimeout = value;
            }
        }

        public int ReceivedBytesThreshold
        {
            get
            {
                return serialPort.ReceivedBytesThreshold;
            }
            set
            {
                serialPort.ReceivedBytesThreshold = value;
            }
        }

        public bool RtsEnable
        {
            get
            {
                return serialPort.RtsEnable;
            }
            set
            {
                serialPort.RtsEnable = value;
            }
        }

        public StopBits StopBits
        {
            get
            {
                return serialPort.StopBits;
            }
            set
            {
                serialPort.StopBits = value;
            }
        }

        public int WriteBufferSize
        {
            get
            {
                return serialPort.WriteBufferSize;
            }
            set
            {
                serialPort.WriteBufferSize = value;
            }
        }

        public int WriteTimeout
        {
            get
            {
                return serialPort.WriteTimeout;
            }
            set
            {
                serialPort.WriteTimeout = value;
            }
        }

        public int TryReadSpanTime { get; set; } = 10;


        public string Terminator { get; set; } = null;


        public SerialPortDataFormat DataFormat { get; set; } = SerialPortDataFormat.Char;


        public int TryReadNumber
        {
            get
            {
                return tryReadNumber;
            }
            set
            {
                if (value < 1)
                {
                    throw new ArgumentOutOfRangeException("TryReadNumber", "TryReadNumber must be equal or greater than 1.");
                }

                tryReadNumber = value;
            }
        }

        public event EventHandler<Message> DataReceived;

        public static string[] GetPortNames()
        {
            return SerialPort.GetPortNames();
        }

        public SerialPortService()
        {
        }

        public SerialPortService(string portName)
            : this(portName, 9600, 8, StopBits.One, Parity.None, Handshake.None)
        {
        }

        public SerialPortService(string portName, int baudRate)
            : this(portName, baudRate, 8, StopBits.One, Parity.None, Handshake.None)
        {
        }

        public SerialPortService(string portName, int baudRate = 9600, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, Handshake handshake = Handshake.None)
        {
            if (portName == null)
            {
                throw new ArgumentNullException("portName");
            }

            if (dataBits < 7 || dataBits > 8)
            {
                throw new ArgumentNullException("dataBits", "only 7,8");
            }

            serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
            {
                Handshake = handshake,
                WriteTimeout = 3000,
                ReadTimeout = 4000
            };
        }

        public SerialPortService(SerialConfigOptions options)
            : this(options.PortName, options.BaudRate, options.DataBits, options.StopBits, options.Parity, options.Handshake)
        {
            if (options == null)
            {
                throw new ArgumentNullException("options");
            }
        }

        public void Open()
        {
            if (serialPort == null)
            {
                throw new InvalidOperationException("Not initialized");
            }

            if (!serialPort.IsOpen)
            {
                serialPort.Open();
                serialPort.DataReceived += SerialPort_DataReceived;
            }
        }

        public void Close()
        {
            if (IsOpen)
            {
                isCloseing = true;
                Thread.Sleep(TryReadSpanTime + 5);
                serialPort.DataReceived -= SerialPort_DataReceived;
                Thread.Sleep(TryReadSpanTime + 5);
                serialPort.Close();
            }
        }

        public void DiscardInBuffer()
        {
            serialPort.DiscardInBuffer();
        }

        public void DiscardOutBuffer()
        {
            serialPort.DiscardOutBuffer();
        }

        public void Dispose()
        {
            Close();
            serialPort = null;
        }

        public int ReadByte()
        {
            return serialPort.ReadByte();
        }

        public int ReadChar()
        {
            return serialPort.ReadChar();
        }

        public string ReadLine()
        {
            return serialPort.ReadLine();
        }

        public string ReadExisting()
        {
            return serialPort.ReadExisting();
        }

        public string ReadTo(string value)
        {
            return serialPort.ReadTo(value);
        }

        public string ReadString()
        {
            try
            {
                string result = null;
                byte[] array = TryRead();
                if (array != null && array.Length != 0)
                {
                    switch (DataFormat)
                    {
                        case SerialPortDataFormat.Char:
                            result = serialPort.Encoding.GetString(array);
                            break;
                        case SerialPortDataFormat.Hex:
                            result = array.ToHexString();
                            break;
                    }
                }

                return result;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }
        }

        public byte[] Read()
        {
            try
            {
                return TryRead();
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }
        }

        private byte[] TryRead()
        {
            int num = 0;
            int num2 = 0;
            bool flag = false;
            List<byte> list = new List<byte>();
            byte[] array = null;
            if (Terminator != null)
            {
                array = Terminator.HexToByte();
                num2 = array.Length;
            }

            while ((serialPort.BytesToRead > 0 || !flag) && num < tryReadNumber)
            {
                int num3 = ((serialPort.BytesToRead < serialPort.ReadBufferSize) ? serialPort.BytesToRead : serialPort.ReadBufferSize);
                byte[] array2 = new byte[num3];
                serialPort.Read(array2, 0, array2.Length);
                list.AddRange(array2);
                if (array != null && num2 > 0)
                {
                    byte[] array3 = new byte[num2];
                    if (array2.Length >= num2)
                    {
                        Buffer.BlockCopy(array2, array2.Length - num2, array3, 0, num2);
                    }
                    else
                    {
                        if (list.ToArray().Length < num2)
                        {
                            continue;
                        }

                        byte[] array4 = list.ToArray();
                        Buffer.BlockCopy(array4, array4.Length - num2, array3, 0, num2);
                    }

                    flag = array.Length != 0 && array3.SequenceEqual(array);
                }

                if (TryReadSpanTime > 0)
                {
                    Thread.Sleep(TryReadSpanTime);
                }

                num++;
            }

            return (list.Count > 0) ? list.ToArray() : null;
        }

        public int WriteHexString(string str)
        {
            try
            {
                byte[] array = str.HexToByte();
                return Write(array, 0, array.Length);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }
        }

        public int WriteAsciiString(string str)
        {
            try
            {
                byte[] bytes = serialPort.Encoding.GetBytes(str);
                return Write(bytes, 0, bytes.Length);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }
        }

        public int Write(byte[] bytes)
        {
            return Write(bytes, 0, bytes.Length);
        }

        public void Write(string text)
        {
            serialPort.Write(text);
        }

        public int Write(byte[] bytes, int offset, int count)
        {
            try
            {
                serialPort.DiscardInBuffer();
                serialPort.Write(bytes, offset, count);
                return count;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }
        }

        public void WriteLine(string str)
        {
            serialPort.WriteLine(str);
        }

        public byte[] SendAndReceive(IEnumerable<byte> datas, TimeSpan timeout)
        {
            byte[] mReply = null;
            DataReceived += SerialPortDataReceived;
            int num = Write(datas.ToArray());
            Stopwatch stopwatch = Stopwatch.StartNew();
            while (mReply == null && (double)stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
            {
                Thread.Sleep(50);
            }

            DataReceived -= SerialPortDataReceived;
            return mReply;
            void SerialPortDataReceived(object sender, Message msg)
            {
                mReply = msg.Data;
            }
        }

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (!isCloseing)
            {
                byte[] data = TryRead();
                Message e2 = new Message(data, Encoding);
                this.DataReceived?.Invoke(this, e2);
            }
        }
    }
    /// <summary>
    /// 扩展方法  byte[]
    /// </summary>
    public static class Extension
    {
        public static string ToHexString(this byte[] bytes, string separator = " ")
        {
            if (bytes == null || bytes.Length < 1)
            {
                return null;
            }

            return string.Join(separator, bytes.Select((byte b) => b.ToString("X2")).ToArray());
        }

        public static byte[] HexToByte(this string hex)
        {
            hex = hex.Replace(" ", "");
            byte[] array = new byte[hex.Length / 2];
            for (int i = 0; i < hex.Length; i += 2)
            {
                array[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            }

            return array;
        }
    }

    public class SerialConfigOptions
    {
        public string PortName { get; set; }

        public int BaudRate { get; set; } = 9600;


        public int DataBits { get; set; } = 8;


        public StopBits StopBits { get; set; } = StopBits.One;


        public Parity Parity { get; set; } = Parity.None;


        public Handshake Handshake { get; set; } = Handshake.None;


        public SerialConfigOptions()
        {
        }

        public SerialConfigOptions(string portName, int baudRate = 9600, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, Handshake handshake = Handshake.None)
        {
            PortName = portName ?? throw new ArgumentNullException("portName");
            if (dataBits < 7 || dataBits > 8)
            {
                throw new ArgumentNullException("dataBits", "only 7,8");
            }

            BaudRate = baudRate;
            DataBits = dataBits;
            StopBits = stopBits;
            Parity = parity;
            Handshake = handshake;
        }

        public SerialConfigOptions(string portName, int baudRate = 9600, int dataBits = 8, int stopBits = 1, int parity = 0, int handshake = 0)
            : this(portName, baudRate, dataBits, GetStopBits(stopBits), GetParity(parity), GetHandshake(handshake))
        {
        }

        public SerialConfigOptions(string portName, int baudRate = 9600, int dataBits = 8, string stopBits = "one", string parity = "none", string handshake = "none")
            : this(portName, baudRate, dataBits, GetStopBits(stopBits), GetParity(parity), GetHandshake(handshake))
        {
        }

        public static Parity GetParity(string parityVal)
        {
            switch (parityVal?.ToLower())
            {
                case "0":
                case "n":
                case "none":
                    return Parity.None;
                case "1":
                case "o":
                case "odd":
                    return Parity.Odd;
                case "2":
                case "e":
                case "even":
                    return Parity.Even;
                case "3":
                case "m":
                case "mark":
                    return Parity.Mark;
                case "4":
                case "s":
                case "space":
                    return Parity.Space;
                default:
                    return Parity.None;
            }
        }

        public static Parity GetParity(int parityVal)
        {
            return GetParity(parityVal.ToString());
        }

        public static StopBits GetStopBits(string stopBitsVal)
        {
            switch (stopBitsVal?.ToLower())
            {
                case "0":
                case "n":
                case "none":
                    return StopBits.None;
                case "1":
                case "o":
                case "one":
                    return StopBits.One;
                case "2":
                case "t":
                case "two":
                    return StopBits.Two;
                case "3":
                case "1.5":
                case "f":
                case "of":
                case "opf":
                case "onepointfive":
                    return StopBits.OnePointFive;
                default:
                    return StopBits.One;
            }
        }

        public static StopBits GetStopBits(int stopBitsVal)
        {
            return GetStopBits(stopBitsVal.ToString());
        }

        public static Handshake GetHandshake(string shake)
        {
            switch (shake?.ToLower())
            {
                case "0":
                case "n":
                case "none":
                    return Handshake.None;
                case "1":
                case "x":
                case "xoxo":
                case "xonxoff":
                case "software":
                    return Handshake.XOnXOff;
                case "2":
                case "r":
                case "rts":
                case "requesttosend":
                case "hardware":
                    return Handshake.RequestToSend;
                case "3":
                case "rx":
                case "rtsxx":
                case "requesttosendxonxoff":
                case "both":
                    return Handshake.RequestToSendXOnXOff;
                default:
                    return Handshake.None;
            }
        }

        public static Handshake GetHandshake(int shake)
        {
            return GetHandshake(shake.ToString());
        }
    }

    public class Message
    {
        private readonly Encoding _encoder = null;

        public byte[] Data { get; private set; }

        public string MessageString => _encoder.GetString(Data);

        public Message(byte[] data, Encoding stringEncoder)
        {
            Data = data;
            _encoder = stringEncoder;
        }

        public string ReadString(SerialPortDataFormat dataFormat= SerialPortDataFormat.Char)
        {
            string result = null;
            byte[] data = Data;
            if (Data != null && Data.Length != 0)
            {
                switch (dataFormat)
                {
                    case SerialPortDataFormat.Char:
                        result = _encoder.GetString(data);
                        break;
                    case SerialPortDataFormat.Hex:
                        result = data.ToHexString();
                        break;
                }
            }

            return result;
        }
    }
}
