
typedef enum display {
  MONO,
  STEREO,
  CROSSEYED,
  WALLEYED
} display;
static display DispType;

#define USE_PRECALC 1

//#define DEBUG 1
#ifdef DEBUG
#include <stdio.h>
#include <stdlib.h>
#define int8_t char
// undef PRECALC to generate perspective table
static int sx, sy;
#define set_scale(x)
void Reset0Ref(void){ fprintf(stderr, "Reset0Ref();  // sx=sy=0\n"); sx=sy=0; }
void Moveto_d(int dy, int dx) { sx+=dx; sy+=dy; fprintf(stderr, "Moveto_d(%d,%d); // sx+=%d sy+= %d   sx=%d sy=%d\n", dy,dx, dx,dy, sx, sy); }
void Intensity_a(int i) {}
void Draw_Line_d(int dy, int dx) { sx+=dx; sy+=dy; fprintf(stderr, "Draw_Line_d(%d,%d); // sx+=%d sy+=%d   sx=%d sy=%d\n", dy,dx, dx,dy, sx, sy); }
void Wait_Recal(void) {
  static int loops = 0;
  loops++;
  if (loops == 3) exit(0);
}
#else
#include <vectrex.h>
#include "controller.h"

#define int8_t int
#define set_scale(s) do { VIA_t1_cnt_lo = s; } while (0)
#endif

#define SCALED

typedef struct screenxy {
  int8_t xleft, xright, y;
} screenxy;

static unsigned int8_t _x, _a, _b, _c;

static void init_random(unsigned int s1,unsigned int s2,unsigned int s3, unsigned int x0) {
  _x = x0; _a = s1; _b = s2; _c = s3; _x++; _a = (_a^_c^_x); _b = (_b+_a); _c = ((_c+(_b>>1))^_a);
}

static unsigned int8_t random(void) { // assert returns unsigned value that fits in an int.
  _x++; _a = (_a^_c^_x); _b = (_b+_a); _c = ((_c+(_b>>1))^_a);
  return _c;
}

#ifdef USE_PRECALC
#include "perspective.h"
#else
static screenxy screen[16][16][16], screen_point; // Screen is [z][y][x]
#endif

// Storing an (X,Y,Z) point using 3 X 4-bit nybbles uses half the space that
// using ints would do, but adds a few thousand cycles to the runtime which is unacceptable.
//#define NIBBLE :4
#define NIBBLE

typedef struct lines {
  unsigned int x0 NIBBLE, x1 NIBBLE;
  unsigned int y0 NIBBLE, y1 NIBBLE;
  unsigned int z0 NIBBLE, z1 NIBBLE;
} lines;
#define MAX_LINE_SEGMENTS 100  // 100 lines is way more than we can comfortably draw at the moment.
static lines line[MAX_LINE_SEGMENTS]; // 600 bytes if ints, 300 if nibbles
static unsigned int8_t next_free_line = 0;

#ifndef USE_PRECALC
// Calculate the perpective table but only on Linux.
static int8_t perspective_x(const int left, const unsigned int8_t x, const unsigned int8_t z) {
  // Can use full 32-bit arithmetic here because this is never executed on the Vectrex.
  // X' = ((X - Xc) * (F/Z)) + Xc
  int Xc = (left ? -1 : 0);
  // Center X and Y as best as possible for an even number of points,
  // and distance Z a little so that the perspective is not too exaggerated.
  // The formulae below should be tweaked by eye once we add graphics.
  // This is also where we would make a small tweak for the Vectrex's unequal X vs Y co-ordinate system
  int WX=(int)x-Xc, /*WY=(int)y-8,*/ WZ=(int)z+10; // x=-8:7, y=-8:7, z=10:25
  return (int8_t)((WX * 128)/WZ + Xc-107);
}

static int8_t perspective_y(const unsigned int8_t y, unsigned const int8_t z) {
  // Y' = ((Y - Yc) * (F/Z)) + Yc
  int /*WX=(int)x-8,*/ WY=(int)y-8, WZ=(int)z+10;
  return (int8_t)((WY * 128)/WZ + 10);
}
#endif

