namespace RioJoy.Core.Mapping;

/// <summary>How a decoded <see cref="RioMapEntry"/> should be routed.</summary>
public enum RioRouteKind
{
    /// <summary>Send a keyboard key (with optional SHIFT/CTRL/ALT modifiers).</summary>
    Keyboard,

    /// <summary>Press a virtual joystick button.</summary>
    Joystick,

    /// <summary>Set the virtual POV hat direction.</summary>
    Hat,

    /// <summary>Perform a mouse action (move or click).</summary>
    Mouse,

    /// <summary>
    /// An internal RIO command (axis reset, recalibrate, status, toggle) — not an
    /// output to the host. Encoded as joy+hat+mouse all set (<c>0x7000</c>).
    /// </summary>
    RioCommand,
}

/// <summary>
/// One entry of the <c>iRIO</c> map: a 16-bit word whose high byte is routing
/// flags and low byte is the payload (VK code / button# / hat direction / mouse
/// action). Faithful decode of <c>Press_V2</c>/<c>Release_V2</c>
/// (riovjoy2.cpp#L1944); see docs/PROTOCOL.md §5.
/// </summary>
public readonly struct RioMapEntry
{
    // High-byte routing flags.
    private const ushort FlagHasLamp = 0x8000;
    private const ushort FlagMouse = 0x4000;
    private const ushort FlagHat = 0x2000;
    private const ushort FlagJoy = 0x1000;
    private const ushort FlagExtended = 0x0800;
    private const ushort FlagAlt = 0x0400;
    private const ushort FlagCtrl = 0x0200;
    private const ushort FlagShift = 0x0100;

    private const ushort RioCommandMask = FlagJoy | FlagHat | FlagMouse; // 0x7000

    /// <summary>The raw 16-bit map word.</summary>
    public ushort Raw { get; }

    public RioMapEntry(ushort raw) => Raw = raw;

    /// <summary>
    /// Build a map word from decoded components (the inverse of the decode
    /// properties) — used by the profile editor to write the <c>iRIO</c> table.
    /// <paramref name="kind"/> sets the routing flags; modifiers/lamp/extended set
    /// their high-byte bits; <paramref name="value"/> is the low byte.
    /// </summary>
    public static RioMapEntry Create(
        RioRouteKind kind,
        byte value,
        bool lit = false,
        bool shift = false,
        bool ctrl = false,
        bool alt = false,
        bool extended = false)
    {
        ushort raw = value;

        raw |= kind switch
        {
            RioRouteKind.Keyboard => 0,
            RioRouteKind.Joystick => FlagJoy,
            RioRouteKind.Hat => FlagHat,
            RioRouteKind.Mouse => FlagMouse,
            RioRouteKind.RioCommand => RioCommandMask,
            _ => 0,
        };

        if (lit) raw |= FlagHasLamp;
        if (extended) raw |= FlagExtended;
        if (alt) raw |= FlagAlt;
        if (ctrl) raw |= FlagCtrl;
        if (shift) raw |= FlagShift;

        return new RioMapEntry(raw);
    }

    /// <summary>This input drives a lighted button (lamp feedback on press/release).</summary>
    public bool HasLamp => (Raw & FlagHasLamp) != 0;

    /// <summary>Keyboard: apply <c>KEYEVENTF_EXTENDEDKEY</c> to the key.</summary>
    public bool Extended => (Raw & FlagExtended) != 0;

    public bool Alt => (Raw & FlagAlt) != 0;
    public bool Ctrl => (Raw & FlagCtrl) != 0;
    public bool Shift => (Raw & FlagShift) != 0;

    /// <summary>Low byte: VK code, joystick button, hat direction, or mouse action.</summary>
    public byte Value => (byte)(Raw & 0xFF);

    private bool IsJoyFlag => (Raw & FlagJoy) != 0;
    private bool IsHatFlag => (Raw & FlagHat) != 0;
    private bool IsMouseFlag => (Raw & FlagMouse) != 0;

    /// <summary>
    /// The routing destination, applying the legacy precedence: all three of
    /// joy/hat/mouse set ⇒ RIO command; none set ⇒ keyboard; otherwise joy, then
    /// hat, then mouse.
    /// </summary>
    public RioRouteKind Kind
    {
        get
        {
            if ((Raw & RioCommandMask) == RioCommandMask)
                return RioRouteKind.RioCommand;
            if (!IsJoyFlag && !IsHatFlag && !IsMouseFlag)
                return RioRouteKind.Keyboard;
            if (IsJoyFlag)
                return RioRouteKind.Joystick;
            if (IsHatFlag)
                return RioRouteKind.Hat;
            return RioRouteKind.Mouse;
        }
    }

    /// <summary>True if this entry has no mapping (a "do nothing" slot).</summary>
    public bool IsUnmapped => Raw == 0;

    public override string ToString() => $"0x{Raw:X4} ({Kind} {Value})";
}
