#define VECTREX 1

#ifdef VECTREX
#include <vectrex.h>
#else
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define Reset0Ref()
#define Draw_Line_d(y,x)
#define Moveto_d(y,x)
#define Draw_VLc(x)
#endif
#define GRIDLOW -64
#define GRIDHIGH 63
#define PGRIDSCALE 255U
#define XYGRIDSCALE 200U
#define ABSSCALE 127U
#define MY_Y GRIDLOW
#define MEANIE_Y = GRIDHIGH // for now

static inline void set_scale(unsigned int s) {
#ifdef VECTREX
  // this should not be faster, but it is. The write to the VIA must be slow.
  static unsigned int last_scale = 0;
  if (last_scale != s) {
    VIA_t1_cnt_lo = last_scale = s;
  }
#endif
}

int gridview = 0; // view without perspective when 1.
unsigned int gridscale;
int SX=0, SY=0; // screen X,Y - actual beam pos

static inline void swap (int *a, int *b) {
  int t = *a; *a = *b; *b = t;
}

#ifdef VECTREX
static const int perspectiveY[256] = {
 -107, -107, -107, -107, -107, -106, -106, -106, -106, -105, -105, -105, -105, -105, -104, -104,
 -104, -104, -103, -103, -103, -103, -102, -102, -102, -102, -101, -101, -101, -101, -100, -100,
 -100, -100, -99, -99, -99, -98, -98, -98, -98, -97, -97, -97, -96, -96, -96, -95,
 -95, -95, -95, -94, -94, -94, -93, -93, -92, -92, -92, -91, -91, -91, -90, -90,
 -90, -89, -89, -88, -88, -88, -87, -87, -86, -86, -86, -85, -85, -84, -84, -84,
 -83, -83, -82, -82, -81, -81, -80, -80, -79, -79, -78, -78, -77, -77, -76, -76,
 -75, -75, -74, -74, -73, -73, -72, -72, -71, -71, -70, -69, -69, -68, -68, -67,
 -66, -66, -65, -65, -64, -63, -63, -62, -61, -61, -60, -59, -59, -58, -57, -57,
 -56, -55, -54, -54, -53, -52, -51, -51, -50, -49, -48, -48, -47, -46, -45, -44,
 -43, -43, -42, -41, -40, -39, -38, -37, -36, -36, -35, -34, -33, -32, -31, -30,
 -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -18, -17, -16, -15, -14, -13,
 -12, -11, -9, -8, -7, -6, -5, -3, -2, -1, 0, 2, 3, 4, 6, 7,
 8, 10, 11, 13, 14, 15, 17, 18, 20, 21, 23, 24, 26, 27, 29, 30,
 32, 34, 35, 37, 39, 40, 42, 44, 45, 47, 49, 51, 52, 54, 56, 58,
 60, 62, 64, 65, 67, 69, 71, 73, 75, 77, 79, 82, 84, 86, 88, 90,
 92, 95, 97, 99, 101, 104, 106, 108, 111, 113, 116, 118, 120, 123, 125, 127,
};
void init_perspective(void) {
}

#else
double perspectiveY[256L]; // map Y=0:255 to Y'=n:255

void init_perspective(void) {
  long mapsto, step, Y, UY;
  // initialise perspective table.  Can be done in rom.  Needs calc using 'e' :-/
  //fprintf(stdout, "Setting p[127] to 255\n");
  perspectiveY[255] = (double)255;
  for (Y = 254L; Y >= 0L; Y--) { // top wide, bottom narrow. 16 passes
    UY=Y-128;
    perspectiveY[Y] = perspectiveY[Y+1]*0.99;
    //fprintf(stdout, "Setting p[%d] to %d\n", UY, (int)perspectiveY[Y]);
  }
  for (Y = 0L; Y < 256L; Y++) { // top wide, bottom narrow. 16 passes
    perspectiveY[Y] = round(perspectiveY[Y]);
  }
  for (Y = 0L; Y < 256L; Y++) { // top wide, bottom narrow. 16 passes
    perspectiveY[Y] = -(255-perspectiveY[Y] - 128);
  }
  perspectiveY[255]=127;

  for (Y = -128L; Y < 128L; Y++) { // top wide, bottom narrow. 16 passes
    fprintf(stdout, " %d,", (int)(signed char)(perspectiveY[((Y+128)&255)]));
    if (((Y+128)&15) == 15) fprintf(stdout, "\n");
  }
  exit(0);
}
#endif

static inline int PY(int Y) {
  if (gridview) return Y;
  return -perspectiveY[255-(Y+128)]-32; // -32 is just to move it down the screen
  // I should have built the '-' and '255-' into the table. Too lazy to go back and redo it.
}

static inline int PX(int X, int Y) {
  if (gridview) return X;
  typedef union {
    struct {
      unsigned int high;
      unsigned int low;
    } byte;
    struct {
      unsigned long val;
    } word;
  } var;
  var prod;
  unsigned int LY = 255U-(unsigned int)(128+PY(Y)); // convert Y from -128:127 to 0:255
  prod.word.val = (long unsigned int)X * (long unsigned int)LY;
  return (int)(prod.byte.high); // what we do to avoid >>8 ... :-( btw <<1 causes overflow}
}

// These are in grid space but draw in perspective:
static inline void MoveAbsXY(int x, int y) {
  Reset0Ref();
  // SX = SY = 0;
  //Moveto_d(y-SY, x-SX);
  Moveto_d(y, x);
  SX = x; SY = y;
}

