using System.Text;

namespace DbaMessageInserter;

public sealed record DbaMessagePayload(
    byte[] CarrierBytes,
    byte[] StoredMessageBytes,
    int OriginalUtf8ByteCount,
    int CapacityBits,
    bool WasTruncated,
    bool TerminatorIncluded)
{
    public int CarrierBits => checked(CarrierBytes.Length * 8);
    public int StoredUtf8ByteCount => StoredMessageBytes.Length;
}

/// <summary>
/// Converts a text message to UTF-8 bytes for the connected NPNCB DBA bit
/// regions. Bytes are supplied LSB-first, and a zero-byte terminator is added
/// when a complete byte remains. U+0000 is reserved for that terminator.
/// </summary>
public static class DbaMessageEncoder
{
    private static readonly UTF8Encoding StrictUtf8 = new(false, true);

    public static DbaMessagePayload Prepare(string message, int capacityBits)
    {
        if (message is null) throw new ArgumentNullException(nameof(message));
        if (capacityBits < 0) throw new ArgumentOutOfRangeException(nameof(capacityBits));
        if (message.IndexOf('\0') >= 0)
            throw new ArgumentException("U+0000 is reserved as the DBA terminator.", nameof(message));

        byte[] originalBytes = StrictUtf8.GetBytes(message);
        int completeByteCapacity = capacityBits / 8;
        int prefixLength = GetLargestValidUtf8PrefixLength(originalBytes, completeByteCapacity);
        byte[] storedMessageBytes = originalBytes[..prefixLength];
        bool wasTruncated = prefixLength < originalBytes.Length;
        bool terminatorIncluded = prefixLength < completeByteCapacity;
        int carrierLength = checked(prefixLength + (terminatorIncluded ? 1 : 0));
        var carrierBytes = new byte[carrierLength];
        Array.Copy(storedMessageBytes, carrierBytes, storedMessageBytes.Length);

        return new DbaMessagePayload(
            carrierBytes,
            storedMessageBytes,
            originalBytes.Length,
            capacityBits,
            wasTruncated,
            terminatorIncluded);
    }

    private static int GetLargestValidUtf8PrefixLength(byte[] bytes, int maximumLength)
    {
        int length = Math.Min(bytes.Length, Math.Max(0, maximumLength));
        while (length > 0)
        {
            try
            {
                _ = StrictUtf8.GetCharCount(bytes, 0, length);
                return length;
            }
            catch (DecoderFallbackException)
            {
                length--;
            }
        }
        return 0;
    }
}
