using RioJoy.Core.Calibration;
using RioJoy.Core.Hid;
using RioJoy.Core.Mapping;
using Xunit;

namespace RioJoy.Core.Tests.Hid;

public class RioHidReportTests
{
    [Fact]
    public void DefaultReport_CentersAxesAndHat()
    {
        var report = new RioHidReport();
        byte[] b = report.ToArray();

        Assert.Equal(RioHidReport.Size, b.Length);
        // Each axis = 16383 = 0x3FFF, little-endian.
        for (int offset = 0; offset < 12; offset += 2)
        {
            Assert.Equal(0xFF, b[offset]);
            Assert.Equal(0x3F, b[offset + 1]);
        }

        Assert.Equal(0x0F, b[12] & 0x0F); // hat centered/null
    }

    [Fact]
    public void SetAxis_WritesLittleEndian_AndClamps()
    {
        var report = new RioHidReport();
        report.SetAxis(JoyAxis.Z, 0x1234);
        Assert.Equal(0x34, report.ToArray()[4]);
        Assert.Equal(0x12, report.ToArray()[5]);

        report.SetAxis(JoyAxis.Z, 70000); // over max → clamped to 32767
        Assert.Equal(0xFF, report.ToArray()[4]);
        Assert.Equal(0x7F, report.ToArray()[5]);
    }

    [Theory]
    [InlineData(RioHat.Up, 0x00)]
    [InlineData(RioHat.Right, 0x01)]
    [InlineData(RioHat.Down, 0x02)]
    [InlineData(RioHat.Left, 0x03)]
    [InlineData(RioHat.Centered, 0x0F)]
    public void SetHat_EncodesNibble(RioHat hat, int expected)
    {
        var report = new RioHidReport();
        report.SetHat(hat);
        Assert.Equal(expected, report.ToArray()[12] & 0x0F);
    }

    [Theory]
    [InlineData(1, 13, 0x01)]   // button 1 → byte 13 bit 0
    [InlineData(8, 13, 0x80)]   // button 8 → byte 13 bit 7
    [InlineData(9, 14, 0x01)]   // button 9 → byte 14 bit 0
    [InlineData(96, 24, 0x80)]  // button 96 → byte 24 bit 7
    public void SetButton_SetsCorrectBit(int button, int byteIndex, int mask)
    {
        var report = new RioHidReport();
        report.SetButton(button, pressed: true);
        Assert.Equal(mask, report.ToArray()[byteIndex]);

        report.SetButton(button, pressed: false);
        Assert.Equal(0, report.ToArray()[byteIndex]);
    }

    [Fact]
    public void SetButton_RejectsOutOfRange()
    {
        var report = new RioHidReport();
        Assert.Throws<ArgumentOutOfRangeException>(() => report.SetButton(0, true));
        Assert.Throws<ArgumentOutOfRangeException>(() => report.SetButton(97, true));
    }

    [Fact]
    public void Buttons_ComposeIndependently()
    {
        var report = new RioHidReport();
        report.SetButton(1, true);
        report.SetButton(2, true);
        report.SetButton(1, false);
        Assert.Equal(0x02, report.ToArray()[13]); // only button 2 remains
    }
}
