#!/usr/bin/env python3
# genPlaceholderArt.py -- generate generic geometric placeholder art
# for the Space Taxi port. Output is 320x200 indexed PNG, 16-color.
# Replace these with hand-authored art any time; the build pipeline
# picks up whichever PNG sits in assets/.

import os
from PIL import Image, ImageDraw

ROOT = os.path.dirname(os.path.abspath(__file__))

# 16-color palette matching the engine's kDefaultPalette (see stRender.c).
# Each entry is (R, G, B) in 0..255. The .jas format will store the
# 4-bit-per-channel quantization automatically via joeyasset.
PALETTE_RGB = [
    (0x00, 0x00, 0x00),  # 0 black
    (0x00, 0x00, 0x77),  # 1 dark blue
    (0xCC, 0x00, 0x00),  # 2 red
    (0x00, 0x77, 0x00),  # 3 dark green
    (0x66, 0x44, 0x00),  # 4 brown
    (0xEE, 0xEE, 0x00),  # 5 yellow
    (0x88, 0x88, 0x88),  # 6 mid gray
    (0xBB, 0xBB, 0xBB),  # 7 light gray
    (0x44, 0x66, 0xFF),  # 8 sky blue
    (0x77, 0x88, 0xFF),  # 9 light blue
    (0xFF, 0x88, 0x88),  # 10 light red
    (0x88, 0xFF, 0x44),  # 11 light green
    (0xFF, 0x88, 0x00),  # 12 orange
    (0xFF, 0x00, 0xFF),  # 13 magenta
    (0x00, 0xFF, 0xFF),  # 14 cyan
    (0xFF, 0xFF, 0xFF),  # 15 white
]


def new_canvas(w=320, h=200):
    """Returns a fresh w x h indexed PIL image with the palette installed."""
    img = Image.new('P', (w, h), 0)
    flat = []
    for r, g, b in PALETTE_RGB:
        flat.extend([r, g, b])
    flat.extend([0] * (768 - len(flat)))   # pad to 256 entries
    img.putpalette(flat)
    return img


def write_indexed_png(img, path):
    """Save with mode='P' so PNG output is indexed (joeyasset wants this
    when converted through ppm; ImageMagick's `convert` preserves
    indexed mode when writing PPM)."""
    img.save(path, 'PNG', optimize=True)
    print(f"  wrote {path}")


