#include "gp32.h"
#include "gpstdio.h"
#include "gpgraphic.h"

#define FRAMEBUFFER_SIZE (320*240*2/2)

GPDRAWSURFACE surface1, surface2, *surface;
static long gpb = 0, gpe = 0;

static void SlowSetPixel(int x, int y)
{
   unsigned char *p;
   x ^= 7; // funny screen layout
   p = &surface->ptbuffer[(y*240+x)>>1];
   *p = *p | (15 << ((x&1)<<2)); // draw in colour 15 (brightest, when we add grey scale)
}


// I have faster algorithms than this - even an anti-aliased one - but
// this is rock-solid reliable code that can be trusted during the debugging phase
// (and also it appears to be fast enough already!)

static void SlowDrawLine(int y1,int x1,int y2, int x2)
{
   int temp,dx,dy,x,y,x_sign,y_sign,flag;
   dx=abs(x2-x1); // Delta of X
   dy=abs(y2-y1); // Delta of Y
   if (((dx >=dy) && (x1>x2)) || // Make sure that first coordinate
   ((dy>dx) && (y1>y2)))         // is the one with least value
   {
      temp=x1;
      x1=x2;
      x2=temp;
      temp=y1;
      y1=y2;
      y2=temp;

   };

   if ((y2-y1)<0) y_sign=-1; // The direction into which Y-coord shall travel
   else y_sign=1;              // Same for X

   if ((x2-x1)<0) x_sign=-1; // ---- " ----
   else x_sign=1;              // ---- " ----

   if (dx>=dy) // Which one of the deltas is the greatest one
   {
      for (x=x1,y=y1,flag=0;x<=x2;x++,flag+=dy) // From x1 to x2
      {                                           // Also increase the
         if (flag>=dx) // Increase/decrease     // flag (displacement value)
         {               // y!
            flag-=dx;
            y+=y_sign;

         };
         SlowSetPixel(x,y); // Plot the pixel
      };
   }
   else
   {
      /* This is the same as above, just with x as y and vice versa.*/
      for (x=x1,y=y1,flag=0;y<=y2;y++,flag+=dx)
      {
         if (flag>=dy)
         {
            flag-=dy;
            x+=x_sign;

         };
         SlowSetPixel(x,y);
      };
   };
}


#include "initval_port.h"

int GpPredefinedStackGet (H_THREAD th)
   {
   switch (th)
      {
      case H_THREAD_GPMAIN:
         return GPMAIN_STACK_SIZE;
      case H_THREAD_NET:
         return NET_STACK_SIZE;
      case H_THREAD_TMR0:
      case H_THREAD_TMR1:
      case H_THREAD_TMR2:
      case H_THREAD_TMR3:
         return USER_STACK_SIZE;
      default:
         return 0;
      }
   }


static unsigned char *framebuf;
static unsigned char *framebuffer_strt;
static unsigned char *framebuffer_stop;


void DisplayBuffer(GPDRAWSURFACE *surface) {
	long ptr = (long)surface->ptbuffer;

	rLCDSADDR1 = ((ptr & 0x0fc00000) >> 1) | ((ptr & 0x3ffffe) >> 1);
	rLCDSADDR2 = ((ptr & 0x003ffffe) >> 1) + (320) * (60) ;
}

//

inline void waitline( int line ) {
	while (line != (rLCDCON1 >> 18));
}

void waitVACTIVE( void ) {
	//while (line != ((rLCDCON1 >> 18) & 0x1ff) );
	while ((rLCDCON5 >> 19) == 0x02 );
}


#ifndef FALSE
#define FALSE (0!=0)
#define TRUE (0==0)
#endif

#define SCREEN_H 800
#define SCREEN_W 1023

#include "externs.h"
#include "mdep.h"
#include "mdep.c"

static int debounce_oneshots = 0, debounce_shields = 0, debounce_coin = -1;

