//! One-shot generator for the CHR tiles and nametable used by
//! `examples/platformer.ne`.
//!
//! Run with `cargo run --bin gen_platformer_tiles`. The output is
//! intended to be pasted into `platformer.ne` under the
//! `sprite Tileset { pixels: [...] }` and
//! `background Level { legend { ... } map: [...] palette_map: [...] }`
//! blocks. Keeping the source of truth here (instead of
//! hand-maintained ASCII in the `.ne` file) ensures the tile art,
//! the nametable, and the named tile indices all stay in sync.
//!
//! Tiles are defined as 8×8 ASCII art where each character selects
//! one of 4 sub-palette slots:
//!     '.' = colour 0  (sky / transparent for sprites)
//!     'a' = colour 1
//!     'b' = colour 2
//!     'c' = colour 3
//!
//! Both the generator *and* the `NEScript` parser accept `.abc` as
//! aliases for `.#%@`, so the output is ready to paste directly
//! into a `pixels:` block without translation.

/// A single 8×8 tile description: name + ASCII art.
struct Tile {
    name: &'static str,
    art: &'static str,
}

/// All tiles referenced by the platformer example. Order matters —
/// tile 0 is the default smiley reserved by the linker, so entry 0
/// here lands at CHR index 1, entry 1 at index 2, and so on.
///
/// Colour conventions (must line up with the palette + attribute
/// layout in `examples/platformer.ne`):
///   sprite sub-palette 0 (player / enemy / coin):
///     a = red/cap, b = peach/skin, c = white/highlight
///   bg sub-palette 0 (sky row): a = white, b = light gray, c = black
///   bg sub-palette 1 (block row): a = red, b = peach, c = dark red
///   bg sub-palette 2 (ground row): a = light green, b = light brown,
///                                   c = dark brown
const TILES: &[Tile] = &[
    Tile {
        name: "Player head L",
        art: "\
..aaaaaa
.aaaaaaa
.aaabbbb
.aabbbbc
aabbcccc
aabbccbc
aabbbbbb
aabbbbbb",
    },
    Tile {
        name: "Player head R",
        art: "\
aaaaaa..
aaaaaaa.
bbbbaaa.
cbbbbbaa
cccccbbb
bcbbccbb
bbbbbbbb
bbbbbbbb",
    },
    Tile {
        name: "Player body L",
        art: "\
.aaaaaaa
aaacaaaa
aaaccaaa
aaaaaaaa
.bbbbbbb
.bbbbbbb
.bb..bbb
aaa..bbb",
    },
    Tile {
        name: "Player body R",
        art: "\
aaaaaaa.
aaaacaaa
aaaccaaa
aaaaaaaa
bbbbbbb.
bbbbbbb.
bbb..bb.
bbb..aaa",
    },
    Tile {
        name: "Enemy",
        art: "\
..aaaa..
.aaaaaa.
.aacbaa.
aacccaaa
abbccbba
.aaaaaa.
a.aaaa.a
a..aa..a",
    },
    Tile {
        name: "Coin",
        art: "\
...bb...
..bbbb..
.bbccbb.
.bcbbcb.
.bcbbcb.
.bbccbb.
..bbbb..
...bb...",
    },
    // Grass top (sub-palette 2: a=green, c=dark brown).
    Tile {
        name: "Grass top",
        art: "\
aaaaaaaa
a.aaa.aa
aaaaaaaa
accacaca
ccccaccc
cccccccc
cbccccbc
cccccccc",
    },
    // Dirt (sub-palette 2: c=dark brown bulk, b=light brown speckles).
    Tile {
        name: "Dirt",
        art: "\
cccccccc
cbccccbc
cccccccc
ccccbccc
cccccccc
cbccccbc
ccccbccc
cccccccc",
    },
    // Brick (sub-palette 1: a=red, c=dark red mortar).
    Tile {
        name: "Brick",
        art: "\
cccccccc
caaaaaca
caaaaaca
caaaaaca
cccccccc
aaacaaaa
aaacaaaa
aaacaaaa",
    },
    // Cloud left (sub-palette 0: a=white, b=light gray shade).
    Tile {
        name: "Cloud L",
        art: "\
........
...aaa..
..aaaaa.
.aaaaaaa
.aaaaaaa
.bbaaaaa
..bbbbba
.....bbb",
    },
    // Cloud right (sub-palette 0).
    Tile {
        name: "Cloud R",
        art: "\
........
.aaa....
aaaaa...
aaaaaaa.
aaaaaaa.
aaaaabb.
abbbbb..
bbb.....",
    },
    // Hill (sub-palette 2: a=green, b=light brown shade).
    Tile {
        name: "Hill",
        art: "\
........
....aa..
...aaaa.
..aaaaaa
..abaaaa
.abbabaa
.aaababa
aabbabba",
    },
    // Bush (sub-palette 2: a=green, c=dark brown outline).
    Tile {
        name: "Bush",
        art: "\
........
...aa...
..aaaa..
.aaaaac.
aaaaaaca
aaaaacca
aaaaacca
acacacac",
    },
    // Q Block (sub-palette 1: a=red frame, b=peach face, c=dark red).
    Tile {
        name: "Q Block",
        art: "\
cccccccc
caaaaaac
cabbbbac
cabccbac
cabcbbac
cabbbbac
caaaaaac
cccccccc",
    },
    // Sky (all transparent — renders as the universal bg colour).
    Tile {
        name: "Sky (blank)",
        art: "\
........
........
........
........
........
........
........
........",
    },
];

