#include <stdio.h>

#include <stdlib.h>

#include <time.h>


/* PENDING: 
    1) Starting singlecpu nowadays only shows stars, not the text/logo.
       I must have broken something with the most recent optimisations.
       (which I think were all in JPP8???)
 */


/* #define DUALCPU */ /* Smart-Aleck debugging technique - usually passed in via makefile */
/*
    dualcpu is a version which includes both my own translated
    code, and retrocade's emulator.  Instructions are executed
    in lockstep, and the virtual processor is halted if a mis-
    match is found between both copies of any register.  This
    helps me find bugs in my code generation extremely easily.
 */
#ifndef FALSE

#define FALSE (0!=0)

#define TRUE (0==0)

#endif


int tracing = FALSE;
int debug = FALSE;
int onscreen_debug = TRUE;
int beginner = FALSE;

#include "mdep.h"


#include "optimiser.c"


#ifdef ALLEGRO

#define GRAPHICS

#else

#define SCREEN_H 1024

#define SCREEN_W 1280

#endif


#include "mdep.c"


static int LastFrames = 0;
static int FPS;
static int pleasedisplay = TRUE;
static clock_t now = 0, then = 0;
static clock_t nowtick = 0, thentick = 0;

void CinemaClearScreen(void)
{
  static int parity = 0, slowdown = 0;
  static int second = 0;
  int mx, my;

  parity ^= 1;
  second += 1;
  if (second == 120) { /* Regenerate shields when low, slowly. */
    second = 0;
    if (beginner) {
      if (ram[0x81] < 9) ram[0x81] += 1;
#ifdef DUALCPU

      if (RCram[0x81] < 9) RCram[0x81] += 1;
#endif

    }
  }
  if (parity) {

    Frames++;
#ifdef ALLEGRO

    /* Calculate FPS */
    nowtick = clock(); now = nowtick/CLOCKS_PER_SEC;
    if (now != then) {
      then = now;
      FPS = Frames - LastFrames;
      LastFrames = Frames;
    }
#ifdef NEVER

    ioSwitches |= IO_COIN; /* Clear coin after one frame */
    coinflag = IO_COIN;
#endif

    mousecode = 0;
    get_mouse_mickeys(&mickeyx, &mickeyy);
    mx = mickeyx; my = mickeyy;
    mousecode |= IO_LEFT; mousecode |= IO_RIGHT;

    if (mx > xmousethresh) mousecode &= ~IO_RIGHT;
    if ((-mx) > xmousethresh) mousecode &= ~IO_LEFT;

    if (my > ymousethresh) mousecode |= IO_UP; else mousecode &= ~IO_UP;
    if (-my > ymousethresh) mousecode |= IO_DOWN; else mousecode &= ~IO_DOWN;

    if (mouse_b & 1) fireflag &= ~IO_FIRE; else fireflag |= IO_FIRE;
    if (mouse_b & 2) shieldsflag &= ~IO_SHIELDS; else shieldsflag |= IO_SHIELDS;

    if (keypressed()) {
      int k = readkey() & 0xff;
      if (k == '1') startflag = 0;
      if (k == '3') {
        ioSwitches &= ~IO_COIN; /* Clear coin counter the bodgy way */
        coinflag = 0;
      }
      if (k == ' ') shieldsflag = IO_SHIELDS;
	if (k == 27) {
        if (debugf) {
          fprintf(debugf, "Closing due to ESC pressed\n");
          fflush(debugf); fclose(debugf);
        }
        /* Save PC, registers and 256 bytes of Ram!  Reload on restart - keep high-score */
        save_config(); 
        exit(0);
      }
    } else {
      startflag = IO_START; /* Bodge.  1/FPS key debouncing! */
    }

    ioInputs = mousecode | fireflag | shieldsflag | startflag;

    /*if (ccpudebug)*/ if (onscreen_debug) {char msg[80];
      sprintf(msg, "ioInputs = %02x Shields = %02x Mx = %04d  My = %04d  Snd %0d/%0d  FPS %0d",
        ioInputs, ram[0x81], mx, my, sound_addr, sound_data, FPS);
      /* the mouse button RCstate is stored in the variable mouse_b */
      textout(buffer, font, msg, 16, 450, 15);
      /*textout(buffer, font, disassembly, 16, 430, 15);*/
    }

    vsync(); /* remove this and the vsync below to see just how fast the game can run!  (WOW!) */

    if (TRUE || pleasedisplay) {
      blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
      pleasedisplay = FALSE;
    }

#ifdef NEVER /* Only needed in speed testing */

    if ((nowtick>>1) != (thentick>>1)) { /* Upper limit on speed */
      clear(buffer);
      pleasedisplay = TRUE;
      thentick = nowtick;
    }
#else

    clear(buffer);
#endif


#else

    if (ccpudebug) fprintf(stdout, "CLEAR: %d\n", Frames);
#endif

  } else {
    /* Doing every second vsync() here makes the speed match that
       of Retrocade (and I presume the original?) almost exactly. */
#ifdef ALLEGRO

#ifndef DUALCPU

    if ((slowdown ^= 1) == 0) vsync();
#endif

#endif

  }
}

