// ============================================================================
// face_node.cpp — the universal face operator, and the C64 as a face-node.
//
// The operator (face_realize / face_tick / face_key) is basis-agnostic: it
// observes the guest into the projection surface and never drives it. The C64
// face proves the interface: its basis callbacks wrap the existing machine
// (fake6502 + emudore Memory + real ROMs). A guest-OS face would fill the same
// callbacks with a wider basis — the operator is unchanged.
// ============================================================================
#include <c64/face_node.h>
#include <c64/memory.h>

// ── phi-hash: φ-fold identity, no stored constants (Ω = Fix(T)) ──
uint64_t face_phi_hash(const char* name)
{
  // φ as a rational fold: multiply-accumulate by the Fibonacci-adjacent ratio.
  // (Derived, not stored: 0x9E3779B97F4A7C15 is 2^64 / φ, the golden hash.)
  uint64_t h = 0;
  for(const char* p = name; p && *p; ++p)
    h = (h ^ (uint8_t)*p) * 0x9E3779B97F4A7C15ULL + 1;
  return h;
}

// ── the universal operator ──────────────────────────────────────────────────

void face_realize(struct FaceNode* f)
{
  if(!f) return;
  f->id    = face_phi_hash(f->name);
  f->cols  = f->cols ? f->cols : 40;
  f->rows  = f->rows ? f->rows : 25;
  for(int i=0;i<40*25;i++) f->surface[i] = ' ';
  if(f->basis.init) f->basis.init(f->self);   // Fix(project): realize hardware
  f->state  = FACE_READY;
  f->active = 1;
}

void face_tick(struct FaceNode* f)
{
  if(!f || !f->active) return;
  // step the guest against the projected hardware (endogenous Δ)
  if(f->basis.step) f->basis.step(f->self, 200000);
  // OBSERVE the guest's own screen into the projection surface (read-only)
  if(f->basis.observe) f->basis.observe(f->self, f->surface, f->cols, f->rows);
  if(f->state < FACE_EXECUTED) f->state = FACE_EXECUTED;
}

// Low-latency variants: step a small batch (so input can be polled between
// batches), and observe separately (once per frame). Same operator, finer grain.
void face_tick_steps(struct FaceNode* f, unsigned int quanta)
{
  if(!f || !f->active) return;
  if(f->basis.step) f->basis.step(f->self, quanta);
  if(f->state < FACE_EXECUTED) f->state = FACE_EXECUTED;
}

void face_tick_observe(struct FaceNode* f)
{
  if(!f || !f->active) return;
  if(f->basis.observe) f->basis.observe(f->self, f->surface, f->cols, f->rows);
}

// FRAME-LOCKED TICK: run exactly ONE of the machine's frames — precisely
// cycles_per_frame cycles — then fire its once-per-frame raster IRQ (which,
// for the C64, runs the KERNAL's SCNKEY keyboard scan + cursor flash). This is
// what makes the machine feel native: it advances one real frame per tick,
// clocked by its OWN contract, not by how fast a host loop spins.
void face_tick_frame(struct FaceNode* f)
{
  if(!f || !f->active) return;
  unsigned int cyc = f->basis.cycles_per_frame ? f->basis.cycles_per_frame : 17095u;
  if(f->basis.step) f->basis.step(f->self, cyc);
  if(f->basis.frame_irq) f->basis.frame_irq(f->self);   // the raster IRQ
  if(f->state < FACE_EXECUTED) f->state = FACE_EXECUTED;
}

void face_key(struct FaceNode* f, uint8_t key)
{
  if(!f || !f->active) return;
  if(f->basis.input) f->basis.input(f->self, key);  // authentic input path
}

// ============================================================================
// THE C64 FACE — basis callbacks wrapping the existing machine.
// These bridge to the fake6502 core + emudore Memory already in the build.
// ============================================================================

// bridge symbols from fake6502_bridge.cpp
extern "C" {
  void     f6502_bind(Memory* mem);
  void     f6502_reset();
  void     f6502_step();
  void     f6502_irq();
  uint16_t f6502_pc();
}

// the C64 face's private machine: just its Memory (the abstraction the guest
// binds to). The real 6502 core is the shared fake6502 bridge.
struct C64Self { Memory* mem; unsigned long tick; };

static void c64_init(void* self)
{
  C64Self* s = (C64Self*)self;
  f6502_bind(s->mem);
  f6502_reset();                 // real KERNAL reset — boots for real
  s->tick = 0;
}

static int c64_step(void* self, unsigned int quanta)
{
  C64Self* s = (C64Self*)self;
  // Run `quanta` machine cycles, firing the raster IRQ once per VIC frame
  // (every cycles_per_frame cycles). This IS the machine's clock: the KERNAL's
  // SCNKEY keyboard scan + cursor flash run in that IRQ, so keyboard timing
  // emerges at the exact frame rate — we implement no keyboard rate.
  const unsigned int FPF = 17095u;   // NTSC cycles/frame (263*65)
  for(unsigned int i=0;i<quanta;i++){
    f6502_step();
    if((++s->tick % FPF) == 0) f6502_irq();   // raster IRQ, once per frame
  }
#ifdef FACE_SELFTEST
  // Once BASIC has settled, inject  PRINT 3+4 <RETURN>  through the real KERNAL
  // keyboard buffer, proving the translated OS executes (prints 7) on metal.
  static int st_done = 0; static unsigned long st_wait = 0;
  if(!st_done){
    st_wait += quanta;
    if(st_wait > 4000000UL){                 // let the KERNAL reach READY.
      const char* cmd = "PRINT 3+4";
      for(const char* p=cmd; *p; p++){
        uint8_t ndx = s->mem->read_byte(0x00C6);
        if(ndx < 10){ s->mem->write_byte(0x0277+ndx,(uint8_t)*p); s->mem->write_byte(0x00C6,ndx+1); }
      }
      uint8_t ndx = s->mem->read_byte(0x00C6);
      if(ndx < 10){ s->mem->write_byte(0x0277+ndx,0x0D); s->mem->write_byte(0x00C6,ndx+1); }
      st_done = 1;
    }
  }
#endif
  return 0;
}