void
CinemaClearScreen (void)
{
  static long Frames = 0L;

  mousecode = 0;

  mousecode |= IO_LEFT;
  mousecode |= IO_RIGHT;

  if ((gpb & rKEY_RIGHT) == 0)
    mousecode &= ~IO_RIGHT;

  if ((gpb & rKEY_LEFT) == 0)
    mousecode &= ~IO_LEFT;

  // don't ask me why these are all reversed - crazy system!
  if ((gpb & rKEY_DOWN) == 0)
    mousecode |= IO_UP;
  else
    mousecode &= ~IO_UP;

  if ((gpb & rKEY_UP) == 0)
    mousecode |= IO_DOWN;
  else
    mousecode &= ~IO_DOWN;

  /*
     if (... fire button is pressed ...) fireflag &= ~IO_FIRE; else fireflag |= IO_FIRE;
     if (... shields button is pressed ...) shieldsflag &= ~IO_SHIELDS; else shieldsflag |= IO_SHIELDS;

     If the start button is pressed, set "startflag = 0;" here
     If the coin button is pressed, set 
     ioSwitches &= ~IO_COIN; / * Clear coin counter the bodgy way * /
     coinflag = 0;
     if the shields button is pressed, set "shieldsflag = IO_SHIELDS;"

     HOWEVER if *nothing* is pressed,       

     startflag = IO_START; / * Bodge.  Clear start button. Allows it to be down for 1/50sec * /

   */

  // NOTE: AT THIS STAGE, THERE IS NO DEBOUNCING, SO ONE PRESS OF THE COIN GIVES 9 LIVES!
  if ((gpe & rKEY_START) == 0) startflag = 0; else startflag = IO_START;
  if ((gpb & rKEY_B) == 0) shieldsflag &= ~IO_SHIELDS; else shieldsflag |= IO_SHIELDS;
  if ((gpb & rKEY_A) == 0) fireflag &= ~IO_FIRE; else fireflag |= IO_FIRE;

  
  if ((gpe & rKEY_SELECT) == 0) {
     if ((gpe & rKEY_SELECT) != debounce_coin) {
        ioSwitches &= ~IO_COIN; coinflag = 0;
     } else {
        ioSwitches |= IO_COIN; coinflag = IO_COIN;  // only on down-press, for 1 frame
     }
  } else {
     ioSwitches |= IO_COIN; coinflag = IO_COIN;  // only on down-press, for 1 frame
  }
  debounce_coin = gpe & rKEY_SELECT;


  if (debounce_oneshots > 0) {
    debounce_oneshots -= 1;
    if (debounce_oneshots == 0) {
      startflag = IO_START; fireflag |= IO_FIRE;
    }
  }

  if (debounce_shields > 0) {
    debounce_shields -= 1;
    if (debounce_shields == 0) {
      shieldsflag |= IO_SHIELDS;
    }
  }

  ioInputs =
    (unsigned short) (mousecode | fireflag | shieldsflag | startflag);

  /* CAN WE DO SOMETHING LIKE: vsync(); */
}

void
CinemaVectorData (int FromX, int FromY, int ToX, int ToY, int vgColour)
{

  /* TWEAK THESE TO MAKE THEM FIT... */

  // scale 1024x768 to 340x240, and rotate
  FromX = FromX / 3;
  ToX = ToX / 3;
  FromY = FromY / 3;
  ToY = ToY / 3;
  if (FromX < 0) FromX = 0;
  if (FromY < 0) FromY = 0;
  if (ToY >= 240) ToY = 239;
  if (ToX >= 320) ToX = 319;
  if (ToX < 0) ToX = 0;
  if (ToY < 0) ToY = 0;
  if (FromY >= 240) FromY = 239;
  if (FromX >= 320) FromX = 319;

  // sign extend short to long
  ToX = ToX << 16;
  ToX = ToX >> 16;
  FromX = FromX << 16;
  FromX = FromX >> 16;

  SlowDrawLine(FromX, FromY, ToX, ToY);
}

#define UNFINISHED(s)		/* Do Nothing */

/* 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;
}

/* main interpreter body */

extern void cineExecute0000 (void);
extern void cineExecute0400 (void);
extern void cineExecute0800 (void);
extern void cineExecute0c00 (void);
extern void cineExecute1000 (void);
extern void cineExecute1400 (void);
extern void cineExecute1800 (void);
extern void cineExecute1c00 (void);


void
cineExecuteFrame (void)
{
  bNewFrame = 0;
  for (;;)
    {
      switch ((register_PC >> 10))
	{
	case 0:
	  cineExecute0000 ();
	  break;
	case 1:
	  cineExecute0400 ();
	  break;
	case 2:
	  cineExecute0800 ();
	  break;
	case 3:
	  cineExecute0c00 ();
	  break;
	case 4:
	  cineExecute1000 ();
	  break;
	case 5:
	  cineExecute1400 ();
	  break;
	case 6:
	  cineExecute1800 ();
	  break;
	case 7:
	  cineExecute1c00 ();
	  break;
	}
      if (bNewFrame == 1)
	return;
    }
}

void
initTailGunner (void)
{
  int i;
  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 &= (unsigned short) ~SW_QUARTERS_PER_GAME;	/* One quarter per game */
  ioSwitches |= (unsigned short) quarterflag;

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

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

  for (i = 0; i < 256; i++)
    ram[i] = 0;
  return;
}

static unsigned char localbuf[FRAMEBUFFER_SIZE+4096];  // SCREEN BUFFER!

