using System.IO.Ports;

namespace RioJoy.Core.Serial;

/// <summary>
/// <see cref="IRioTransport"/> backed by a physical <see cref="SerialPort"/>.
/// Opens the port at the RIO's 9600 8N1 settings and pulses DTR on open as a
/// board reset/handshake (SETDTR, 50 ms, CLRDTR — port of
/// <c>OpenConnection</c>/<c>SetupConnection</c>, riovjoy2.cpp#L747; see
/// docs/PROTOCOL.md §1).
/// </summary>
public sealed class SerialPortTransport : IRioTransport
{
    /// <summary>RIO link bit rate.</summary>
    public const int BaudRate = 9600;

    /// <summary>DTR reset-pulse hold time on open.</summary>
    public static readonly TimeSpan DtrPulse = TimeSpan.FromMilliseconds(50);

    private readonly SerialPort _port;
    private readonly Stream _stream;

    public SerialPortTransport(string portName)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(portName);

        _port = new SerialPort(portName, BaudRate, Parity.None, 8, StopBits.One)
        {
            Handshake = Handshake.None,
            // The framing state machine handles timing; keep reads non-throwing.
            ReadTimeout = SerialPort.InfiniteTimeout,
            WriteTimeout = SerialPort.InfiniteTimeout,
        };

        _port.Open();

        // DTR reset pulse: assert, hold, release.
        _port.DtrEnable = true;
        Thread.Sleep(DtrPulse);
        _port.DtrEnable = false;

        _stream = _port.BaseStream;
    }

    public string Description => $"{_port.PortName} @ {BaudRate} 8N1";

    public ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken) =>
        _stream.ReadAsync(buffer, cancellationToken);

    public ValueTask WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken) =>
        _stream.WriteAsync(data, cancellationToken);

    public void Dispose()
    {
        // Disposing closes and releases the COM port for other owners.
        _port.Dispose();
    }
}