// ── Named CHR tile indices used by the nametable layout below ──
// (Player/enemy/coin tile indices are referenced by name only in
// the .ne file's `draw` statements, not by the nametable here, so
// the nametable-only constants live here.)
const GRASS: u8 = 7;
const DIRT: u8 = 8;
const BRICK: u8 = 9;
const CLOUD_L: u8 = 10;
const CLOUD_R: u8 = 11;
const HILL: u8 = 12;
const BUSH: u8 = 13;
const QBLOCK: u8 = 14;
const SKY: u8 = 15;

/// Validate that a tile's ASCII art is well-formed (8 rows × 8 cols,
/// only the legal `.abc` characters). Pixel→CHR encoding now happens
/// inside the `NEScript` parser when it sees the `pixels:` block, so
/// this generator's job is just to make sure the strings we paste
/// are syntactically valid.
fn validate_tile_art(name: &str, art: &str) {
    let rows: Vec<&str> = art.lines().filter(|l| !l.is_empty()).collect();
    assert_eq!(
        rows.len(),
        8,
        "tile '{name}': expected 8 rows, got {}",
        rows.len()
    );
    for (y, row) in rows.iter().enumerate() {
        assert_eq!(
            row.len(),
            8,
            "tile '{name}' row {y}: expected 8 cols, got {row:?}"
        );
        for ch in row.chars() {
            assert!(
                matches!(ch, '.' | 'a' | 'b' | 'c'),
                "tile '{name}' row {y}: invalid character '{ch}'"
            );
        }
    }
}

/// Build a 32×30 nametable for a static Mario-ish level vista.
/// Horizontal scrolling pans this single nametable via `scroll()`,
/// so it only needs to look interesting at any X offset.
fn build_nametable() -> [u8; 960] {
    let mut nt = [SKY; 960];
    let set = |nt: &mut [u8; 960], y: usize, x: usize, t: u8| {
        nt[y * 32 + x] = t;
    };

    // Row 5 & 6: two clouds at different X positions for parallax feel.
    for &cx in &[4usize, 20] {
        set(&mut nt, 5, cx, CLOUD_L);
        set(&mut nt, 5, cx + 1, CLOUD_R);
    }
    set(&mut nt, 6, 12, CLOUD_L);
    set(&mut nt, 6, 13, CLOUD_R);

    // Row 15: a suspended brick platform with a Q-block in the middle.
    for x in 6..=9 {
        let t = if x == 8 { QBLOCK } else { BRICK };
        set(&mut nt, 15, x, t);
    }
    for x in 18..=20 {
        set(&mut nt, 15, x, BRICK);
    }

    // Row 20: hills sitting behind the grass line.
    for x in 2..=4 {
        set(&mut nt, 20, x, HILL);
    }
    for x in 22..=25 {
        set(&mut nt, 20, x, HILL);
    }

    // Row 21: bushes on top of the grass.
    for x in 10..=12 {
        set(&mut nt, 21, x, BUSH);
    }
    for x in 27..=28 {
        set(&mut nt, 21, x, BUSH);
    }

    // Row 22: grass top — a full horizontal line.
    for x in 0..32 {
        set(&mut nt, 22, x, GRASS);
    }

    // Row 23-29: dirt rows with a few buried bricks for texture.
    for y in 23..30 {
        for x in 0..32 {
            set(&mut nt, y, x, DIRT);
        }
    }
    for &(y, x) in &[(24usize, 5usize), (25, 11), (24, 18), (25, 24), (23, 30)] {
        set(&mut nt, y, x, BRICK);
    }

    nt
}

/// Build a 64-byte attribute table that pairs every 2×2 metatile
/// with a background sub-palette.
///
/// The attribute table covers a 16×15 grid of 16×16 metatiles. Each
/// byte encodes 4 quadrants (top-left, top-right, bottom-left,
/// bottom-right) of a 32×32-pixel "super-metatile" with 2 bits
/// apiece (palette index 0..3). For this demo we simply pick a
/// sub-palette per screen row region:
/// - top region (sky): sub-palette 0
/// - mid region (hills/blocks): sub-palette 1
/// - ground region (grass/dirt): sub-palette 2
fn build_attributes() -> [u8; 64] {
    let mut attr = [0u8; 64];
    // The attribute table is 8×8 bytes. Row `ay` covers nametable
    // rows 4*ay..4*ay+3. Palette layout:
    //   rows 0-11 (ay 0-2): sub-palette 0 (sky/clouds: whites)
    //   rows 12-15 (ay 3):  sub-palette 1 (brick/Q-block row)
    //   rows 16-19 (ay 4):  sub-palette 0 (more sky above the hills)
    //   rows 20-29 (ay 5-7): sub-palette 2 (grass/hills/bushes/dirt)
    for ay in 0..8 {
        let pal: u8 = match ay {
            0..=2 => 0,
            3 => 1,
            4 => 0,
            _ => 2, // 5, 6, 7
        };
        let quad = pal & 0b11;
        let byte = quad | (quad << 2) | (quad << 4) | (quad << 6);
        for ax in 0..8 {
            attr[ay * 8 + ax] = byte;
        }
    }
    attr
}

