#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef OPENGL
#ifdef RASPBERRY_PI
#include <GL/gl.h>
#include <GL/glut.h>
#else
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#endif
#include <time.h>   // for clock
#endif

#ifdef USE_VECTERM
#include <stdbool.h>
#include <pthread.h>
#include "../vecterm/vecterm_communication.h"

static framegen_data *init_framegen_data(void) {
    static framegen_data frame;
    frame.options.use_ay_regs           = false;
    frame.options.use_led               = false;
    frame.options.use_led_dimmed        = false;
    frame.options.use_points            = false;

    frame.options.screen_swap_x_y       = false;
    frame.options.screen_flip_x         = false;
    frame.options.screen_flip_y         = false;
    frame.options.aspect_ratio_adjust   = false;
    switch (vecterm_orientation()) {
        case 3:
            frame.options.screen_swap_x_y       = true;
            frame.options.screen_flip_x         = true;
            frame.options.aspect_ratio_adjust   = true;
            break;
        case 2:
            frame.options.screen_flip_x         = true;
            frame.options.screen_flip_y         = true;
            break;
        case 1:
            break;
        default:
            frame.options.screen_swap_x_y       = true;
            frame.options.screen_flip_y         = true;
            frame.options.aspect_ratio_adjust   = true;
            break;
    }
    frame.options.aspect_ratio          = 3.0/5.0;
    
    frame.options.use_opt_1             = true;
    frame.options.use_opt_2             = true;
    frame.options.use_opt_point_offset  = false;
    frame.options.use_opt_point_char_detect  = false;
    frame.options.continue_distance     = 1;
    frame.options.point_size            = 8;
    
    frame.measurement_flags             = VECTERM_MEASUREMENT_FLAGS_BUTTONS  | VECTERM_MEASUREMENT_FLAGS_DIGITAL_X | VECTERM_MEASUREMENT_FLAGS_DIGITAL_Y;
    
    static float tmp_mem[5*16*1024];
    frame.max_line_count = 16*1024;
    frame.ptr = tmp_mem;
    return (&frame);
}

framegen_data *frame;
#endif

#ifdef OPENGL
#define COLORS 16
GLubyte colors[COLORS][3] = {
  {0,0,0},
  {1,1,1},
  {3,3,3},
  {7,7,7},
  {15,15,15},
  {31,31,31},
  {63,63,63},
  {127,127,127},
  {0+128,0+128,0+128},
  {1+128,1+128,1+128},
  {3+128,3+128,3+128},
  {7+128,7+128,7+128},
  {15+128,15+128,15+128},
  {31+128,31+128,31+128},
  {63+128,63+128,63+128},
  {127+128,127+128,127+128},
};
#endif

#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 MinX = 1000, MinY = 1000, MaxX = 0, MaxY = 0;

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