static inline void add_line(const unsigned int8_t x0, const unsigned int8_t y0, unsigned const int8_t z0,
                            const unsigned int8_t x1, const unsigned int8_t y1, unsigned const int8_t z1) {
  // Could probably speed this up by using globals rather than 6 parameters.
  // Or make it inline (which I just did). Not that it matters as these are not in main loop.
  line[next_free_line].x0 = x0; line[next_free_line].x1 = x1;
  line[next_free_line].y0 = y0; line[next_free_line].y1 = y1;
  line[next_free_line].z0 = z0; line[next_free_line].z1 = z1;
  next_free_line += 1;
}

static unsigned int tick = 0;
static void draw_object(display dims) {
  unsigned int8_t i, x0,x1,y0,y1,z0,z1;
  int x, y, win_offset, dx, dy, nx, ny;

  for (i = 0; i < next_free_line; i++) {

#ifdef DEBUG
    fprintf(stderr, "Line segment #%d\n", i);
#endif
    
    // These bitfield assignments are forcing me to add -Wno-conversion to the GCC parameters:
    x0 = line[i].x0;
    x1 = line[i].x1;

    y0 = line[i].y0;
    y1 = line[i].y1;

    z0 = line[i].z0;
    z1 = line[i].z1;

    set_scale(0xFF);
    if (dims == CROSSEYED) {
      win_offset = 32;
    } else if (dims == WALLEYED) {
      win_offset = -32;
    } else {
      win_offset = 0;
    }

    if ((dims == MONO) || (((dims >= CROSSEYED) || (dims == STEREO)) && ((tick&1) == 0))) {
    // Right eye:
    Reset0Ref();
    y = screen[z0][y0][x0].y SCALED; // pending rescale of array
    x = screen[z0][y0][x0].xright SCALED;
    if (dims >= CROSSEYED) {
      Moveto_d((-y)>>1, (x>>1)-win_offset);
    } else {
      Moveto_d(y, x);
    }
    // Subtract last coord from this coord to get delta move value
    dy = (ny=(screen[z1][y1][x1].y SCALED)) - y;
    dx = (nx=(screen[z1][y1][x1].xright SCALED)) - x;
    Intensity_a(0x6F);
    if (dims >= CROSSEYED) {
      Draw_Line_d((-dy)>>1, dx>>1);
    } else {
      Draw_Line_d(dy, dx);
    }
    }
#ifdef DEBUG
    fprintf(stderr, "Right eye:\nPoint %u,%u,%u maps to screen %d,%d\n",
                    x0,y0,z0,
                    screen[z0][y0][x0].xright SCALED,
                    screen[z0][y0][x0].y SCALED);
    fprintf(stderr, "  and %u,%u,%u maps to screen %d,%d\n\n",
                    x1,y1,z1,
                    screen[z1][y1][x1].xright SCALED,
                    screen[z1][y1][x1].y SCALED);
#endif

    if (((dims >= CROSSEYED) || (dims == STEREO)) && ((tick&1) != 0)) {
    // Left eye:
    Reset0Ref(); // *OR* should be equivalent - but isn't:      Moveto_d(-ny,-nx);
    y = (screen[z0][y0][x0].y SCALED);
    x = (screen[z0][y0][x0].xleft SCALED);
    if (dims >= CROSSEYED) {
      Moveto_d((-y)>>1, (x>>1)+win_offset);
    } else {
      Moveto_d(y, x);
    }
    // Subtract last coord from this coord to get delta move value
    dy = (screen[z1][y1][x1].y SCALED) - y;
    dx = (screen[z1][y1][x1].xleft SCALED) - x;
    Intensity_a(0x6F);
    if (dims >= CROSSEYED) {
      Draw_Line_d((-dy)>>1, dx>>1);
    } else {
      Draw_Line_d(dy, dx);
    }
    }

#ifdef DEBUG
    if (dims == STEREO) {
    fprintf(stderr, "Left eye:\nPoint %u,%u,%u maps to screen %d,%d\n",
                    x0,y0,z0,
                    screen[z0][y0][x0].xleft SCALED,
                    screen[z0][y0][x0].y SCALED);
    fprintf(stderr, "  and %u,%u,%u maps to screen %d,%d\n\n",
                    x1,y1,z1,
                    screen[z1][y1][x1].xleft SCALED,
                    screen[z1][y1][x1].y SCALED);
    }
#endif
  }
}

