using VRio.Core.Protocol;
using Xunit;

namespace VRio.Core.Tests;

public class ProtocolTests
{
    [Fact]
    public void Checksum_is_low7_of_sum_of_low7()
    {
        // 0x88 & 0x7F = 0x08, + 0x05 = 0x0D.
        Assert.Equal(0x0D, RioChecksum.Compute(new byte[] { 0x88, 0x05 }));
    }

    [Theory]
    [InlineData(0)]
    [InlineData(1)]
    [InlineData(-1)]
    [InlineData(8191)]
    [InlineData(-8192)]
    [InlineData(1234)]
    [InlineData(-4321)]
    public void AnalogCodec_round_trips_and_stays_7bit(short value)
    {
        AnalogCodec.Split(value, out byte low, out byte high);

        Assert.Equal(0, low & 0x80);  // payload bytes must keep the high bit clear
        Assert.Equal(0, high & 0x80);
        Assert.Equal(value, AnalogCodec.Combine(low, high));
    }

    [Fact]
    public void Builder_output_parses_back_with_valid_checksum()
    {
        byte[] wire = PacketBuilder.AnalogReply(100, -200, 300, -8192, 8191);

        var parser = new PacketParser();
        RioRxEvent? got = null;
        foreach (byte b in wire)
            if (parser.Feed(b, out RioRxEvent ev))
                got = ev;

        Assert.NotNull(got);
        Assert.Equal(RioRxKind.Packet, got!.Value.Kind);
        Assert.True(got.Value.ChecksumValid);
        Assert.Equal(RioCommand.AnalogReply, got.Value.Packet.Command);

        byte[] p = got.Value.Packet.Payload;
        Assert.Equal(100, AnalogCodec.Combine(p[0], p[1]));
        Assert.Equal(-200, AnalogCodec.Combine(p[2], p[3]));
        Assert.Equal(300, AnalogCodec.Combine(p[4], p[5]));
        Assert.Equal(-8192, AnalogCodec.Combine(p[6], p[7]));
        Assert.Equal(8191, AnalogCodec.Combine(p[8], p[9]));
    }

    [Fact]
    public void Parser_reports_control_bytes_outside_framing()
    {
        var parser = new PacketParser();
        Assert.True(parser.Feed((byte)RioControl.Ack, out RioRxEvent ev));
        Assert.Equal(RioRxKind.ControlByte, ev.Kind);
        Assert.Equal((byte)RioControl.Ack, ev.Byte);
    }

    [Fact]
    public void Parser_resyncs_on_high_bit_byte_mid_packet()
    {
        var parser = new PacketParser();

        // Start a LampRequest (2-byte payload), then interrupt with a new command byte.
        Assert.False(parser.Feed(0x84, out _));
        Assert.True(parser.Feed(0xFF, out RioRxEvent err)); // IDLE mid-packet = framing error
        Assert.Equal(RioRxKind.FramingError, err.Kind);
        Assert.False(parser.InPacket);

        // A clean packet right after must still parse.
        byte[] wire = PacketBuilder.VersionReply(1, 2);
        RioRxEvent? got = null;
        foreach (byte b in wire)
            if (parser.Feed(b, out RioRxEvent ev))
                got = ev;
        Assert.Equal(RioRxKind.Packet, got!.Value.Kind);
        Assert.True(got.Value.ChecksumValid);
    }

    [Theory]
    [InlineData(0x00, LampBrightness.Off, LampFlash.Solid)]
    [InlineData(0x14, LampBrightness.Dim, LampFlash.Solid)]
    [InlineData(0x3C, LampBrightness.Bright, LampFlash.Solid)]
    [InlineData(0x3D, LampBrightness.Bright, LampFlash.FlashSlow)]
    [InlineData(0x16, LampBrightness.Dim, LampFlash.FlashMed)]
    [InlineData(0x04, LampBrightness.Dim, LampFlash.Solid)]   // field 1 only
    [InlineData(0x30, LampBrightness.Bright, LampFlash.Solid)] // field 2 only
    public void LampState_decodes_brightness_and_flash(byte state, LampBrightness brightness, LampFlash flash)
    {
        Assert.Equal(brightness, RioLampState.Brightness(state));
        Assert.Equal(flash, RioLampState.Flash(state));
    }
}
