/*
 * emudore, Commodore 64 emulator
 * Copyright (c) 2016, Mario Ballano <mballano@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <c64/c64.h>
#include <c64/face_node.h>
#include <c64/substrate.h>
#include <hardwarecommunication/port.h>

using namespace myos::hardwarecommunication;

// ── the universal translator: the C64 runs AS A FACE through this interface ──
// face_bind_c64 installs the C64's hardware-contract translation table
// (CHROUT/screen observe + keyboard-buffer input). face_realize/face_tick are
// the OS-agnostic operator — identical for any translated OS.
extern void face_bind_c64(struct FaceNode* f, Memory* mem);
static struct FaceNode g_c64_face;

// ── the REAL 6502 core behind the c64_cpu node (fake6502 bridge) ──
extern "C" {
  void     f6502_bind(Memory* mem);
  void     f6502_reset();
  void     f6502_step();
  void     f6502_irq();
  void     f6502_nmi();
  uint16_t f6502_pc();
  uint8_t  f6502_a();
  uint8_t  f6502_x();
  uint8_t  f6502_y();
}

C64::C64()
{
  /* create chips */
  io_   = new IO();
  cpu_  = new Cpu();
  mem_  = new Memory();
  cia1_ = new Cia1();
  cia2_ = new Cia2();
  vic_  = new Vic();
  sid_  = new Sid();
  mon_  = new Monitor();
  
  /* init cpu */
  cpu_->memory(mem_);
  cpu_->reset();
  /* init vic-ii */
  vic_->memory(mem_);
  vic_->cpu(cpu_);
  vic_->io(io_);
  /* init cia1 */
  cia1_->cpu(cpu_);
  cia1_->io(io_);
  /* init cia2 */
  cia2_->cpu(cpu_);
  /* init io */
  io_->cpu(cpu_);
  io_->memory(mem_);

  /* DMA */
  mem_->vic(vic_);
  mem_->cia1(cia1_);
  mem_->cia2(cia2_);
  mem_->sid(sid_);
 /* r2 support */
 
  mon_->io(io_);
  mon_->mem(mem_);
  mon_->cpu(cpu_);
}

C64::~C64()
{
  delete cpu_;
  delete mem_;
  delete cia1_;
  delete cia2_;
  delete vic_;
  delete sid_;
  delete io_;
  delete mon_;

}

