namespace Mbproxy.Proxy;

/// <summary>
/// Pure, allocation-free helpers for parsing Modbus Application Protocol (MBAP) headers.
///
/// MBAP frame layout (7-byte header + PDU):
///   [0..1]  TxId (big-endian uint16)
///   [2..3]  ProtocolId (big-endian uint16; always 0 for standard Modbus)
///   [4..5]  Length (big-endian uint16; covers UnitId + PDU bytes)
///   [6]     UnitId
///   [7..]   PDU (function code + data); length is (lengthField - 1) bytes
///
/// Total frame bytes = 6 (fixed header without length's coverage) + lengthField
///                   = 7 (header) + (lengthField - 1) (PDU body without UnitId).
/// </summary>
internal static class MbapFrame
{
    /// <summary>Number of bytes in the MBAP header (TxId + ProtocolId + Length + UnitId).</summary>
    public const int HeaderSize = 7;

    /// <summary>Maximum MBAP PDU body size (Modbus spec max: 253 bytes).</summary>
    public const int MaxPduBodySize = 253;

    /// <summary>Per-pair buffer size: header (7) + max PDU body (253) = 260 bytes.</summary>
    public const int BufferSize = HeaderSize + MaxPduBodySize;

    /// <summary>
    /// Parses all fields from a 7-byte MBAP header buffer.
    /// Returns <c>false</c> when <paramref name="buffer"/> is shorter than 7 bytes.
    /// Does NOT validate <paramref name="protocolId"/> or <paramref name="length"/> —
    /// that is the caller's responsibility (and ultimately the PLC's job).
    /// </summary>
    public static bool TryParseHeader(
        ReadOnlySpan<byte> buffer,
        out ushort txId,
        out ushort protocolId,
        out ushort length,
        out byte unitId)
    {
        if (buffer.Length < HeaderSize)
        {
            txId = protocolId = length = 0;
            unitId = 0;
            return false;
        }

        txId       = (ushort)((buffer[0] << 8) | buffer[1]);
        protocolId = (ushort)((buffer[2] << 8) | buffer[3]);
        length     = (ushort)((buffer[4] << 8) | buffer[5]);
        unitId     = buffer[6];
        return true;
    }

    /// <summary>
    /// Returns the total frame length in bytes given the MBAP length field.
    /// Formula: 6 (TxId + ProtocolId + LengthField bytes) + lengthField
    ///        = 7 (full header) + (lengthField - 1) (PDU body without UnitId).
    /// </summary>
    public static int TotalFrameLength(ushort lengthField) => 6 + lengthField;
}
