namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus;

/// <summary>
///     CRC-16/MODBUS helper: reflected polynomial <c>0xA001</c>, initial value <c>0xFFFF</c>.
///     On the wire the CRC is appended low byte first, high byte second — <see cref="Append"/>
///     does that; <see cref="Compute"/> just returns the 16-bit value. Byte-stream-agnostic:
///     no socket or framing knowledge lives here.
/// </summary>
public static class ModbusCrc
{
    /// <summary>Computes the CRC-16/MODBUS checksum over <paramref name="data"/>.</summary>
    /// <param name="data">The bytes to checksum (e.g. the RTU frame minus the trailing CRC).</param>
    /// <returns>The 16-bit CRC value.</returns>
    public static ushort Compute(ReadOnlySpan<byte> data)
    {
        ushort crc = 0xFFFF;
        foreach (var b in data)
        {
            crc ^= b;
            for (var i = 0; i < 8; i++)
            {
                var carry = (crc & 0x0001) != 0;
                crc >>= 1;
                if (carry)
                {
                    crc ^= 0xA001;
                }
            }
        }

        return crc;
    }

    /// <summary>
    ///     Returns <paramref name="body"/> with its CRC-16/MODBUS appended, low byte first.
    /// </summary>
    /// <param name="body">The frame body to checksum.</param>
    /// <returns>A new array: <paramref name="body"/> followed by <c>[crc &amp; 0xFF, crc &gt;&gt; 8]</c>.</returns>
    public static byte[] Append(ReadOnlySpan<byte> body)
    {
        var crc = Compute(body);
        var result = new byte[body.Length + 2];
        body.CopyTo(result);
        result[^2] = (byte)(crc & 0xFF);
        result[^1] = (byte)(crc >> 8);
        return result;
    }
}