void C64::start()
{
  /* HDGL UNIVERSAL TRANSLATOR: the C64 runs as a FACE. face_realize installs
   * the C64's hardware-contract translation table and resets the real 6502;
   * face_tick steps the guest and TRANSLATES its console-out (screen RAM) into
   * the projection surface. The operator is OS-agnostic — the same calls would
   * drive any translated OS; only the translation table (face_bind_c64) is
   * C64-specific. This is the universality: translate the contract, not the
   * machine. */
  face_bind_c64(&g_c64_face, mem_);
  face_realize(&g_c64_face);           // binds+resets the real 6502 (via table)

  // expose the face's observed surface to the projection renderer
  extern volatile unsigned char g_face_screen[40*25];
  extern volatile unsigned int  g_face_active;
  g_face_active = 1;

  /* ── THE SUBSTRATE OWNS THE LOOP. Initialize it (Omega settles to phi from a
   * per-boot session coordinate), then the orbit below ticks it every
   * iteration. The C64 is registered as a face among faces. This is the merge:
   * the OS64-faces substrate is the core; the 6502 emulator is ONE face. */
  {
    // session coordinate from the CPU timestamp counter (differs each boot)
    uint32_t lo, hi; asm volatile("rdtsc" : "=a"(lo), "=d"(hi));
    hdgl::substrate_init(lo ^ hi);
  }

  /* main substrate orbit loop */
  while(true)
  {
    if(isRunning)
    {
      /* ── SUBSTRATE ORBIT (inline): one small quantum per iteration. The
       * substrate ticks, Omega re-settles to phi, the keyboard is polled EVERY
       * iteration (so input is caught within one small quantum = snappy, like
       * the OS64-faces reference), and the C64 face advances one quantum. The
       * C64 is a FACE here, not the owner of the loop. */
      hdgl::substrate_tick();                 // tau++, Omega = Fix(T) = phi

      /* keyboard is polled at 1000Hz by the PIT tick (IOPITEventHandler),
       * independent of this loop's speed — so input latency is ~1ms flat.
       * Here we just drain the ring into the KERNAL below. */

      /* step the C64 face ONE small quantum (interleaved, snappy) */
      face_tick_steps(&g_c64_face, 12000);

      /* step every OTHER registered face one quantum (fib ladder, etc.) */
      hdgl::face_step_all();

      /* observe the guest screen into the face surface */
      face_tick_observe(&g_c64_face);

      /* copy the translated surface into the projection the renderer draws,
       * tracking whether anything actually changed (skip redraw if not). */
      bool surface_changed = false;
      for(int i=0;i<40*25;i++){
        if(g_face_screen[i] != g_c64_face.surface[i]){
          g_face_screen[i] = g_c64_face.surface[i];
          surface_changed = true;
        }
      }

      /* deliver any observed keystrokes through the translation table (the
       * authentic KERNAL keyboard-buffer path). Snapshot head with interrupts
       * disabled so an IRQ mid-drain can't cause a reprocess race (ABCCDDEE). */
      extern volatile unsigned char g_scan_ring[256];
      extern volatile unsigned int  g_scan_head, g_scan_tail;
      // US-QWERTY set-1 scancode -> ASCII. Two tables: unshifted and shifted,
      // so symbols that need Shift ($ ( ) " * + : ; etc.) translate correctly.
      static const char kmap[128] = {
        0,27,'1','2','3','4','5','6','7','8','9','0','-','=','\b','\t',
        'q','w','e','r','t','y','u','i','o','p','[',']','\r',0,'a','s',
        'd','f','g','h','j','k','l',';','\'','`',0,'\\','z','x','c','v',
        'b','n','m',',','.','/',0,'*',0,' ',0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
      static const char kmap_shift[128] = {
        0,27,'!','@','#','$','%','^','&','*','(',')','_','+','\b','\t',
        'Q','W','E','R','T','Y','U','I','O','P','{','}','\r',0,'A','S',
        'D','F','G','H','J','K','L',':','"','~',0,'|','Z','X','C','V',
        'B','N','M','<','>','?',0,'*',0,' ',0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
      static bool shift_down = false;   // persists across frames
      unsigned int head_snap;
      asm volatile("cli");
      head_snap = g_scan_head;
      asm volatile("sti");
      while(g_scan_tail != head_snap){
        unsigned char raw = g_scan_ring[g_scan_tail & 0xFF]; g_scan_tail++;
        unsigned char sc  = raw & 0x7F;
        bool released = (raw & 0x80) != 0;
        // track shift make/break (left 0x2A, right 0x36)
        if(sc == 0x2A || sc == 0x36){ shift_down = !released; continue; }
        if(released) continue;                        // ignore other releases
        char a = shift_down ? kmap_shift[sc] : kmap[sc];
        if(a){ extern volatile unsigned int g_dbg_facekeys; g_dbg_facekeys++;
               face_key(&g_c64_face, (uint8_t)a); }   // TRANSLATE input
      }

      /* CIA timing + IO + projection refresh (only when the face changed) */
      if(!cia1_->emulate()) break;
      if(!cia2_->emulate()) break;
      if(!vic_->emulate()) break;
      if(!io_->emulate()) break;
      /* also detect VIC colour changes (POKE53280/53281 change colour but not
       * characters, so must trigger a refresh too) */
      {
        extern volatile unsigned char g_vic_border, g_vic_backgnd;
        static unsigned char last_border=255, last_bg=255;
        if(g_vic_border!=last_border || g_vic_backgnd!=last_bg){
          last_border=g_vic_border; last_bg=g_vic_backgnd; surface_changed=true;
        }
      }
      static unsigned int frame_ctr = 0; frame_ctr++;
      if(surface_changed || frame_ctr < 120) io_->screen_refresh();

      if(reset) break;
    }
    else
    {
      mon_->Start();
      isRunning = true;
    }
  }
}

void C64::stop()
{
  isRunning = false;
}




 

