using System.Runtime.InteropServices;
using RioJoy.Core.Calibration;
using RioJoy.Core.Mapping;
using RioJoy.Core.Output;

// On-cabinet smoke test: drive the real HidFeederJoystickSink (which opens the
// RioGamepad driver and submits reports via IOCTL_RIO_SUBMIT_REPORT) and read
// the virtual gamepad back through winmm joyGetPosEx, confirming fed values
// actually surface to the OS. Run after installing the driver:
//     dotnet run --project tools/RioJoySmokeTest -c Release
// Exit code: 0 = all passed, 1 = a check failed, 2 = driver/joystick not found.

const uint JOY_RETURNALL = 0x000000FF;
int failures = 0;
string lastName = "";
void Check(string what, bool ok)
{
    Console.WriteLine($"  [{(ok ? "PASS" : "FAIL")}] {what}");
    if (!ok) failures++;
}

Console.WriteLine("== RioJoy feeder -> driver smoke test ==");

if (!HidFeederJoystickSink.TryCreate(out var sink) || sink is null)
{
    Console.WriteLine("  [FAIL] HidFeederJoystickSink.TryCreate returned false (driver not present?).");
    return 2;
}
using (sink)
{
    Console.WriteLine("  [PASS] Opened RioGamepad device interface.");

    int joyId = FindJoyId();
    if (joyId < 0)
    {
        Console.WriteLine("  [FAIL] No winmm joystick found to read back from.");
        return 2;
    }
    Console.WriteLine($"  Reading back via winmm joystick id {joyId} (\"{lastName}\").");

    // 1. X axis: min / mid / max  (RioHidReport logical max = 32767 -> joyGetPosEx ~0..65535)
    sink.SetAxis(JoyAxis.X, 0);            var lo  = Read(joyId);
    sink.SetAxis(JoyAxis.X, 16384);        var mid = Read(joyId);
    sink.SetAxis(JoyAxis.X, 32767);        var hi  = Read(joyId);
    Console.WriteLine($"  X(min)={lo.X}  X(mid)={mid.X}  X(max)={hi.X}");
    Check("X axis tracks min<mid<max", lo.X < mid.X && mid.X < hi.X);
    Check("X(min) near 0",      lo.X < 4000);
    Check("X(max) near 65535",  hi.X > 61000);

    // reset X to centre so it doesn't confuse later reads
    sink.SetAxis(JoyAxis.X, 16384);

    // 2. Z axis independent move
    sink.SetAxis(JoyAxis.Z, 32767);        var z = Read(joyId);
    Console.WriteLine($"  Z(max)={z.Z}");
    Check("Z axis reaches max", z.Z > 61000);
    sink.SetAxis(JoyAxis.Z, 16384);

    // 3. Buttons: 1 and 5
    sink.SetButton(1, true);               var b1 = Read(joyId);
    Check("button 1 down -> bit0 set", (b1.Buttons & 0x1) != 0);
    sink.SetButton(5, true);               var b5 = Read(joyId);
    Check("button 5 down -> bit4 set", (b5.Buttons & 0x10) != 0);
    sink.SetButton(1, false);
    sink.SetButton(5, false);              var b0 = Read(joyId);
    Check("buttons released -> none set (low 8)", (b0.Buttons & 0xFF) == 0);

    // 4. Hat / POV
    sink.SetHat(RioHat.Up);                var hUp = Read(joyId);
    Check("hat Up -> POV 0", hUp.Pov == 0);
    sink.SetHat(RioHat.Right);             var hR  = Read(joyId);
    Check("hat Right -> POV 9000", hR.Pov == 9000);
    sink.SetHat(RioHat.Centered);          var hC  = Read(joyId);
    Check("hat Centered -> POV centered (0xFFFF)", hC.Pov == 0xFFFF);
}

Console.WriteLine(failures == 0
    ? "== ALL CHECKS PASSED =="
    : $"== {failures} CHECK(S) FAILED ==");
return failures == 0 ? 0 : 1;


// ---- winmm read-back helpers ------------------------------------------------

static (uint X, uint Y, uint Z, uint Buttons, uint Pov) Read(int id)
{
    Thread.Sleep(120); // let the report propagate through the HID stack
    var info = new JOYINFOEX { dwSize = (uint)Marshal.SizeOf<JOYINFOEX>(), dwFlags = JOY_RETURNALL };
    uint rc = joyGetPosEx(id, ref info);
    if (rc != 0) throw new InvalidOperationException($"joyGetPosEx failed: {rc}");
    return (info.dwXpos, info.dwYpos, info.dwZpos, info.dwButtons, info.dwPOV);
}

int FindJoyId()
{
    int n = (int)joyGetNumDevs();
    int firstPresent = -1;
    for (int id = 0; id < n; id++)
    {
        var caps = new JOYCAPS();
        if (joyGetDevCapsW(id, ref caps, (uint)Marshal.SizeOf<JOYCAPS>()) != 0) continue;
        var info = new JOYINFOEX { dwSize = (uint)Marshal.SizeOf<JOYINFOEX>(), dwFlags = JOY_RETURNALL };
        if (joyGetPosEx(id, ref info) != 0) continue; // not present
        string name = caps.szPname ?? "";
        if (firstPresent < 0) { firstPresent = id; lastName = name; }
        // RioGamepad identity (Public.h: RIO_VENDOR_ID 0x1209 / RIO_PRODUCT_ID 0x5249).
        if (caps.wMid == 0x1209 && caps.wPid == 0x5249) { lastName = name; return id; }
        if (name.IndexOf("RIOJoy", StringComparison.OrdinalIgnoreCase) >= 0) { lastName = name; return id; }
    }
    return firstPresent;
}

[DllImport("winmm.dll")] static extern uint joyGetNumDevs();
[DllImport("winmm.dll")] static extern uint joyGetPosEx(int uJoyID, ref JOYINFOEX pji);
[DllImport("winmm.dll", CharSet = CharSet.Unicode)] static extern uint joyGetDevCapsW(int uJoyID, ref JOYCAPS pjc, uint cbjc);

[StructLayout(LayoutKind.Sequential)]
struct JOYINFOEX
{
    public uint dwSize, dwFlags, dwXpos, dwYpos, dwZpos, dwRpos, dwUpos, dwVpos,
                dwButtons, dwButtonNumber, dwPOV, dwReserved1, dwReserved2;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct JOYCAPS
{
    public ushort wMid, wPid;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string szPname;
    public uint wXmin, wXmax, wYmin, wYmax, wZmin, wZmax, wNumButtons, wPeriodMin, wPeriodMax,
                wRmin, wRmax, wUmin, wUmax, wVmin, wVmax, wCaps, wMaxAxes, wNumAxes, wMaxButtons;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string szRegKey;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szOEMVxD;
}