fn main() {
    // ── CHR tiles as pixel-art strings ──────────────────────────
    //
    // `NEScript`'s `sprite Name { pixels: [...] }` accepts one string
    // per pixel row; the parser splits the grid into 8×8 tiles in
    // row-major reading order, so emitting each 8-row tile
    // sequentially (stacked vertically, 1 tile wide × 15 tiles tall)
    // produces CHR tile indices 1..15 in the same order as the
    // TILES array.
    println!("// ── CHR tiles (paste into sprite Tileset {{ pixels: [...] }}) ──");
    println!("    pixels: [");
    for (i, tile) in TILES.iter().enumerate() {
        validate_tile_art(tile.name, tile.art);
        let tile_idx = i + 1;
        println!("        // tile {tile_idx}: {}", tile.name);
        for row in tile.art.lines().filter(|l| !l.is_empty()) {
            println!("        \"{row}\",");
        }
    }
    println!("    ]\n");

    // ── Background tilemap via legend + map form ────────────────
    //
    // Each distinct nametable tile gets one easy-to-read legend
    // character. `.` is the most common (sky), so use it for the
    // default fill to keep the `map:` strings tidy. The compiler
    // validates every character — any typo in the rows below is
    // caught by the parser rather than by a render bug at runtime.
    println!("// ── Nametable (paste into background Level {{ legend/map/palette_map }}) ──");
    println!("    legend {{");
    println!("        \".\": 15   // sky (blank)");
    println!("        \"<\": 10   // cloud left half");
    println!("        \">\": 11   // cloud right half");
    println!("        \"#\": 9    // brick");
    println!("        \"Q\": 14   // question block");
    println!("        \"^\": 12   // hill silhouette");
    println!("        \"*\": 13   // bush");
    println!("        \"=\": 7    // grass top");
    println!("        \"%\": 8    // dirt");
    println!("    }}");
    println!();
    println!("    map: [");
    let nt = build_nametable();
    assert_eq!(nt.len(), 960);
    let legend = legend_char_for;
    for row in 0..30 {
        let mut s = String::from("        \"");
        for col in 0..32 {
            s.push(legend(nt[row * 32 + col]));
        }
        s.push_str("\",");
        println!("{s}");
    }
    println!("    ]\n");

    // ── Palette map (auto-packs the 64-byte attribute table) ────
    //
    // `palette_map:` is a 16-wide grid of sub-palette digits (one
    // per 16×16 metatile). The parser packs pairs of rows into the
    // 8×8 attribute table automatically, so we emit the metatile
    // grid directly and skip the awkward `(br<<6)|(bl<<4)|(tr<<2)|tl`
    // bit-packing the old raw-bytes form demanded.
    //
    // The attribute table covers 16 metatile rows (8 attr rows ×
    // 2 each); the last row sits below the visible 240-scanline
    // screen but the PPU still reads it, so we emit all 16 rows to
    // match whatever the original hand-packed attribute byte was.
    println!("// ── Attributes (paste into background Level {{ palette_map: [...] }}) ──");
    println!("    palette_map: [");
    let attr = build_attributes();
    for my in 0..16 {
        let mut s = String::from("        \"");
        for mx in 0..16 {
            // Recover the sub-palette index for metatile (mx, my)
            // from the packed attribute table. Each byte covers a
            // 2×2 metatile block at attr[(my/2)*8 + mx/2], with
            // quadrants laid out as BR BL TR TL in the high bits.
            let byte = attr[(my / 2) * 8 + mx / 2];
            let quadrant = match (mx % 2, my % 2) {
                (0, 0) => byte & 0b11,
                (1, 0) => (byte >> 2) & 0b11,
                (0, 1) => (byte >> 4) & 0b11,
                _ => (byte >> 6) & 0b11,
            };
            s.push(char::from(b'0' + quadrant));
        }
        s.push_str("\",");
        println!("{s}");
    }
    println!("    ]");
}

/// Map a CHR tile index back to its legend character in the
/// generated `map:` block. Must stay in sync with the `legend { ... }`
/// block printed by `main()`.
fn legend_char_for(tile: u8) -> char {
    match tile {
        15 => '.',
        10 => '<',
        11 => '>',
        9 => '#',
        14 => 'Q',
        12 => '^',
        13 => '*',
        7 => '=',
        8 => '%',
        other => panic!("no legend char for tile {other}"),
    }
}
