using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;

namespace iq2dev.com.IQCommLib
{
    public class IQEthernet : ITransmitComm
    {
        #region Fields

        private TcpClient _tcpClient = null!;
        string _ipAddress;
        int _ipPort;

        #endregion Fields

        #region Properties

        public string ConnectionString => $"{_ipAddress}:{_ipPort}";

        #endregion Properties

        public IQEthernet(string ipAddress, int ipPort)
        {
            _tcpClient = new TcpClient();
            _ipAddress = ipAddress;
            _ipPort = ipPort;
            _tcpClient.ReceiveTimeout = 1000;
            _tcpClient.SendTimeout = 1000;
            _tcpClient.NoDelay = true;
            //_tcpClient.SendBufferSize = 256;
        }

        public void ClearBuffer()
        {
            if (_tcpClient == null)
            {
                return;
            }

            while ((_tcpClient != null) && (_tcpClient.Available > 0))
            {
                byte[] streamBuffer = new byte[_tcpClient.Available + 1];
                _tcpClient.GetStream().Read(streamBuffer, 0, _tcpClient.Available);
            }
        }

        #region ITransmitComm

        public List<byte> SendRecv(IQCommChannel channel, List<byte> transmitData)
        {
            transmitData.Insert(0, (byte)channel);
            transmitData.Insert(0, Convert.ToByte(transmitData.Count));
            transmitData[1] = Convert.ToByte(transmitData[1] | IQComport.get6bitChecksum(transmitData.ToArray()));
            byte command = transmitData[2];
            _tcpClient.GetStream().Write(transmitData.ToArray(), 0, transmitData.Count);

            //Wait for trace a message
            int messageSize = _tcpClient.GetStream().ReadByte();
            if (messageSize < 0)
            {
                throw new Exception("the end of the stream has been read");
            }

            //check massage size
            if (messageSize < 2)
            {
                //invalid message. CLear wrong packet
                DateTime startDateTime = DateTime.Now;
                while ((DateTime.Now - startDateTime).TotalMilliseconds < 5)
                {
                    ClearBuffer();
                }
                throw new TimeoutException("Continue with next packet");
            }

            byte[] recvBuffer = new byte[1 + messageSize];
            recvBuffer[0] = Convert.ToByte(messageSize);

            int readBytes = _tcpClient.GetStream().Read(recvBuffer, 1, messageSize);
            while (readBytes < messageSize)
            {
                readBytes += _tcpClient.GetStream().Read(recvBuffer, readBytes + 1, messageSize - readBytes);
            }

            // check checksum
            byte cks = (byte)(recvBuffer[1] & 0x3F);
            if (cks != IQComport.get6bitChecksum(recvBuffer))
                throw new IQCommException("response checksum error");

            // check error channel
            if ((recvBuffer[1] & 0xC0) == (byte)(IQCommChannel.Error))
            {
                String errMessage = "";
                if (recvBuffer[3] == 0x56)
                {
                    var isduType = recvBuffer[4] == 1 ? "read" : "write";
                    UInt16 index = (UInt16)(((recvBuffer[5] << 8) & 0xFF00) + recvBuffer[6]);
                    byte subindex = recvBuffer[7];
                    UInt16 errorCode = (UInt16)(((recvBuffer[8] << 8) & 0xFF00) + recvBuffer[9]);
                    throw new IQCommException(errorCode, $"ISDU {isduType}, index: {index}, subindex: {subindex} cached operation failed" +
                        $"with 0x" + String.Format("{0:X04}", errorCode));
                }
                else
                {
                    UInt16 errorCode = (UInt16)(((recvBuffer[3] << 8) & 0xFF00) + recvBuffer[4]);
                    if (IQCommErrorCodes.description.ContainsKey(errorCode))
                        errMessage += IQCommErrorCodes.description[errorCode];
                    throw new IQCommException(errorCode, "IQ comm error 0x" + String.Format("{0:X04}", errorCode) + " (" + errMessage + ")");
                }
            }
            // check confirm channel
            else if ((recvBuffer[1] & 0xC0) != (byte)(channel))
            {
                throw new IQCommException("iq comm channel is not confirmed within response");
            }

            if (recvBuffer[2] != (0x80 | command))
            {
                throw new IQCommException("IQI command confirmation is absent");
            }

            return recvBuffer.ToList().GetRange(3, recvBuffer.Length - 3);

        }

        public string Connect()
        {
            if (_tcpClient.Connected)
            {
                Disconnect();
            }
            _tcpClient = new TcpClient();
            _tcpClient.Connect(_ipAddress, _ipPort);
            return $"{_ipAddress}:{_ipPort}";
        }

        public void Disconnect()
        {
            _tcpClient.Client.Shutdown(SocketShutdown.Both);
            _tcpClient.Close();
        }

        #endregion ITransmitComm
    }
}