void
CinemaClearScreen (void)
{
  static long Frames = 0L;
  int mx = 0, my = 0;
  int tmp_btns;
  
  mousecode = 0;

#ifdef NEVER
  get_mouse_mickeys (&mickeyx, &mickeyy);
  mx = mickeyx;
  my = mickeyy;
#endif

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

   */


  tmp_btns = vecterm_get_buttons(); // READ ONCE PER FRAME

  // digital for now.  May switch to analog later
  mx = vecterm_get_joystick_digital_x_controller_0();
  my = -vecterm_get_joystick_digital_y_controller_0();

  mousecode |= IO_LEFT;
  mousecode |= IO_RIGHT;

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

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

  if (tmp_btns & (VECTERM_BUTTON_0_2 | VECTERM_BUTTON_1_2)) startflag = 0; else startflag = IO_START;
  if (tmp_btns & (VECTERM_BUTTON_0_3 | VECTERM_BUTTON_1_3)) shieldsflag &= ~IO_SHIELDS; else shieldsflag |= IO_SHIELDS;
  if (tmp_btns & (VECTERM_BUTTON_0_4 | VECTERM_BUTTON_1_4)) fireflag &= ~IO_FIRE; else fireflag |= IO_FIRE;


  // unfortunately coining is happening on *release* of button 1 rather than on press of it.  Fix later.
  if ((tmp_btns & (VECTERM_BUTTON_0_1 | VECTERM_BUTTON_1_1)) == 0) {
    if ((tmp_btns & (VECTERM_BUTTON_0_1 | VECTERM_BUTTON_1_1)) != 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 = tmp_btns & (VECTERM_BUTTON_0_1 | VECTERM_BUTTON_1_1);
  

  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(); */

#ifdef DOUBLE_BUFFERING
  blit (buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
  clear (buffer);
#endif

#ifdef OPENGL
  //  fprintf (stdout, "Clear: %ld [bb: (%ld,%ld) (%ld,%ld)]\n", Frames++,
  //    MinX, MinY, MaxX, MaxY);
#endif
}

void
CinemaVectorData (int FromX, int FromY, int ToX, int ToY, int vgColour)
{
#ifdef USE_VECTERM
    if (frame->line_count < frame->max_line_count) {
//        printf("FromX %i, FromY %i, ToX %i, ToY %i, col %i\n",FromX,FromY,ToX,ToY,vgColour);
        float *fptr = frame->ptr + 5*frame->line_count++;
        fptr[0] = (float)FromX/(float)SCREEN_W;
        fptr[1] = 1.0-(float)FromY/(float)SCREEN_H;
        fptr[2] = (float)ToX/(float)SCREEN_W;
        fptr[3] = 1.0-(float)ToY/(float)SCREEN_H;
        fptr[4] = (float)(vgColour&15)/15.0;
    }
#endif

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

#ifdef NEVER
  FromX = (FromX * 2) / 3;
  ToX = (ToX * 2) / 3;
  FromY = (FromY * 2) / 3;
  ToY = (ToY * 2) / 3;
  FromX -= 32;
  ToX -= 32;
  FromY = SCREEN_H - FromY;
  ToY = SCREEN_H - ToY;		/* Y in inverted on the PC */
#endif

#ifdef NEVER
  /* Probably all one intensity on the Palm for now */
  /* HACK */ if (vgColour & 0x8)
    vgColour = 0xf;
  else
    vgColour = 0x7;
#endif

ToX = ToX << 16;
ToX = ToX >> 16;
FromX = FromX << 16;
FromX = FromX >> 16;

  if (FromX < MinX)
    MinX = FromX;
  if (ToX < MinX)
    MinX = ToX;
  if (FromY < MinY)
    MinY = FromY;
  if (ToY < MinY)
    MinY = ToY;
  if (FromX > MaxX)
    MaxX = FromX;
  if (ToX > MaxX)
    MaxX = ToX;
  if (FromY > MaxY)
    MaxY = FromY;
  if (ToY > MaxY)
    MaxY = ToY;

#ifdef OPENGL
  if ((FromX == ToX) && (FromY == ToY)) {
    //    fprintf (stdout, "Point: %d %d (%d)\n", FromX, FromY, vgColour);
    glColor3ubv(colors[vgColour % COLORS]);
    glVertex2i(FromX, FromY);
    glVertex2i(ToX+1, ToY+1);
  } else {
    //    fprintf (stdout, "Line: %d %d -> %d %d (%d)\n", FromX, FromY, ToX, ToY, vgColour);
    glColor3ubv(colors[vgColour % COLORS]);
    glVertex2i(FromX, FromY);
    glVertex2i(ToX, ToY);
  }
#endif
}

#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 cineExecute (void);


void
cineExecuteFrame (void)
{
#ifdef USE_VECTERM
    frame->line_count = 0;
#endif
    bNewFrame = 0;
    do cineExecute(); while (bNewFrame != 1);
}

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


/* #define SCREEN_SAVER_MODE */

void
reshape(int width, int height)
{
#ifdef OPENGL
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, width, 0, height, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glEnable(GL_BLEND);
    glEnable(GL_LINE_SMOOTH);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
}

void
display(void)
{
    int i;
    static int color = 0;
    static int odd = 0;
    
    odd = (odd+1)&15;
    if (odd == 0) {
#ifdef OPENGL
        glClear(GL_COLOR_BUFFER_BIT);
        
        glBegin(GL_LINES);
#endif
        cineExecuteFrame ();
#ifdef OPENGL
        glEnd();
        
        glutSwapBuffers();
#endif
	
#ifdef USE_VECTERM
        vecterm_new_frame(frame);
#endif
    }
}

void
idle(void)
{
#ifdef OPENGL
  glutPostRedisplay();
#endif
}

void
bail(int code)
{
    exit(code);
}

#ifdef SCREEN_SAVER_MODE
void
ss_keyboard(char key, int x, int y)
{
    bail(0);
}

void
ss_mouse(int button, int state, int x, int y)
{
    bail(0);
}

void
ss_passive(int x, int y)
{
    static int been_here = 0;

    /* for some reason, GLUT sends an initial passive motion callback
       when a window is initialized, so this would immediately
       terminate the program.  to get around this, see if we've been
       here before. (actually if we've been here twice.) */

    if (been_here > 1)
	bail(0);
    been_here++;
}

#else

void
keyboard(unsigned char key, int x, int y)
{
    static int old_x = 50;
    static int old_y = 50;
    static int old_width = SCREEN_W;
    static int old_height = SCREEN_H;

    if (key == 27) {
#ifdef USE_VECTERM
        vecterm_close_connection();
#endif
        bail(0);
    } else if (key == 'w') {
#ifdef OPENGL
        glutPositionWindow(old_x, old_y);
        glutReshapeWindow(old_width, old_height);
#endif
    } else if (key == 'f') {
#ifdef OPENGL
        if (glutGet(GLUT_WINDOW_WIDTH) != glutGet(GLUT_SCREEN_WIDTH)) {
	    old_x = glutGet(GLUT_WINDOW_X);
	    old_y = glutGet(GLUT_WINDOW_Y);
	    old_width = glutGet(GLUT_WINDOW_WIDTH);
	    old_height = glutGet(GLUT_WINDOW_HEIGHT);
	    glutFullScreen();
	}
#endif
    } else if (key == '3') {
      ioSwitches &= ~IO_COIN; /* Clear coin counter the bodgy way */
      coinflag = 0;
    } else if (key == '1') {
      startflag = 0;
      debounce_oneshots = 4;
    } else if (key == '\r') {
      fireflag &= ~IO_FIRE;
      debounce_oneshots = 2;
    } else if ((key == '\t') || (key == ' ')) {
      shieldsflag &= ~IO_SHIELDS;
      if (debounce_shields <= 1) debounce_shields = 16;
    }
  /*
     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 * /

   */
}
#endif

int
main(int argc, char** argv)
{
    int i;

    //    printf(" sizeof(int) %i\n", sizeof(int));
    //    printf(" sizeof(float) %i\n", sizeof(float));

    //    exit(0);
    
#ifdef USE_VECTERM
    if (!vecterm_open_connection(0)) {
        printf("error: opening vecterm connection failed\n");
        return (-1);
    }
    frame = init_framegen_data();
#endif

    initTailGunner ();

#ifdef OPENGL
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(50, 50);
    glutInitWindowSize(SCREEN_W, SCREEN_H);
    glutInit(&argc, argv);

    glutCreateWindow("Tailgunner");
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
#ifdef SCREEN_SAVER_MODE
    glutPassiveMotionFunc(ss_passive);
    glutKeyboardFunc(ss_keyboard);
    glutMouseFunc(ss_mouse);
    glutSetCursor(GLUT_CURSOR_NONE);
    glutFullScreen(); 
#else
    glutKeyboardFunc(keyboard);
#endif

    glutIdleFunc(idle);
    glutMainLoop();
#else // not OPENGL
    // alternative to glutMainLoop
    {
      int rc;
      clock_t last, now, next; // TO DO: Handle wrap in 71 minutes
      struct timespec ts;

      for (;;) {
        last = clock();
        next = last + (clock_t)CLOCKS_PER_SEC/(clock_t)50;
        display();
        now = clock();
        ts.tv_sec = 0; ts.tv_nsec = (next - now)*((clock_t)100000000/(clock_t)CLOCKS_PER_SEC);
        if (ts.tv_nsec > 0) {
          rc = nanosleep (&ts, NULL);
          if (rc != 0) {
            fprintf(stdout, "%ld -> %d\n", ts.tv_nsec, rc);
          }
        }
      }
    }
#endif
    return 0;
}
//#endif