void GpMain (void *arg) {
   long ptr;
   int  n;

   long *pal;

   // copy palette..
   
   rLCDCON1 &= ~1;
   pal = (long *)PALETTE;

   // set every colour to white except the background
   pal[0] = 0x31<<1;  //  so why is the background still black?  it shouldn't be!
   for (n = 1; n < 16; n++) {
   	pal[n] = 0xfffe;  // max white, for now
   }
        
   // Ensure 4KB alignment..
   framebuf = (unsigned char*)((((long)localbuf)+0xfffL) & 0xfffff000L);

   // appears to be 320x240x4 mode despite comment below about 16bit mode

   surface1.ptbuffer = framebuf;
   surface1.bpp = 4;
   surface1.buf_w = 320;
   surface1.buf_h = 240;
   surface1.ox = 0;
   surface1.oy = 0;
   surface1.o_buffer = NULL; // WHAT IS THIS???

   surface2.ptbuffer = framebuf + (FRAMEBUFFER_SIZE >> 1);
   surface2.bpp = 4;
   surface2.buf_w = 320;
   surface2.buf_h = 240;
   surface2.ox = 0;
   surface2.oy = 0;
   surface2.o_buffer = NULL;

   if (framebuf == NULL) {
   	// apparently when I used gp_malloc it *was* null.  Hence the local static now
   	GpAppExit();
   }
        
   framebuffer_strt = framebuf;
   framebuffer_stop = framebuffer_strt + FRAMEBUFFER_SIZE;
        
   ARMDisableInterrupt();

   // Do the MMU mapping.. remember 4KB pages!
   //   for the mmu flags: 0xff2 -> no cache, no writeback

   swi_mmu_change(framebuffer_strt,framebuffer_stop-1,0x00000ff2);

   // 320x240x16 screen mode, 0 pixel offscreen - this comment appears to be wrong...
   // expects 33MHz HCLK

   rLCDCON1 = ((3 << 8) | (0 << 7) | (3 << 5) | (10 << 1) | (0));
        // CLKVAL=3, VNMODE=0, PNRMODE=TFT, BPPMODE=1000 (4bits), ENDID=0

   rLCDCON2 = ((1 << 24) | (319 << 14) | (2 << 6) | 1);
        // VBPD=1, LINEVAL=319, VFPD=2, VSPW=1

   rLCDCON3 = ((6 << 19) | (239 << 8) | (2));
        // HBPD=6, HOZVAL=239, HFPD=2

   rLCDCON4 = ((1 << 24) | (0 << 16) | (4));
        // PALADDEN=0, ADDVAL=0, HSPW=4

   rLCDCON5 = ((1 << 10) | (1 << 9) | (1 << 8) | (0 << 7) | (0 << 6) |
               (0 << 4) | (1 << 2) | (0));
        // INVVCLK=1, INVVLINE=1, INVVFRAME=1, INVVD=0, INVVDEN=0
        // INVENDLINE=0, ENLEND=0, BSWP=0, HWSWP=1

   rTPAL = 0;

   DisplayBuffer(&surface2); // dispay surface 2 initially

   rLCDSADDR3 = ((0 << 11) | (60));
   // OFFSIZE=0, PAGEWIDTH=60 halfwords -> 240 pix in 4bpp
   rLCDCON1 |= 1;	// enable lcd

        n = 0;
   gpb = rPBDAT;	// 0x156
   gpe = rPEDAT;

   waitline(2);
   waitline(1);

   surface = &surface1;  // allow writing to surface one just in case init does any
   initTailGunner();

   // while ((gpb & 0x9000) != 0) { -- what would this do???

   for (;;) {
       // surface 2 is currently being displayed
       gpb = rPBDAT;        // Note all buttons
       gpe = rPEDAT;

       surface = &surface1; // draw to surface1
       {int xy = 0; for (xy = 0; xy < 320*240/2; xy++) surface->ptbuffer[xy] = 0; }  // wipe

       cineExecuteFrame();

       waitline(2);
       waitline(1);
       DisplayBuffer(&surface1);

       while ((gpb & rKEY_L) == 0) { // pause on surface 1 if select pressed
          gpb = rPBDAT;
          /* do nothing */;
       }

       // surface 1 is now being displayed
       surface = &surface2;  // so draw to surface 2
       {int xy = 0; for (xy = 0; xy < 320*240/2; xy++) surface->ptbuffer[xy] = 0; }

       cineExecuteFrame();

       waitline(2);
       waitline(1);
       DisplayBuffer(&surface2);

       while ((gpb & rKEY_R) == 0) {  // pause on surface 2 if start pressed
          gpb = rPBDAT;
          /* do nothing */;
       }

    }
}


int main(int argc, char **argv)
{
  GpMain(NULL);
  return(0);
}