def gen_tilebank():
    """tilebank0.png: 320x200, 40x25 cells of 8x8 each (= 1000 tile
    slots). The engine convention is:
       index   0      = empty (non-solid)
       index   1..63  = solid walls
       index  64..127 = landing-pad surfaces
       index 128..255 = decorative non-solid
    We fill a few representative slots; the rest stay black so they
    show as "empty" if a level references them.
    """
    img = new_canvas()
    px = img.load()

    def draw_tile(idx, painter):
        bx = (idx % 40) * 8
        by = (idx // 40) * 8
        painter(bx, by)

    def block(color):
        return lambda x, y: [px.__setitem__((x+dx, y+dy), color)
                             for dx in range(8) for dy in range(8)]

    def bricks(color_a, color_b):
        def paint(x, y):
            for dy in range(8):
                row_offset = 4 if (y // 8 + dy // 4) % 2 == 0 else 0
                for dx in range(8):
                    is_grout = (dx == (row_offset % 8)) or (dy == 3 or dy == 7)
                    px[x + dx, y + dy] = color_b if is_grout else color_a
        return paint

    def stripes(color_a, color_b):
        def paint(x, y):
            for dy in range(8):
                c = color_a if (dy + (x // 8)) % 2 == 0 else color_b
                for dx in range(8):
                    px[x + dx, y + dy] = c
        return paint

    def landing_pad(color_a, color_b):
        # diagonal stripes signal a landing-pad surface
        def paint(x, y):
            for dy in range(8):
                for dx in range(8):
                    px[x + dx, y + dy] = color_a if ((dx + dy) // 2) % 2 == 0 else color_b
        return paint

    # tile 0 -> already black (empty); leave as-is

    # walls 1..7: a variety of brick/stripe patterns
    draw_tile(1,  bricks(6, 0))                    # gray brick
    draw_tile(2,  bricks(4, 0))                    # brown brick
    draw_tile(3,  block(6))                        # solid gray
    draw_tile(4,  block(4))                        # solid brown
    draw_tile(5,  stripes(6, 7))                   # gray stripes (ceiling)
    draw_tile(6,  stripes(1, 8))                   # blue stripes (sky deco)
    draw_tile(7,  block(7))                        # solid light gray

    # landing pad tiles 64..67
    draw_tile(64, landing_pad(5, 12))              # yellow/orange pad
    draw_tile(65, landing_pad(11, 3))              # green pad
    draw_tile(66, landing_pad(9, 8))               # blue pad
    draw_tile(67, landing_pad(15, 7))              # white pad

    write_indexed_png(img, os.path.join(ROOT, 'tiles', 'tilebank0.png'))


def gen_sprites():
    """RETIRED 2026-07-20: sprites.png is now fully extracted from the
    C64 dump by stuff/spacetaxi/extractSprites.py (taxi $C1/$C0/$DC/
    $DD, passenger $C4-$CB/$D9, flame $6DB0 table, warp $E2-$E8 --
    216x96, 4 rows). Regenerating placeholder art here would clobber
    the real sheet, the same trap gen_font has with the hand-edited
    font.png. Refuses to run; use extractSprites.py instead."""
    print("gen_sprites: REFUSING -- sprites.png is real C64 art now.")
    print("  regenerate via: python3 stuff/spacetaxi/extractSprites.py "
          "stuff/spacetaxi/raw.bin examples/spacetaxi/assets/sprites/sprites.png")
    return


def gen_sprites_retired():
    img = new_canvas(9 * 24, 3 * 24)   # 216x72: 9 cols x 3 rows of 24x24
    d = ImageDraw.Draw(img)

    # ---- Taxi cels: simple side-view "pod with thruster" ----
    # Cel 0 = idle (thruster off)
    # Cel 1..3 = thrust-on, alternating flame frames
    for cel in range(4):
        x = cel * 24
        # Pod body: rounded rectangle in gray
        d.rectangle([x+2,  4, x+21, 16], fill=7,  outline=15)
        # Cockpit window in blue
        d.rectangle([x+13, 6, x+19, 10], fill=8,  outline=15)
        # Landing skid
        d.line([(x+3, 17), (x+20, 17)], fill=6)
        d.line([(x+3, 17), (x+3,  19)], fill=6)
        d.line([(x+20,17), (x+20, 19)], fill=6)
        # Thruster flame (cels 1..3 only, alternating shape)
        if cel >= 1:
            flame_color = [2, 12, 5][(cel - 1) % 3]
            flame_top   = 18
            flame_bot   = 22 if cel != 2 else 23
            d.polygon([(x+8,  flame_top),
                       (x+15, flame_top),
                       (x+12, flame_bot)], fill=flame_color)

    # ---- Taxi shrink cels (transporter warp): the cab receding into the
    # distance, 5 progressively smaller sizes, centered in the 24x24 cell.
    # Placed in the free row-0 slots (cels 4..8). No flame -- it is warping
    # away, not thrusting. The renderer draws one per warp step ($E2..$E9
    # on the C64 = 8 shrink cels; the port approximates with 5 sizes).
    for i in range(5):
        cx    = (4 + i) * 24 + 12
        scale = 1.0 - i * 0.19            # 1.0, 0.81, 0.62, 0.43, 0.24
        bw    = max(3, int(round(19 * scale)))
        bh    = max(2, int(round(12 * scale)))
        x0    = cx - bw // 2
        y0    = 12 - bh // 2
        # Pod body (gray, white outline)
        d.rectangle([x0, y0, x0 + bw, y0 + bh], fill=7, outline=15)
        # Cockpit window (blue) while big enough to show
        if bw >= 8:
            d.rectangle([x0 + bw - 5, y0 + 1, x0 + bw - 1, y0 + 3], fill=8)

    # ---- Passenger cels: small stick figure, walk cycle ----
    # Centered in 16x16, walking right.
    for cel in range(4):
        x = cel * 16
        y = 24
        # Head
        d.ellipse([x+5, y+1, x+10, y+6], fill=10, outline=15)
        # Body
        d.line([(x+7, y+7), (x+7, y+12)], fill=15)
        # Arms (swing alternately based on cel)
        if cel == 0 or cel == 2:
            d.line([(x+4, y+10), (x+10, y+10)], fill=15)
        else:
            d.line([(x+3, y+9),  (x+11, y+11)], fill=15)
        # Legs (alternating stride)
        if cel == 0 or cel == 2:
            d.line([(x+7, y+12), (x+4, y+15)], fill=15)
            d.line([(x+7, y+12), (x+10,y+15)], fill=15)
        else:
            d.line([(x+7, y+12), (x+5, y+15)], fill=15)
            d.line([(x+7, y+12), (x+9, y+15)], fill=15)

    write_indexed_png(img, os.path.join(ROOT, 'sprites', 'sprites.png'))


def gen_font():
    """font.png: 320x200 (40x25 grid of 8x8 ASCII glyphs).
    Authored manually for the printable subset (0x20..0x7E).
    Each glyph cell is at (ascii%40, ascii/40) when ascii<128.
    Unfilled cells stay black (treated as TILE_NO_GLYPH at runtime).

    WARNING: this generator is UPPERCASE-ONLY (lowercase maps to the
    uppercase glyphs). The COMMITTED font.png has been hand-extended with
    real lowercase glyphs, so running this clobbers it -- the HUD reverts
    to all-caps. Regenerate SPRITES only (gen_sprites) unless you also add
    the lowercase glyphs here first; then `git checkout` font.png.
    """
    # A compact 5-pixel-wide bitmap font, drawn in 7 rows with 1px
    # padding on all sides (so 5x7 active inside an 8x8 cell, baseline
    # at row 6). The font supports digits, uppercase letters, space,
    # and basic punctuation -- enough for HUD text. Lowercase letters
    # map to the same glyphs as uppercase (room to add later).
    GLYPHS = {
        ' ': [],
        '!': ['..#..', '..#..', '..#..', '..#..', '.....', '..#..', '.....'],
        '"': ['.#.#.', '.#.#.', '.....', '.....', '.....', '.....', '.....'],
        '0': ['.###.', '#...#', '#..##', '#.#.#', '##..#', '#...#', '.###.'],
        '1': ['..#..', '.##..', '..#..', '..#..', '..#..', '..#..', '.###.'],
        '2': ['.###.', '#...#', '....#', '...#.', '..#..', '.#...', '#####'],
        '3': ['.###.', '#...#', '....#', '..##.', '....#', '#...#', '.###.'],
        '4': ['...#.', '..##.', '.#.#.', '#..#.', '#####', '...#.', '...#.'],
        '5': ['#####', '#....', '####.', '....#', '....#', '#...#', '.###.'],
        '6': ['.###.', '#....', '#....', '####.', '#...#', '#...#', '.###.'],
        '7': ['#####', '....#', '...#.', '..#..', '..#..', '..#..', '..#..'],
        '8': ['.###.', '#...#', '#...#', '.###.', '#...#', '#...#', '.###.'],
        '9': ['.###.', '#...#', '#...#', '.####', '....#', '....#', '.###.'],
        ':': ['.....', '..#..', '.....', '.....', '.....', '..#..', '.....'],
        '.': ['.....', '.....', '.....', '.....', '.....', '..#..', '.....'],
        '-': ['.....', '.....', '.....', '#####', '.....', '.....', '.....'],
        '/': ['....#', '...#.', '...#.', '..#..', '.#...', '.#...', '#....'],
        'A': ['.###.', '#...#', '#...#', '#####', '#...#', '#...#', '#...#'],
        'B': ['####.', '#...#', '#...#', '####.', '#...#', '#...#', '####.'],
        'C': ['.###.', '#...#', '#....', '#....', '#....', '#...#', '.###.'],
        'D': ['####.', '#...#', '#...#', '#...#', '#...#', '#...#', '####.'],
        'E': ['#####', '#....', '#....', '####.', '#....', '#....', '#####'],
        'F': ['#####', '#....', '#....', '####.', '#....', '#....', '#....'],
        'G': ['.###.', '#...#', '#....', '#..##', '#...#', '#...#', '.###.'],
        'H': ['#...#', '#...#', '#...#', '#####', '#...#', '#...#', '#...#'],
        'I': ['.###.', '..#..', '..#..', '..#..', '..#..', '..#..', '.###.'],
        'J': ['..###', '...#.', '...#.', '...#.', '...#.', '#..#.', '.##..'],
        'K': ['#...#', '#..#.', '#.#..', '##...', '#.#..', '#..#.', '#...#'],
        'L': ['#....', '#....', '#....', '#....', '#....', '#....', '#####'],
        'M': ['#...#', '##.##', '#.#.#', '#.#.#', '#...#', '#...#', '#...#'],
        'N': ['#...#', '##..#', '#.#.#', '#.#.#', '#..##', '#...#', '#...#'],
        'O': ['.###.', '#...#', '#...#', '#...#', '#...#', '#...#', '.###.'],
        'P': ['####.', '#...#', '#...#', '####.', '#....', '#....', '#....'],
        'Q': ['.###.', '#...#', '#...#', '#...#', '#.#.#', '#..#.', '.##.#'],
        'R': ['####.', '#...#', '#...#', '####.', '#.#..', '#..#.', '#...#'],
        'S': ['.###.', '#...#', '#....', '.###.', '....#', '#...#', '.###.'],
        'T': ['#####', '..#..', '..#..', '..#..', '..#..', '..#..', '..#..'],
        'U': ['#...#', '#...#', '#...#', '#...#', '#...#', '#...#', '.###.'],
        'V': ['#...#', '#...#', '#...#', '#...#', '.#.#.', '.#.#.', '..#..'],
        'W': ['#...#', '#...#', '#...#', '#.#.#', '#.#.#', '##.##', '#...#'],
        'X': ['#...#', '#...#', '.#.#.', '..#..', '.#.#.', '#...#', '#...#'],
        'Y': ['#...#', '#...#', '.#.#.', '..#..', '..#..', '..#..', '..#..'],
        'Z': ['#####', '....#', '...#.', '..#..', '.#...', '#....', '#####'],
    }

    img = new_canvas()
    px = img.load()

    def paint_glyph(ascii_code, glyph_rows):
        col = ascii_code % 40
        row = ascii_code // 40
        bx  = col * 8
        by  = row * 8
        # active 5x7 area: cell-x 1..5, cell-y 0..6  (top row, 1px left pad)
        for ry, line in enumerate(glyph_rows):
            for rx, ch in enumerate(line):
                if ch == '#':
                    px[bx + 1 + rx, by + ry] = 15      # white

    for ascii_code in range(32, 127):
        c = chr(ascii_code)
        if c in GLYPHS:
            paint_glyph(ascii_code, GLYPHS[c])
        elif 'a' <= c <= 'z':
            paint_glyph(ascii_code, GLYPHS[c.upper()])

    write_indexed_png(img, os.path.join(ROOT, 'font.png'))


def main():
    os.makedirs(os.path.join(ROOT, 'tiles'),   exist_ok=True)
    os.makedirs(os.path.join(ROOT, 'sprites'), exist_ok=True)
    print("genPlaceholderArt: emitting 320x200 indexed PNGs...")
    gen_tilebank()
    gen_sprites()
    gen_font()
    print("done.")


if __name__ == '__main__':
    main()