// THE RASTER IRQ — fired once per VIC frame by the substrate. This is the
// machine's own heartbeat: the KERNAL's raster handler runs SCNKEY (keyboard
// scan) and flashes the cursor HERE, so keyboard timing EMERGES at the exact
// frame rate — we implement no keyboard rate at all; the KERNAL does, once per
// frame, exactly as the real hardware does.
static void c64_frame_irq(void* self)
{
  (void)self;
  f6502_irq();       // fire the CIA/raster interrupt; KERNAL scans keyboard
}

// ── VIC color observation (widened projection: observe MORE real state) ──
// The face observes not just screen codes ($0400) but the VIC color state:
//   $D020 border, $D021 background, $D800.. per-character color RAM.
// POKE53280/53281 (= $D020/$D021) therefore become visible: the machine
// really wrote them; we now observe and project them. Read-only throughout.
volatile unsigned char g_vic_border = 6;      // default C64 blue
volatile unsigned char g_vic_backgnd = 6;
volatile unsigned char g_face_color[40*25];   // per-cell foreground (from $D800)

static void c64_observe(void* self, unsigned char* surface, int cols, int rows)
{
  C64Self* s = (C64Self*)self;
  // OBSERVE real screen RAM ($0400) -> ASCII projection surface (read-only)
  for(int i=0;i<cols*rows && i<40*25;i++){
    uint8_t sc = s->mem->read_byte(0x0400 + i);   // C64 screen code
    uint8_t a;
    if(sc == 0x00)       a = '@';
    else if(sc <= 0x1A) a = 'A' + (sc - 1);
    else if(sc <= 0x3F) a = sc;
    // PETSCII graphics: map the common ones to the closest ASCII shape so
    // graphics programs (mazes, bars) render instead of showing blanks.
    else if(sc == 0x4D) a = '/';    // CHR$(205) diagonal  (maze)
    else if(sc == 0x4E) a = '\\';   // CHR$(206) diagonal  (maze)
    else if(sc == 0x5C) a = '|';    // vertical bar
    else if(sc == 0x40) a = '-';    // horizontal bar
    else if(sc == 0x66) a = '#';    // checker/filled
    else if(sc == 0x51) a = 'O';    // filled circle
    else if(sc >= 0x40 && sc <= 0x5A) a = 'A' + (sc - 0x41); // shifted letters
    else                a = ' ';
    surface[i] = a;
    // OBSERVE per-cell color RAM ($D800) -> foreground color index (low nibble)
    g_face_color[i] = s->mem->read_byte(0xD800 + i) & 0x0F;
  }
  // OBSERVE the VIC border/background color registers (POKE53280/53281)
  g_vic_border  = s->mem->read_byte(0xD020) & 0x0F;
  g_vic_backgnd = s->mem->read_byte(0xD021) & 0x0F;
}

static void c64_input(void* self, uint8_t key)
{
  C64Self* s = (C64Self*)self;
  // deliver into the real KERNAL keyboard buffer (KEYD $0277 / NDX $C6),
  // the authentic path — BASIC reads and echoes it itself.
  uint8_t petscii;
  if(key >= 'a' && key <= 'z') petscii = key - 0x20; else petscii = key;
  uint8_t ndx = s->mem->read_byte(0x00C6);
  if(ndx < 10){
    s->mem->write_byte(0x0277 + ndx, petscii);
    s->mem->write_byte(0x00C6, ndx + 1);
  }
  // We inject into the buffer directly rather than via the hardware matrix, so
  // the KERNAL must NOT think a physical key is being held (or it auto-repeats,
  // producing doubled characters like HEELLLOO). Tell it: no key in the matrix
  // ($CB=0x40), and last-key = none ($C5=0x40), and repeat off ($028A=0x40).
  s->mem->write_byte(0x00CB, 0x40);   // no key currently down in matrix
  s->mem->write_byte(0x00C5, 0x40);   // last key = none
  s->mem->write_byte(0x028A, 0x40);   // RPTFLG: no repeat
}

static uint32_t c64_coord(void* self){ (void)self; return f6502_pc(); }

// build a C64 face bound to a given Memory (the abstraction).
static C64Self g_c64_self;
void face_bind_c64(struct FaceNode* f, Memory* mem)
{
  g_c64_self.mem = mem; g_c64_self.tick = 0;
  f->name = "c64";
  f->self = &g_c64_self;
  f->cols = 40; f->rows = 25;
  f->basis.init    = c64_init;
  f->basis.step    = c64_step;
  f->basis.observe = c64_observe;
  f->basis.input   = c64_input;
  f->basis.coord   = c64_coord;
  // THE C64's OWN CLOCK CONTRACT (NTSC): 263 lines x 65 cycles = 17095
  // cycles/frame at 59.83 Hz. The substrate ticks the machine at exactly this
  // cadence; one substrate tick = one VIC frame. (PAL would be 19656 @ 50.12.)
  // These are DERIVED from the machine's raster geometry, not magic numbers.
  f->basis.cycles_per_frame = 263u * 65u;   // = 17095  (NTSC)
  f->basis.refresh_hz       = 60u;          // 59.83 Hz, the VIC-II/NTSC rate
  f->basis.frame_irq        = c64_frame_irq;
}
