namespace VRio.Core.Device;

/// <summary>
/// The RIO's digital address space, as RIOJoy's <c>iRIO</c> map sees it:
/// 72 lamp-capable buttons at 0x00–0x47 (reported by index in
/// ButtonPressed/Released), keypad 0 at 0x50–0x5F and keypad 1 at 0x60–0x6F
/// (reported as KeyPressed/Released with a pad + key index; the PC adds the
/// 0x50/0x60 offset). vRIO works in addresses and converts to wire form when
/// sending.
/// </summary>
public static class RioAddressSpace
{
    /// <summary>Number of digital button inputs / lamps (addresses 0x00–0x47).</summary>
    public const int LampCount = 72;

    /// <summary>Address offset of keypad 0 (pad byte 0 on the wire).</summary>
    public const int Keypad0Base = 0x50;

    /// <summary>Address offset of keypad 1 (pad byte 1 on the wire).</summary>
    public const int Keypad1Base = 0x60;

    /// <summary>Highest valid address (keypad 1, key 0x0F).</summary>
    public const int MaxAddress = Keypad1Base + 0x0F; // 0x6F

    /// <summary>True for a lamp-capable button address (0x00–0x47).</summary>
    public static bool IsButton(int address) => address is >= 0 and < LampCount;

    /// <summary>True for a keypad address (0x50–0x5F or 0x60–0x6F).</summary>
    public static bool IsKeypad(int address) =>
        address is >= Keypad0Base and <= Keypad0Base + 0x0F
                or >= Keypad1Base and <= MaxAddress;

    /// <summary>
    /// Convert a keypad address to its wire (pad, index) pair — the inverse of
    /// the PC's +0x50/+0x60 offsetting.
    /// </summary>
    public static (byte Pad, byte Index) ToKeypad(int address)
    {
        if (!IsKeypad(address))
            throw new ArgumentOutOfRangeException(nameof(address), $"0x{address:X2} is not a keypad address.");

        return address >= Keypad1Base
            ? ((byte)1, (byte)(address - Keypad1Base))
            : ((byte)0, (byte)(address - Keypad0Base));
    }

    /// <summary>
    /// The I/O boards a healthy cockpit reports, as (board number, name) —
    /// numbering from the legacy firmware's board table (riovjoy2.cpp
    /// <c>GetBoardName</c>). vRIO answers CheckRequest with one BoardOk
    /// CheckReply per entry.
    /// </summary>
    public static readonly IReadOnlyList<(byte Number, string Name)> Boards = new (byte, string)[]
    {
        (0x00, "AuxLowerRight"),
        (0x08, "AuxLowerLeft"),
        (0x10, "Secondary1"),
        (0x11, "Secondary2"),
        (0x18, "AuxUpperCenter"),
        (0x19, "AuxUpperLeft"),
        (0x1A, "AuxUpperRight"),
        (0x20, "IntKeyPad"),
        (0x28, "ExtKeyPad"),
        (0x30, "Throttle"),
        (0x38, "JoyStick"),
    };
}

/// <summary>
/// The five analog axes, indexed in AnalogReply payload order
/// (riojoy/docs/PROTOCOL.md §4).
/// </summary>
public enum RioAxis
{
    Throttle = 0,
    LeftPedal = 1,
    RightPedal = 2,
    JoystickY = 3,
    JoystickX = 4,
}

/// <summary>
/// Realistic travel of each axis on the physical cockpit, measured from the
/// rest position (raw 0). The wire format can carry 14-bit signed values, but
/// the hardware only uses a small slice of it, and each control has a
/// direction — from RIOJoy's calibrator (RioJoy.Core <c>AxisCalibrator</c>,
/// ported from riovjoy2.cpp):
/// <list type="bullet">
///   <item>The throttle lever's startup position is unknown, so RIOJoy
///   ratchets its start from the highest sample seen and measures forward
///   travel as start − sample, up to 800 counts — forward travel runs
///   <em>negative</em> on the wire.</item>
///   <item>The pedals are spring-loaded with a predictable rest; RIOJoy takes
///   the lowest sample as rest and a press raises the value, up to 500.</item>
///   <item>The joystick is self-centering; RIOJoy auto-ranges from observed
///   min/max, seeded at ±80 either side of center.</item>
/// </list>
/// Values a vRIO front-end produces should stay inside these windows to look
/// like real hardware.
/// </summary>
public static class RioAxisRange
{
    /// <summary>Throttle raw value at full forward travel (rest 0; travel runs negative).</summary>
    public const int ThrottleFull = -800;

    /// <summary>Pedal raw value at a full press (rest 0; spring-loaded).</summary>
    public const int PedalFull = 500;

    /// <summary>Joystick full deflection either side of its 0 center.</summary>
    public const int JoystickExtent = 80;

    /// <summary>Raw value at full travel from rest — the sign is the wire direction.</summary>
    public static int Full(RioAxis axis) => axis switch
    {
        RioAxis.Throttle => ThrottleFull,
        RioAxis.LeftPedal or RioAxis.RightPedal => PedalFull,
        _ => JoystickExtent,
    };
}
