#include "screens.h"
#include <c64/vic.h>
#include <c64/cia.h>
#include <oscar.h>
#include <string.h>

// --- memory layout for the embedded screen data ------------------------
//
// The 5 LZO-compressed bitmaps are ~28 KB total (5500-6100 bytes per
// screen).  The 5 .attr tables are 5 KB total (1000 bytes each, kept
// uncompressed because they don't compress well).  Together that's
// ~33 KB, more than the default main region (~34 KB) can comfortably
// hold alongside code (~700 B), BSS (~50 B), stack, and heap.
//
// So we split the data across two regions:
//
//   1. LZO bitmaps in the main region (default data section, $0880-
//      $9000).  They're read-only, used once per state transition, and
//      they go right after the code so the linker can pack them
//      tightly.
//
//   2. .attr tables in a custom "screens" region at $A000-$C000 (the
//      BASIC ROM area, which is banked out as RAM after
//      memmap_setup() and is not used by the trampoline).  This is
//      8 KB, more than enough for 5 × 1000 = 5 KB of attr data.
//
// The trampoline is in the code section (around $08B3-$08FD), not at
// $A000+ — the $A000+ entries in the .map are sstack (subroutine
// stack) declarations of size 0, not actual data.  So the entire
// $A000-$C000 range is free for our use.
//
// We don't need a heap for this program (no malloc).  We set
// heapsize(0) below in main.c to maximize the room available for data.

// --- embedded bitmap data (LZO-compressed, in main region) ------------

const char ScreenTitleBin[] = {
	#embed 8000 0 lzo "data/processed/title.bin"
};

const char ScreenWaiting1Bin[] = {
	#embed 8000 0 lzo "data/processed/waiting1.bin"
};

const char ScreenWaiting2Bin[] = {
	#embed 8000 0 lzo "data/processed/waiting2.bin"
};

const char ScreenWinHareBin[] = {
	#embed 8000 0 lzo "data/processed/win_hare.bin"
};

const char ScreenWinScootBin[] = {
	#embed 8000 0 lzo "data/processed/win_scoot.bin"
};

// --- embedded .attr data (uncompressed, in custom "screens" region) ---

#pragma section( screens, 0)

#pragma region( screens, 0xA000, 0xC000, , , {screens} )

#pragma data(screens)

const char ScreenTitleAttr[] = {
	#embed "data/processed/title.attr"
};

const char ScreenWaiting1Attr[] = {
	#embed "data/processed/waiting1.attr"
};

const char ScreenWaiting2Attr[] = {
	#embed "data/processed/waiting2.attr"
};

const char ScreenWinHareAttr[] = {
	#embed "data/processed/win_hare.attr"
};

const char ScreenWinScootAttr[] = {
	#embed "data/processed/win_scoot.attr"
};

#pragma data(data)

// --- per-screen descriptor ---------------------------------------------
//
// d021 is the $D021 background color value generated by
// tools/convert_screens.py as a .d021 sidecar file.  We inline the
// value here rather than reading the file at runtime:
//   title=0 (black), waiting1=0 (black), waiting2=11 (dark grey),
//   win_hare=0 (black), win_scoot=0 (black).

struct ScreenDef {
    const char *lzo;  // LZO-compressed bitmap
    const char *attr; // raw screen memory (1000 bytes)
    byte d021;        // background color
};

static const struct ScreenDef screens[5] = {
    { ScreenTitleBin,     ScreenTitleAttr,     0  },  // SCREEN_TITLE
    { ScreenWaiting1Bin,  ScreenWaiting1Attr,  0  },  // SCREEN_WAITING1
    { ScreenWaiting2Bin,  ScreenWaiting2Attr,  11 },  // SCREEN_WAITING2
    { ScreenWinHareBin,   ScreenWinHareAttr,   0  },  // SCREEN_WIN_HARE
    { ScreenWinScootBin,  ScreenWinScootAttr,  0  },  // SCREEN_WIN_SCOOT
};

// --- helpers -----------------------------------------------------------

// Copy `len` bytes from `src` to `dst`.  Used for the .attr data
// (1000 bytes) and for the show_white_screen() bitmap clear.  The
// compiler turns this into a tight loop; for 1000 bytes that's well
// under a frame at 1 MHz.
static void copy_bytes(const char *src, char *dst, unsigned len)
{
    for (unsigned i = 0; i < len; i++)
        dst[i] = src[i];
}

