using VRio.Core.Device;

namespace VRio.Core.Panel;

/// <summary>Shape of a panel group.</summary>
public enum PanelGroupKind
{
    /// <summary>A 4×2 MFD button cluster (lamp buttons).</summary>
    Mfd,

    /// <summary>A vertical 1×8 board column (lamp buttons).</summary>
    Column,

    /// <summary>A 4×4 keypad (no lamps).</summary>
    Keypad,
}

/// <summary>
/// One functional group on the cockpit control panel: a titled block of address
/// buttons laid out row-major in <see cref="Rows"/> × <see cref="Cols"/>.
/// <see cref="OriginCol"/>/<see cref="OriginRow"/> place the block on the panel
/// in button-cell units.
/// </summary>
public sealed record PanelGroup(
    string Title,
    PanelGroupKind Kind,
    int Cols,
    int Rows,
    bool LampCapable,
    int OriginCol,
    int OriginRow,
    IReadOnlyList<int?> Addresses);

/// <summary>One address button placed on the panel (absolute cell coordinates).</summary>
public sealed record PanelButton(int Address, PanelGroup Group, int Col, int Row, bool LampCapable);

/// <summary>
/// The cockpit control panel's logical layout — the same functional grouping
/// RIOJoy's profile editor draws (its <c>CockpitPanel</c>, derived from the
/// original Win32 RIO control-panel mockup): five MFD clusters, four board
/// columns, and the two later-added 4×4 keypads. Every RIO address
/// 0x00–0x47 / 0x50–0x6F is placed exactly once. Keeping the byte-for-byte same
/// layout means a button on vRIO sits where the same button sits in RIOJoy's
/// editor — pressing one lights the other.
/// </summary>
public static class CockpitLayout
{
    // Helper: 4×2 MFD address block, top row then bottom row (addresses descend).
    private static int?[] Mfd(int hi) => new int?[]
    {
        hi, hi - 1, hi - 2, hi - 3,
        hi - 4, hi - 5, hi - 6, hi - 7,
    };

    // Helper: vertical 1×8 column from a base address upward.
    private static int?[] Col8(int baseAddr) => new int?[]
    {
        baseAddr, baseAddr + 1, baseAddr + 2, baseAddr + 3,
        baseAddr + 4, baseAddr + 5, baseAddr + 6, baseAddr + 7,
    };

    // Helper: 4×4 keypad. Physical keys 1 2 3 C / 4 5 6 D / 7 8 9 E / A 0 B F map
    // to address = base + that hex digit.
    private static int?[] Keypad(int baseAddr)
    {
        int[] digits = { 0x1, 0x2, 0x3, 0xC, 0x4, 0x5, 0x6, 0xD, 0x7, 0x8, 0x9, 0xE, 0xA, 0x0, 0xB, 0xF };
        var a = new int?[16];
        for (int i = 0; i < 16; i++) a[i] = baseAddr + digits[i];
        return a;
    }

    /// <summary>All panel groups, positioned for rendering.</summary>
    public static IReadOnlyList<PanelGroup> Groups { get; } = new[]
    {
        // Upper MFD row.
        new PanelGroup("Upper Left MFD", PanelGroupKind.Mfd, 4, 2, true, 0, 1, Mfd(0x2F)),
        new PanelGroup("Upper Middle MFD", PanelGroupKind.Mfd, 4, 2, true, 4, 1, Mfd(0x27)),
        new PanelGroup("Upper Right MFD", PanelGroupKind.Mfd, 4, 2, true, 8, 1, Mfd(0x37)),

        // Lower MFD row.
        new PanelGroup("Lower Left MFD", PanelGroupKind.Mfd, 4, 2, true, 0, 5, Mfd(0x0F)),
        new PanelGroup("Lower Right MFD", PanelGroupKind.Mfd, 4, 2, true, 8, 5, Mfd(0x07)),

        // Board columns.
        new PanelGroup("Throttle", PanelGroupKind.Column, 1, 8, true, 0, 9, Col8(0x38)),
        new PanelGroup("Secondary", PanelGroupKind.Column, 1, 8, true, 4, 9, Col8(0x10)),
        new PanelGroup("Screen", PanelGroupKind.Column, 1, 8, true, 6, 9, Col8(0x18)),
        new PanelGroup("Joystick", PanelGroupKind.Column, 1, 8, true, 11, 9, Col8(0x40)),

        // Keypads (no lamps): internal in the middle, external between the
        // Secondary/Screen columns and the Joystick column.
        new PanelGroup("Internal Keypad", PanelGroupKind.Keypad, 4, 4, false, 4, 4, Keypad(0x50)),
        new PanelGroup("External Keypad", PanelGroupKind.Keypad, 4, 4, false, 7, 9, Keypad(0x60)),
    };

    /// <summary>Flatten the groups to positioned <see cref="PanelButton"/>s (absolute cells).</summary>
    public static IReadOnlyList<PanelButton> Buttons()
    {
        var buttons = new List<PanelButton>();
        foreach (PanelGroup g in Groups)
        {
            for (int i = 0; i < g.Addresses.Count; i++)
            {
                if (g.Addresses[i] is not { } addr) continue;
                int localCol = i % g.Cols;
                int localRow = i / g.Cols;
                // +1 row leaves space under the title.
                buttons.Add(new PanelButton(addr, g, g.OriginCol + localCol, g.OriginRow + 1 + localRow, g.LampCapable));
            }
        }
        return buttons;
    }

    // Named physical controls (from the Win32RIO control-panel mockup); every
    // other lamp button is known only by its hex address.
    private static readonly Dictionary<int, string> Named = new()
    {
        [0x3D] = "Panic",
        [0x3F] = "Throttle",
        [0x40] = "Main",
        [0x41] = "Hat Back",
        [0x42] = "Hat Up",
        [0x43] = "Hat Right",
        [0x44] = "Hat Left",
        [0x45] = "Pinky",
        [0x46] = "Middle",
        [0x47] = "Upper",
    };

    /// <summary>
    /// The label printed on the physical control: keypad keys carry their hex
    /// digit, a few controls have names, the rest is null (show the address).
    /// </summary>
    public static string? PhysicalLabel(int address)
    {
        if (RioAddressSpace.IsKeypad(address))
            return (address & 0x0F).ToString("X1");
        return Named.TryGetValue(address, out string? name) ? name : null;
    }
}