int main(void) {
  init_random(0xcb,0xa9,0xd5,0x34);

#ifndef USE_PRECALC
  unsigned int8_t x, y, z;
  // x = y = z = 0U;
  printf("static const screenxy screen[16][16][16] = {\n");  
  for (z = 0; z < 16U; z++) {
    printf("  {\n");
    for (y = 0U; y < 16U; y++) {
      printf("  // screen[z=%0d][y=%0d][x]:\n  {", z, y);
      for (x = 0U; x < 16U; x++) {
        screen_point.xleft = perspective_x(1, x,z);
        screen_point.xright = perspective_x(0, x,z);
        screen_point.y = perspective_y(y,z);
        printf("{%4d,%4d,%4d}", screen_point.xleft>>1, screen_point.xright>>1, screen_point.y>>1);
        if (x != 15) printf(", ");
        if (x == 7) printf("\n   ");
        screen[z][y][x] = screen_point;
      }
      printf("},\n");
    }
    printf("  },\n");
  }
  printf("};\n\n");
  fflush(stdout);
#endif

  unsigned int zdepth;
  int deltaz;
  next_free_line = 0; // COMPILER BUG? declaration of next_free_line should have done this already! 


  int reset_list;

  zdepth = 14;
  deltaz = 1;

  add_line( 0U, 0U,0U,   0U,15U,0U); // Near frame
  add_line( 0U,15U,0U,  15U,15U,0U);
  add_line(15U,15U,0U,  15U, 0U,0U);
  add_line(15U, 0U,0U,   0U, 0U,0U);

  for (reset_list = next_free_line; reset_list < 16; reset_list++) {
    add_line(random()&15,random()&15,random()&15, random()&15,random()&15,random()&15);
  }
  reset_list = next_free_line;

  for (;;) {
  for (;;) {
    Wait_Recal();
    check_buttons();
    Print_Str_d( 60, -75, "A  MONO \x80");
    Print_Str_d( 20, -75, "B  STEREO (ALMOST)\x80");
    Print_Str_d(-20, -75, "C  CROSS-EYED \x80");
    Print_Str_d(-60, -75, "D  WALL-EYED \x80");
    if (button_1_1_pressed()) {
      DispType = MONO; break;
    } else if (button_1_2_pressed()) {
      DispType = STEREO; break;
    } else if (button_1_3_pressed()) {
      DispType = CROSSEYED; break;
    } else if (button_1_4_pressed()) {
      DispType = WALLEYED; break;
    }
  }


  for (;;) {
    next_free_line = reset_list;

    // Make a shape cycle through te depths...
    add_line( 7U, 0U,zdepth,   0U, 7U,zdepth); // Far
    add_line( 0U, 7U,zdepth,   7U,15U,zdepth);
    add_line( 7U,15U,zdepth,  15U, 7U,zdepth);
    add_line(15U, 7U,zdepth,   7U, 0U,zdepth);

    Wait_Recal();
    check_buttons();
    if (button_1_1_pressed()) {
      break;
    } else if (button_1_2_pressed()) {
      break;
    } else if (button_1_3_pressed()) {
      break;
    } else if (button_1_4_pressed()) {
      break;
    }
    // Use the left eye view only when generating a stereo pair
    draw_object(DispType);

    if (++tick == 3) {
      tick = 0;
      zdepth = (unsigned int)((int)zdepth + deltaz);
      if ((zdepth == 0) || (zdepth == 15)) deltaz = -deltaz;
    }
  }
  }

  return 0;

}