// Clear the color RAM at $D800-$DBFF (4 pages × 256 bytes, slightly
// more than the 1000-byte logical range $D800-$DBE7; the extra 24
// bytes are harmless mirrors).  Each nibble = 0 = black, which is the
// default "11" pixel value for cells that don't explicitly set color
// RAM.
static void clear_color_ram(void)
{
    __asm {
        lda #0
        ldx #4
        ldy #0
    L0: sta $d800, y
        sta $d900, y
        sta $da00, y
        sta $db00, y
        iny
        bne L0
        dex
        bne L0
    }
}

// Configure the VIC for multicolor bitmap mode pointing at the data
// at $D000 (screen memory) and $E000 (bitmap).  Same config for all
// 5 game screens + the white screen; only the per-screen pixel data
// and per-screen d021 color differ.
static void vic_setup_mcm(void)
{
    vic.ctrl1 = VIC_CTRL1_BMM | VIC_CTRL1_DEN | VIC_CTRL1_RSEL;
    vic.ctrl2 = VIC_CTRL2_MCM | VIC_CTRL2_CSEL;
    cia2.pra  = (cia2.pra & 0xfc) | 0x00;
    vic.memptr = 0x48;
}

// --- public API: show_screen() -----------------------------------------

void show_screen(int n)
{
    if (n < 0 || n >= 5) return;
    const struct ScreenDef *s = &screens[n];

    // 1. Decompress the 8 KB bitmap into $E000-$FFFF and copy the 1 KB
    //    screen memory into $D000-$D3E7.  Both happen while the VIC
    //    is still in its old mode (or, on the very first call, in
    //    whatever state memmap_setup() left it).  We do the attr
    //    copy first so the visible region stays coherent for as long
    //    as possible during the bitmap decompression.
    copy_bytes(s->attr, (char *)0xD000, 1000);
    oscar_expand_lzo((char *)0xE000, s->lzo);

    // 2. Clear color RAM (the "11" color per cell; we don't have
    //    per-cell "11" data in the .attr files, so it stays 0).
    clear_color_ram();

    // 3. Set the per-screen colors.
    vic.color_back  = s->d021;
    vic.color_back1 = 0;  // unused in the 5 menu screens (no "01" pixels)
    vic.color_back2 = 0;  // unused in the 5 menu screens (no "10" pixels)
    vic.color_back3 = 0;
    vic.color_border = 0;

    // 4. Flip the VIC into multicolor bitmap mode.
    vic_setup_mcm();
}

// --- public API: show_white_screen() -----------------------------------

void show_white_screen(void)
{
    // Fill the 8 KB bitmap at $E000-$FFFF with 0x00 so every pixel is
    // a "00" code (which uses $D021).  At ~1 byte per 2-3 cycles via
    // a simple loop, this is ~5-8 ms, well under a 20 ms frame.
    char *p = (char *)0xE000;
    for (unsigned i = 0; i < 8000; i++)
        p[i] = 0;

    // Clear screen memory for tidiness.  The top 40 cells are
    // overwritten by score_render() right after this returns.
    char *sm = (char *)0xD000;
    for (unsigned i = 0; i < 1000; i++)
        sm[i] = 0;

    // Clear color RAM.  With the bitmap = 0 there are no "11" pixels,
    // so this is technically unnecessary, but doing it keeps the
    // score bar cells predictable (score_render() will set the top 40
    // cells' color RAM to 1 = white).
    clear_color_ram();

    // Whole screen white, including the border.  $D021 is the "00"
    // color and is the only one that matters for the body of the
    // screen (bitmap is 0); $D022 and $D023 are set to white too in
    // case any stray "01" / "10" pixel ever appears.  $D020 is the
    // visible border around the bitmap.
    vic.color_back  = 1;  // $D021 = white
    vic.color_back1 = 1;  // $D022 = white (for any "01" pixel)
    vic.color_back2 = 1;  // $D023 = white (for any "10" pixel)
    vic.color_back3 = 0;
    vic.color_border = 1; // $D020 = white border

    // Same VIC mode config as show_screen() — the body of the
    // bitmap happens to be 0, but the score bar overlay (drawn by
    // score_render()) writes to the top 8 rows and expects the VIC
    // to be in multicolor bitmap mode.
    vic_setup_mcm();
}