static int MinX=1000, MinY=1000, MaxX=0, MaxY=0;

void CinemaVectorData(int RCFromX, int RCFromY, int RCToX, int RCToY, int vgColour)
{
  /*Remove stars: if ((RCFromX == RCToX) && (RCFromY == RCToY)) return;*/

  RCFromX = (RCFromX * 2) / 3; RCToX = (RCToX * 2) / 3;
  RCFromY = (RCFromY * 2) / 3; RCToY = (RCToY * 2) / 3;
  RCFromX -= 32; RCToX -= 32;
  RCFromY = SCREEN_H-RCFromY; RCToY=SCREEN_H-RCToY;

/* HACK */ if (vgColour & 0x8) vgColour = 0xf; else vgColour = 0x7;


#ifdef ALLEGRO

  if (ccpudebug) if (debugf) fprintf(debugf, "LINE: From (%0d,%0d) To (%0d,%0d) Col %0d\n",
          RCFromX, RCFromY, RCToX, RCToY, vgColour);
#else

  fprintf(stdout, "LINE: From (%0d,%0d) To (%0d,%0d) Col %0d\n",
          RCFromX, RCFromY, RCToX, RCToY, vgColour);
#endif


  if (RCFromX < MinX) MinX = RCFromX;
  if (RCToX < MinX) MinX = RCToX;
  if (RCFromY < MinY) MinY = RCFromY;
  if (RCToY < MinY) MinY = RCToY;
  if (RCFromX > MaxX) MaxX = RCFromX;
  if (RCToX > MaxX) MaxX = RCToX;
  if (RCFromY > MaxY) MaxY = RCFromY;
  if (RCToY > MaxY) MaxY = RCToY;
#ifdef ALLEGRO

  line(buffer,RCFromX,RCFromY,RCToX,RCToY,vgColour);
#endif

}

void xUNFINISHED(char *s, int PC)
{
  if (!debugf) {
    debugf = fopen("retro.crash", "w");
  }
  if (debugf) fprintf(debugf, "%04x DANGER: Unimplemented instruction - %s\n",
    PC, s);
  exit(1);
}
#define UNFINISHED(s) xUNFINISHED(s, register_PC)


/* Reset C-CPU registers, flags, etc to default starting values
 */
void cineSetJMI(UINT8 j) {
  ccpu_jmi_dip = j;
}

void cineSetMSize(UINT8 m) {
  ccpu_msize = m;

}

void cineSetMonitor(UINT8 m) {
  ccpu_monitor = m;
}

void cineReleaseTimeslice ( void ) {
  bBailOut = TRUE;
}

/* extra functions */

#ifdef DUALCPU


#include "cinedbg.c"


char disassembly[256];
#include "cineops.c"