static inline void PerspectiveMove(int x, int y) {
  set_scale(gridscale);
  MoveAbsXY(PX(x,y),PY(y));
}

static inline void LineAbsXY(int x, int y) {
  Draw_Line_d(y-SY, x-SX); // sizes are carefully chosen so this never overflows
  SX = x; SY = y;
}

static inline void PerspectiveLine(int x, int y) {
  set_scale(gridscale);
  LineAbsXY(PX(x,y),PY(y));
}

#define MAX_MEANIES 3
static int
  meanie_x[MAX_MEANIES] = {-48, 0, 48},
  meanie_y[MAX_MEANIES] = {0, -50, -16},
  meanie_dx[MAX_MEANIES] = { 2, 0, 0},
  meanie_dy[MAX_MEANIES] = {0, 2, 4};

static int my_x = 0;

static inline void draw_me(void) {
  static int Me[] = {4, -9,0, 9,-9, 9,9, -9,9, -9,-9};
  PerspectiveMove(my_x,GRIDLOW); 
  set_scale(0x7f);
  Draw_VLc(Me);
}

static int missile_x, missile_y;
static int firing = 0;

static inline void draw_missile(void) {
  static int Missile[] = {4, -9,0, 9,-9, 9,9, -9,9, -9,-9};
  PerspectiveMove(missile_x,missile_y);
  set_scale((unsigned int)PX(0x7f, missile_y)); // hacky way to apply perspective
  //set_scale((unsigned int)PY(missile_y));
  Draw_VLc(Missile);
  missile_y += 8;
  if (missile_y >= GRIDHIGH) firing = 0; // also when it hits a target...
}

static inline void draw_meanie(int x, int y) {
  static int Meanie[] = {4, -3,0, 3,-3, 3,3, -3,3, -3,-3};
  PerspectiveMove(x,y);
  set_scale((unsigned int)PX(0x7f, y)<<1); // hacky way to apply perspective 
  //set_scale((unsigned int)PY(y)<<1);
  Draw_VLc(Meanie);
}

static inline void draw_meanies(void) {
  int m; // assumes all on screen all the time.
  for (m = 0; m < MAX_MEANIES; m++) {
//    if (((meanie_y[m] > GRIDLOW)) && ((meanie_y[m] < GRIDHIGH))) {
      draw_meanie(meanie_x[m], meanie_y[m]);
//    }
  }
}

static void draw_moving_grid(int depth) {
  int row, col;
    // adding these two lines not only costs cycles we can no longer afford,
    // they also mess up the display!  Not sure why.
    //Print_Str_d(127, -60, "BEAM RIDER\x80");
    //Print_Str_d(-127, -120, "L 1 R 2 VIEW 3 FIRE 4\x80");
    PerspectiveMove(0, GRIDHIGH);
    Moveto_d(0, 64);  // Horizon
    Draw_Line_d(0, -128);  // Horizon
    for (col = -48; col <= 48; col += 16) {
      PerspectiveMove(col, GRIDLOW); // as long as gridhigh-gridlow <= 127
      PerspectiveLine(col, GRIDHIGH); // draw vertical grid component
    }
    for (row = depth+32; row >= depth-64; row -= 32) {
       // depth is the small delta movement between gaps
       PerspectiveMove(-48, row); PerspectiveLine(48, row); // draw horizontal grid component
    }
}

int main(void) {
  int left=0, right=0, fire=0;
  int direction=0;
  int granularity = 1; // grid speed: can increase up to 4 as more speed needed later in game
  int depth = 0, actiontick = 0, slowmode = 1;
  // set slowmode to 0 when player reaches basic score that
  // shows he knows how the game works...
  init_perspective();
  for (;;) {
#ifdef VECTREX
    Wait_Recal();
    Intensity_7F();

    // poll buttons. Old code. Should use library calls now.
    asm("pshs x,y,u,dp");
    asm("lda  #0");
    asm("jsr  0HF1B4 ; read_btns_mask":::"a","b","d");
    asm("puls dp,u,y,x");
    left = (*(volatile int *)(0xC80F)) & 1;
    right = (*(volatile int *)(0xC80F)) & 2;
    gridview = (*(volatile int *)(0xC80F)) & 4; // debugging aid
    gridscale = gridview ? XYGRIDSCALE : PGRIDSCALE; // whenever gridview changes...
    fire = (*(volatile int *)(0xC80F)) & 8;
#endif

    if (left && !right) {
      if (my_x > -48) my_x -= 2; direction = -2;
    } else if (right && !left) {
      if (my_x < 48) my_x += 2; direction = 2;
    }
    if (my_x & 15) my_x += direction; // make sure it parks on a beam when you release

    if (fire && !firing) {
      missile_x = (int)(my_x+8)&(int)0xf0; missile_y = GRIDLOW;
      firing = 1;
    }
    if (firing) draw_missile();
    draw_moving_grid(depth);
    draw_meanies();
    draw_me();
    if (actiontick == 0) {
      // update game state - enemy AI...
      int i;
      meanie_x[0] += meanie_dx[0];
      if (meanie_x[0] == 64-16) meanie_dx[0] = -meanie_dx[0];
      if (meanie_x[0] == -64+16) meanie_dx[0] = -meanie_dx[0];
      for (i = 0; i < MAX_MEANIES; i++) {
        meanie_y[i] -= granularity+meanie_dy[i];
        if (meanie_y[i] <= -64) meanie_y[i] = 64;
      }
      depth -= granularity;
      if (depth <= 0) depth = 32;
    }
    if (slowmode) actiontick = (actiontick + 1)&1;
  }
  return 0;
}