// -----------------------------------------------------------
// transitions.c - slide transitions
//   every slide is drawn with the display switched off, its
//   colors are snapshotted into FADEBUF and blackened, then an
//   in-transition reveals it; an out-transition hides it again.
//   types (picked per slide in tin_tbl/tout_tbl in main.c):
//     TR_FADE     - fade through black via the fadetbl ramps
//     TR_DISSOLVE - cells appear/vanish in random (LFSR) order
//     TR_WIPE     - column sweep from left to right
//   each reveal starts with a short grey border flash
// -----------------------------------------------------------
#include "transitions.h"
#include "common.h"
#include "music.h"

// ---- wait for the next bottom-border arrival of the beam ----
void wait_frame(void)
{
	while (vic.raster == RAS_BOTTOM)     // first leave line RAS_BOTTOM if we are on it
		;
	while (vic.raster != RAS_BOTTOM)
		;
	music_play();                        // advance the music once per frame
}

// ---- copy color RAM to FADEBUF, then blacken color RAM ----
static void snapshot_colram(void)
{
	for (char i = 0; i < 250; i++)
	{
		FADEBUF[i]       = COLRAM[i]       & 15;   // color RAM reads have
		FADEBUF[i + 250] = COLRAM[i + 250] & 15;   //   garbage upper bits
		FADEBUF[i + 500] = COLRAM[i + 500] & 15;
		FADEBUF[i + 750] = COLRAM[i + 750] & 15;
		COLRAM[i]       = VCOL_BLACK;
		COLRAM[i + 250] = VCOL_BLACK;
		COLRAM[i + 500] = VCOL_BLACK;
		COLRAM[i + 750] = VCOL_BLACK;
	}
}

// ============================================================
// TR_FADE - fade through black
// ============================================================

static char curmap[16];                  // current fade-in color map

// ---- curmap[c] = color c darkened fadecnt times ----
static void build_map(char fadecnt)
{
	for (char c = 0; c < 16; c++)
	{
		char v = c;
		for (char n = fadecnt; n; n--)
			v = fadetbl[v];
		curmap[c] = v;
	}
}

// ---- write curmap[FADEBUF] into the whole color RAM ----
static void apply_map(void)
{
	for (char i = 0; i < 250; i++)
	{
		COLRAM[i]       = curmap[FADEBUF[i]];
		COLRAM[i + 250] = curmap[FADEBUF[i + 250]];
		COLRAM[i + 500] = curmap[FADEBUF[i + 500]];
		COLRAM[i + 750] = curmap[FADEBUF[i + 750]];
	}
}

// ---- one fade-out step: every color RAM cell one shade darker ----
static void apply_fade(void)
{
	for (char i = 0; i < 250; i++)
	{
		COLRAM[i]       = fadetbl[COLRAM[i]       & 15];
		COLRAM[i + 250] = fadetbl[COLRAM[i + 250] & 15];
		COLRAM[i + 500] = fadetbl[COLRAM[i + 500] & 15];
		COLRAM[i + 750] = fadetbl[COLRAM[i + 750] & 15];
	}
}

// ---- fade in: fadecnt counts how many times the target color is
// darkened; counting 4 -> 0 walks each cell from black back
// up to its snapshot color ----
static void fade_in(void)
{
	char fadecnt = FADE_STEPS - 1;
	for (;;)
	{
		build_map(fadecnt);
		apply_map();
		for (char i = 0; i < FADE_SPEED; i++)
			wait_frame();
		if (fadecnt == 0)
			break;
		fadecnt--;
	}
}

// ---- darken the whole color RAM step by step until black ----
static void fade_out(void)
{
	for (char s = 0; s < FADE_STEPS; s++)
	{
		for (char i = 0; i < FADE_SPEED; i++)
			wait_frame();
		apply_fade();
	}
}

// ============================================================
// TR_DISSOLVE - cells appear/vanish in random order
//   a 10-bit LFSR walks all indexes 1..1023 exactly once per
//   period; indexes >= 1000 are skipped, cell 0 (which the LFSR
//   never yields) is handled up front. 25 cells per frame ->
//   the full screen takes 40 frames (0.8 s).
// ============================================================

static unsigned lfsr = 0x0137;           // LFSR state (any nonzero seed);
                                         //   never reset, coverage holds
                                         //   from any starting point

// ---- 10-bit Galois LFSR (x^10 + x^7 + 1, period 1023) ----
// steps until the state is a valid cell index (< 1000)
static unsigned lfsr_next(void)
{
	do
	{
		bool tap = lfsr & 1;
		lfsr >>= 1;
		if (tap)
			lfsr ^= 0x0240;
	} while (lfsr >= 1000);              // skip 1000..1023
	return lfsr;
}

static void dissolve_in(void)
{
	COLRAM[0] = FADEBUF[0];              // cell 0 first
	for (char f = 0; f < 40; f++)
	{
		wait_frame();
		for (char c = 0; c < 25; c++)
		{
			unsigned i = lfsr_next();
			COLRAM[i] = FADEBUF[i];      // restore the snapshot color
		}
	}
}

static void dissolve_out(void)
{
	COLRAM[0] = VCOL_BLACK;              // cell 0 first
	for (char f = 0; f < 40; f++)
	{
		wait_frame();
		for (char c = 0; c < 25; c++)
			COLRAM[lfsr_next()] = VCOL_BLACK;
	}
}

// ============================================================
// TR_WIPE - column sweep, one column per frame, left to right
// ============================================================

static void wipe_in(void)
{
	for (char col = 0; col < 40; col++)
	{
		wait_frame();
		for (char row = 0; row < 25; row++)
		{
			unsigned i = rowoff[row] + col;
			COLRAM[i] = FADEBUF[i];      // restore the snapshot color
		}
	}
}

static void wipe_out(void)
{
	for (char col = 0; col < 40; col++)
	{
		wait_frame();
		for (char row = 0; row < 25; row++)
			COLRAM[rowoff[row] + col] = VCOL_BLACK;
	}
}

// ============================================================
// dispatch
// ============================================================

// ---- draw the current slide while the display is switched off
// (everything shows border color = black), snapshot its colors,
// blacken them, switch the display back on, flash, transition in ----
void show_slide(void)
{
	static const char flashtbl[3] = { VCOL_WHITE, VCOL_LT_GREY, VCOL_DARK_GREY };

	vic.ctrl1 = 0x0b;                    // display off
	slide_setup();
	snapshot_colram();
	vic.ctrl1 = 0x1b;                    // display on

	for (char i = 0; i < 3; i++)         // short white-to-grey border flash:
	{                                    //   gives the reveal a beat
		vic.color_border = flashtbl[i];
		wait_frame();
	}
	vic.color_border = VCOL_BLACK;

	switch (tin_tbl[slide])              // the slide's in-transition
	{
	case TR_FADE:     fade_in();     break;
	case TR_DISSOLVE: dissolve_in(); break;
	case TR_WIPE:     wipe_in();     break;
	}
}

// ---- run the slide's out-transition ----
void trans_out(void)
{
	vic.spr_enable = 0;                  // sprites vanish with the slide

	switch (tout_tbl[slide])
	{
	case TR_FADE:     fade_out();     break;
	case TR_DISSOLVE: dissolve_out(); break;
	case TR_WIPE:     wipe_out();     break;
	}
}