/*#define RAWIO*/   /* Used to debug higher-level IO procedures */
#include "tailgunr-exec.c"


#define CHECK(register_A) \

    if (register_A != RC##register_A) \
      {ERROR("L%04x: RC%s = %d  %s = %d\n", old_PC, #register_A, RC##register_A, #register_A, register_A); anyfail = TRUE;}

static int anyfail = FALSE;
#endif


int main(int argc, char **argv) {
#ifndef DUALCPU

#include "dispatch-ops.h"  /* Switch label array */

#endif

  int old_PC, i;
  /* We do read the eprom contents into memory, in case the code is
     pulling data out of the rom, but we do not execute from this copy
     (unless we later discover that there is some self-modifying code
     going on, but that's unlikely in this architecture since it's
     stored in an eprom :-) )
   */


  clock_t before, after;

  /* -- Context information ends. */

  if ((argc > 1) && (strcmp(argv[1], "-b") == 0)) beginner = TRUE;

#ifdef DUALCPU

  debugf = fopen("retro.log", "w");
#else

  debugf = NULL;
#endif


  before = clock();

  init_graph();

  bNewFrame = 0;
  sdwGameXSize = SCREEN_W;
  sdwGameYSize = SCREEN_H;
  bFlipX = 0;  bFlipY = 0;     bSwapXY = 0;
  ioInputs = 0xffff;

  ioSwitches = 0xffff; /* Tweaked  testing bottom bit = quarters/game */
  ioSwitches = (ioSwitches & (~SW_SHIELDMASK)) | SW_SHIELDS; /* GT */

  ioSwitches &= ~SW_QUARTERS_PER_GAME; /* One quarter per game */
  ioSwitches |= quarterflag;

  /*bOverlay = 0xff;*/

  cineSetJMI(0);     /* Use external input */
  cineSetMSize(1);   /* 8K */
  cineSetMonitor(0); /* bi-level */

  CinemaClearScreen(); /* Initialise ioInputs etc before game starts */

  memset(ram, 0, sizeof(ram));

  (void)load_config();

  /* We cannot use gtExec() in a loop on its own the same wasy as cineExec()
     at the moment, because we are not yet correctly updating the PC in that code */
#ifdef DUALCPU

  ccpudebug = 0; /* Off until turned on in interpreter */
  for (;;) {
    *disassembly = '\0';
    old_PC = register_PC = RCregister_PC;
    cineExec(); gtExec();
    if (*disassembly != '\0') if (debugf) fprintf(debugf, "%s\n", disassembly);
    CHECK(register_A);
    CHECK(register_B);
    CHECK(register_I);
    CHECK(register_J);
    CHECK(register_P);
    CHECK(cmp_old);
    CHECK(cmp_new);
#ifdef NEVER /* Let's see what a difference this makes to the speed */

    for (i = 0; i < 256; i++) {
      if (RCram[i] != ram[i]) {
        anyfail = TRUE;
        ERROR("L%04x: RCram[0x%02x] = 0x%04x  ram[0x%02x] = 0x%04x\n%s\n", old_PC, i, RCram[i], i, ram[i], disassembly);
      }
    }
#endif

    if (anyfail) exit(1);
    if (bNewFrame != 0) {
      bNewFrame = 0;
      CinemaClearScreen();
    }
    if (bailOut) bailOut = FALSE;
  }
#else

  for (;;) {
/*#define RAWIO*/   /* Used to debug higher-level IO procedures */
#include "tailgunr-ops.c"

    Lelse:
        /* Jumping to any illegal address or end of eprom will come here */
        fprintf(stderr, "Error: control was passed to 0x%04x (%d)\n",
          register_PC, register_PC);
        {FILE *errfile = fopen("error.log", "w");
          if (errfile != NULL) {
            fprintf(errfile, "Error: control was passed to 0x%04x (%d)\n",
              register_PC, register_PC);
            fclose(errfile);
          }
        }
        exit(1); /* Illegal instruction */
  }
#endif

  exit(0);
  return(0);
}