using System.IO.Ports;
using InnovEnergy.Lib.Protocols.Modbus.Protocol;

namespace InnovEnergy.Lib.Protocols.Modbus.Connections;

public class ModbusSerialConnection : ModbusConnection
{
    private String   PortName { get; }
    private Int32    BaudRate { get; }
    private Parity   Parity   { get; }
    private Int32    DataBits { get; }
    private StopBits StopBits { get; }
    private Int32    TimeoutMs  { get; }

    private Byte[]      Buffer     { get; } = new Byte[1024];
    private SerialPort? _SerialPort;
    
    public ModbusSerialConnection(String portName, Int32 baudRate, Parity parity, Int32 dataBits, StopBits stopBits, TimeSpan timeout)
    {
        PortName  = portName;
        BaudRate  = baudRate;
        Parity    = parity;
        DataBits  = dataBits;
        StopBits  = stopBits;
        TimeoutMs = (Int32) timeout.TotalMilliseconds;
    }

    
    public override IReadOnlyList<Byte> Receive(UInt16 bytesToRead)
    {
        var bytesReceived = 0;
        var serialPort = SerialPort();
        
        do
        {
            var received = serialPort.Read(Buffer, bytesReceived, Buffer.Length - bytesReceived);
            if (received < 0)
                throw new NotConnectedException("Serial Connection has been closed");

            bytesReceived += received;
        }
        while (bytesReceived < bytesToRead);

        return new ArraySegment<Byte>(Buffer, 0, bytesToRead);
    }

    public override void Transmit(IEnumerable<Byte> data)
    {
        var array = data.ToArray();
        SerialPort().Write(array, 0, array.Length);
    }

    public override void Open() => SerialPort();

    private SerialPort SerialPort()
    {
        if (_SerialPort == null)
        {
            _SerialPort = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits) { ReadTimeout = TimeoutMs, WriteTimeout = TimeoutMs}; 
            _SerialPort.Open(); 
        }

        return _SerialPort;
    }

    public override void Close()
    {
        if (_SerialPort != null)
        {
            try
            {
                _SerialPort.Close();
                _SerialPort?.Dispose();
            }
            catch (Exception)
            {
                // ignored
            }
        }

        _SerialPort = null;
    }
    
}