#ifndef __linux
#define VECTREX 1
#endif
/*

    After many previous attempts, I am having one final effort to create a C version
    of Tailgunner.  Graphics will consist of DrawLine(x1,y1, x2,y2, intensity) and Clear()
    so should be replacable on any system, while remaining easy enough to build on the
    Vectrex.
  
 */

#ifdef VECTREX
// Compile with -O2 option
#include <vectrex.h>
#define set_scale(A) dp_VIA_t1_cnt_lo = A
#define int8_t int
#define uint8_t unsigned int
#define int16_t long

#else

// #################################### Linux code follows ########################################
#include <stdint.h>
#include <stdio.h>

static int debug = 0;

#define FPS 30

#define SHOW_FPS 1
#define HORIZONTAL 1

#define USE_FB_320X480 1
#ifdef USE_FB_320X480
#define USE_FB 1
#endif

#ifdef USE_FB
#define FBGL_IMPLEMENTATION 1
#include "fbgl.h"
fbgl_psf1_font_t *font;
#endif

#ifdef USE_FB
static fbgl_t fb;
static uint32_t fbcolor = 0xFFFFFF;

void fbDrawColor(uint8_t r, uint8_t g, uint8_t b, uint32_t c) {
  fbcolor = (c<<16) | (c<<8) | c;
}

void fbDrawPoint(int32_t x0, int32_t y0) {
  if (debug) fprintf(stdout, "fbDrawPoint(%d,%d);\r\n", x0, y0);
  int sx = (x0*fb.width/128)+fb.width/2;
  int sy = (y0*fb.width/128)+fb.height/2;
  if (debug) fprintf(stdout, "fbgl_put_pixel(%d,%d, %d,%d, %06x, %dp;\r\n", sx, sy,  fbcolor, &fb);
  fbgl_put_pixel(sx, (fb.height-1)-sy, fbcolor, &fb);
}

void fbDrawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
  if (debug) fprintf(stdout, "fbDrawLine(%d,%d, %d,%d);\r\n", x0, y0,  x1, y1);
  int sx0 = (x0*fb.width/128)+fb.width/2;
  int sy0 = (y0*fb.width/128)+fb.height/2;
  int sx1 = (x1*fb.width/128)+fb.width/2;
  int sy1 = (y1*fb.width/128)+fb.height/2;
  if (debug) fprintf(stdout, "fbgl_draw_line(%d,%d, %d,%d, %06x, %p);\r\n", sx0, sy0,  sx1, sy1,  fbcolor, &fb);
  fbgl_draw_line((fbgl_point_t){.x=sx0,.y=(fb.height-1)-sy0},
                 (fbgl_point_t){.x=sx1,.y=(fb.height-1)-sy1},
                 fbcolor, &fb);
}
#endif

static int8_t vscale = 127;
static int8_t vbright = 0x7F;
static int8_t vx = 0;
static int8_t vy = 0;

static int fromx = 0, fromy = 0;
static void G_MoveAbs(int8_t x, int8_t y, uint8_t scale) {
  if (debug) fprintf(stdout, "G_MoveAbs(%d,%d, scale=%u);\r\n", x, y, scale);
  fromx = (x * scale) / 256; fromy = (y * scale) / 256;
  if (debug) fprintf(stdout, "fromx = %d; fromy = %d;\r\n", fromx, fromy);
}
static void G_MoveRel(int8_t x, int8_t y, uint8_t scale) {
  if (debug) fprintf(stdout, "G_MoveRel(%d,%d, scale=%u);\r\n", x, y, scale);
  fromx += (x * scale) / 256; fromy += (y * scale) / 256;
  if (debug) fprintf(stdout, "fromx = %d; fromy = %d;\r\n", fromx, fromy);
}
static void G_LineRel(int8_t x, int8_t y, uint8_t intensity, uint8_t scale) {
  if (debug) fprintf(stdout, "G_LineRel(%d,%d, intensity=%d, scale=%d);\r\n", x, y, intensity, scale);
  int tox, toy;
  tox = (x * scale) / 256; toy = (y * scale) / 256;
  // draw [from,fromy] to [tox,toy]
#ifdef USE_FB
  fbDrawColor(intensity, intensity, intensity, intensity);
  fbDrawLine(fromx, fromy, fromx+tox, fromy+toy);
#endif
  fromx += tox; fromy += toy;
}
static void Clear(void) {
  // clear screen
  if (debug) fprintf(stdout, "clear();\r\n");
#ifdef USE_FB
  fbgl_clear(&fb);
#endif
}
static int InitGraphics(void) {
#ifdef USE_FB
  if (debug) fprintf(stderr, "InitGraphics();\r\n");
  // Load PSF1 font
  font = fbgl_load_psf1_font("font-16.psf");
  if (!font) {
    fprintf(stderr, "Failed to load PSF1 font-16.psf\r\n");
    return 1;
  }

  if (fbgl_init("/dev/fb1", &fb) == -1) {
    fprintf(stdout, "Error: could not open framebuffer device\r\n");
    return -1;
  }
  // Initialize keyboard
  if (fbgl_keyboard_init() != 0) {
    fprintf(stderr, "Failed to initialize keyboard\r\n");
    fbgl_destroy(&fb);
    return 1;
  }
  fbgl_set_fps(FPS);
#endif
}
static void set_scale(uint8_t scale) {
  vscale = scale;
}
static void Wait_Recal(void) {
  if (debug) fprintf(stdout, "Wait_Recal();\r\n");
  usleep(1000000/FPS);
  Clear();
}
static void Intensity_7F(void) {
  vbright = 0x7F;
}
static void Intensity_a(uint8_t i) {
  vbright = i;
}
static void Reset0Ref(void) {
  if (debug) fprintf(stdout, "Reset0Ref();\r\n");
  G_MoveAbs(vx = 0, vy = 0, vscale);
}
static void Moveto_d(int8_t y, int8_t x) {
  if (debug) fprintf(stdout, "Moveto_d(y=%d, x=%d);\r\n", y, x);
  G_MoveRel(vx = x, vy = y, vscale);
}
static void Draw_Line_d(int8_t y, int8_t x) {
  if (debug) fprintf(stdout, "Draw_Line_d(y=%d, x=%d);\r\n", y, x);
  G_LineRel(x, y, vbright, vscale);
}
#endif

// ################################### Vectrex code follows #######################################
#define MAX_VERTICES 15

typedef struct {
    int8_t x, y;
} screen;

static inline int8_t trig_mul(int8_t a, int8_t b) {
  return (int8_t)(((int16_t)a * (int16_t)b) >> 6L);
}

// Although this table could be shortened, a full table is faster...
static const int8_t sin_table[256] = {
  0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 16, 17, 19, 20, 22, 23,
  24, 26, 27, 29, 30, 32, 33, 34, 36, 37, 38, 39, 41, 42, 43, 44,
  45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59,
  59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 60, 60,
  59, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46,
  45, 44, 43, 42, 41, 39, 38, 37, 36, 34, 33, 32, 30, 29, 27, 26,
  24, 23, 22, 20, 19, 17, 16, 14, 12, 11, 9, 8, 6, 5, 3, 2,
  0, -2, -3, -5, -6, -8, -9, -11, -12, -14, -16, -17, -19, -20, -22, -23,
  -24, -26, -27, -29, -30, -32, -33, -34, -36, -37, -38, -39, -41, -42, -43, -44,
  -45, -46, -47, -48, -49, -50, -51, -52, -53, -54, -55, -56, -56, -57, -58, -59,
  -59, -60, -60, -61, -61, -62, -62, -62, -63, -63, -63, -64, -64, -64, -64, -64,
  -64, -64, -64, -64, -64, -64, -63, -63, -63, -62, -62, -62, -61, -61, -60, -60,
  -59, -59, -58, -57, -56, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46,
  -45, -44, -43, -42, -41, -39, -38, -37, -36, -34, -33, -32, -30, -29, -27, -26,
  -24, -23, -22, -20, -19, -17, -16, -14, -12, -11, -9, -8, -6, -5, -3, -2,
};

static inline int8_t sin_fixed(uint8_t angle) {
    return sin_table[angle&0xFF];
}

static inline int8_t cos_fixed(uint8_t angle) {
  return sin_table[(angle + 64U)&0xFF]; // &0xFF only needed on linux
}

// Objects are stored as 8-bit coordinates centered on 0,0,0

// A model is a list of vertices, plus a list of lines to be drawn from vertex to vertex.

// Sin and Cos tables are lookups returning 8 bit values in the range -64:64, where
// 64 represents 1.0f and -64 is -1.0f  This is to avoid the need for an extra byte
// in calculations if -128:128 were used, or expensive divides if -127:127 were used.

// We will apply the transformations to the vertices first, then draw the ship using
// the transformed vertices.  This differs from the original code where each line was
// being transformed resulting in redundant transformations being calculated any time
// two lines met at a point.  Drawing is optimised by only issuing a move when the
// next line does not follow the proceding line.  In those cases a reset0ref is also
// performed.

typedef struct {
    int8_t x, y, z;
} point3d;

typedef struct {
    int8_t from, to;
} edge_t;

typedef struct {
  const edge_t *edge;
  const point3d *vertex;
  const uint8_t edge_count;
  const uint8_t vertex_count;
} object_t;

#define DEMO_POINTS1 12
static const point3d ship1_vertex[DEMO_POINTS1] = {
  /* 0 */ { 0, 0, -100/2 },
  /* 1 */ { 0, 0, -20/2 },
  /* 2 */ { 0, -40/2, -60/2 },
  /* 3 */ { 0, 40/2, -60/2 },
  /* 4 */ { -100/2, -40/2, 80/2 },
  /* 5 */ { 100/2, -40/2, 80/2 },
  /* 6 */ { -20/2, 20/2, 120/2 },
  /* 7 */ { 20/2, 20/2, 120/2 },
  /* 8 */ { -30/2, 0, 120/2 },
  /* 9 */ { 30/2, 0, 120/2 },
  /* 10 */ { -40/2, 0, -60/2 },
  /* 11 */ { 40/2, 0, -60/2 },
};
#define DEMO_EDGES1 20
// Reordering the vectors to chain from one to the next has speeded up the graphics enough
// to actually be playable (though some more speed is still needed...)  However we lose the
// Reset0Ref that is inserted whenever there is a discontinuity, so we'll have to try it in
// a real Vectrex to see if the drift is too much.
static const edge_t ship1_edge[DEMO_EDGES1] = {
  {  0, 10 },  { 10,  1 },  {  1, 11 },  { 11,  0 },  {  0,  2 },  {  2,  1 },  {  1,  3 },  {  3,  0 },
  {  7,  1 },  {  1,  9 },  {  9,  7 },  {  7,  6 },  {  6,  8 },  {  8,  9 },  {  9,  5 },  {  5,  1 },  {  1,  4 },  {  4,  8 }, {8, 1},  {  1,  6 },
};
object_t ship1 = {
  ship1_edge, ship1_vertex, DEMO_EDGES1, DEMO_POINTS1
};

#define DEMO_POINTS2 15
static const point3d ship2_vertex[DEMO_POINTS2] = {
  /*  0 */          { 0, 0, -90/2 },
  /*  1 */          { 0, 0, 90/2 },
  /*  2 */          { 0, -34/2, /*-129*/ -128/2 },
  /*  3 */          { -45/2, 0, /*150*/ 127/2 },
  /*  4 */          { 45/2, 0, /*150*/ 127/2 },
  /*  5 */          { -45/2, 0, -60/2 },
  /*  6 */          { -45/2, 0, 60/2 },
  /*  7 */          { 45/2, 0, -60/2 },
  /*  8 */          { 45/2, 0, 60/2 },
  /*  9 */          { -60/2, 24/2, /*150*/ 127/2 },
  /* 10 */          { 60/2, 24/2, /*150*/ 127/2 },
  /* 11 */          { -60/2, 24/2, 60/2 },
  /* 12 */          { 60/2, 24/2, 60/2 },
  /* 13 */          { -60/2, -27/2, -90/2 },
  /* 14 */          { 60/2, -27/2, -90/2 },
};
#define DEMO_EDGES2 21
static const edge_t ship2_edge[DEMO_EDGES2] = {
  {  2,  0},  {  0,  7},  {  7,  8},  {  8,  1},  {  1,  6},  {  6,  5},  {  5,  0},
  {  8, 12},  { 12, 10},  { 10,  4},  {  4,  8},  {  8, 14},  { 14,  2},  {  2, 13},   { 13,  6},  {  6,  3},  {  3,  9},  {  9, 11},  { 11,  6},
  {  5, 13},
  { 14,  7},
};

object_t ship2 = {
  ship2_edge, ship2_vertex, DEMO_EDGES2, DEMO_POINTS2
};

#define DEMO_POINTS3 11
static const point3d ship3_vertex[DEMO_POINTS3] = {
  /*  0 */          { 0, 0, -100/2 },
  /*  1 */          { 0, 0, 20/2 },
  /*  2 */          { 0, 40/2, 100/2 },
  /*  3 */          { 0, 40/2, -40/2 },
  /*  4 */          { 0, 80/2, 100/2 },
  /*  5 */          { -100/2, -40/2, 100/2 },
  /*  6 */          { 100/2, -40/2, 100/2 },
  /*  7 */          { -40/2, -20/2, 100/2 },
  /*  8 */          { 40/2, -20/2, 100/2 },
  /*  9 */          { -60/2, 0, -40/2 },
  /* 10 */          { 60/2, 0, -40/2 },
};

#define DEMO_EDGES3 17
static const edge_t ship3_edge[DEMO_EDGES3] = {
  {  0, 10},  { 10,  1},  {  1,  9},  {  9,  0},  {  0,  3},  {  3,  1},  {  1,  4},  {  4,  2},  {  2,  1},  {  1,  6},  {  6,  8},  {  8,  1},  {  1,  5},  {  5,  7},  {  7,  1},
  { 10,  3},  {  3,  9},
};
object_t ship3 = {
  ship3_edge, ship3_vertex, DEMO_EDGES3, DEMO_POINTS3
};

#define DEMO_POINTS4 11
static const point3d ship4_vertex[DEMO_POINTS4] = {
  /*  0 */          { 0, 10/2, -20/2 },
  /*  1 */          { 0, -25/2, -45/2 },
  /*  2 */          { 0, 2/2, -92/2 },
  /*  3 */          { -20/2, -10/2, 110/2 },
  /*  4 */          { 20/2, -10/2, 110/2 },
  /*  5 */          { -22/2, 15/2, -60/2 },
  /*  6 */          { 22/2, 15/2, -60/2 },
  /*  7 */          { -30/2, 2/2, -70/2 },
  /*  8 */          { 30/2, 2/2, -70/2 },
  /*  9 */          { -90/2, 20/2, 90/2 },
  /* 10 */          { 90/2, 20/2, 90/2 },
};

#define DEMO_EDGES4 23
static const edge_t ship4_edge[DEMO_EDGES4] = {
  {  2,  8},  {  8,  0},  {  0,  7},  {  7,  2},  {  2,  1},  {  1,  0},  {  0, 10},  { 10,  4},  {  4,  3},  {  3,  0},  {  0,  4},  {  4,  1},  {  1,  3},  {  3,  9},  {  9,  0},
  {  8,  6},  {  6,  5},  {  5,  7},  {  7,  1},  {  1,  8},  {  8,  7},
  {  6,  0},  {  0,  5},
};
object_t ship4 = {
  ship4_edge, ship4_vertex, DEMO_EDGES4, DEMO_POINTS4
};

static screen screen_vertex[MAX_VERTICES]; // As many vertices as largest object requires.

static inline void draw_object(const object_t *object, uint8_t scale, int8_t ox, int8_t oy) {
  const edge_t *edge = object->edge;
  int8_t i = 0, from, to, last_to = -1, vfx, vfy;
  set_scale(scale);
  for (;;) {
    if (i == (int8_t)object->edge_count) return;
    from = edge[i].from; to = edge[i].to;
    vfx = screen_vertex[from].x; vfy = screen_vertex[from].y;
    if (from != last_to) { Reset0Ref(); Moveto_d(oy, ox);  Moveto_d(vfy, vfx); }
    last_to = to;
    Draw_Line_d(screen_vertex[to].y - vfy, screen_vertex[to].x - vfx);
    i += 1;
  }
}

static inline void project_vertex(const point3d *v, uint8_t ax, uint8_t ay, uint8_t az, screen *vec) {
    int8_t x = v->x;
    int8_t y = v->y;
    int8_t z = v->z;

    // Rotate around X axis
    int8_t cosy = cos_fixed(ax);
    int8_t siny = sin_fixed(ax);
    int8_t y1 = trig_mul(y, cosy) -  trig_mul(z, siny);
    int8_t z1 = trig_mul(y, siny) +  trig_mul(z, cosy);
    y = y1; z = z1;

    // Rotate around Y axis
    int8_t cosx = cos_fixed(ay);
    int8_t sinx = sin_fixed(ay);
    int8_t x1 =   trig_mul(x, cosx) +  trig_mul(z, sinx);
    int8_t z2 = - trig_mul(x, sinx) +  trig_mul(z, cosx);
    x = x1; z = z2;

    // Rotate around Z axis
    int8_t cosz = cos_fixed(az);
    int8_t sinz = sin_fixed(az);
    int8_t x2 =  trig_mul(x, cosz) -  trig_mul(y, sinz);
    int8_t y2 =  trig_mul(x, sinz) +  trig_mul(y, cosz);

    // A simple x,y projection will suffice!
    // However if we really needed perspective to be applied, we could map the X value
    // using a lookup table based on the point's Z coordinate.
    vec->x = x2;
    vec->y = y2;

}

/*
     Calculating the orientation and flight path of a ship on the fly is expensive, and
     made worse by the fact that we're not actually going to work in a true 3D model of
     the world with 16-bit 3D spatial coordinates and bezier-determined flightpaths where
     the ship's orientation along the flight path matches what the human expects.  These
     turn out to be sufficiently expensive to compute that doing so is not feasible while
     also computing the 3D view of the ship.  So either the flight paths or the ship drawing
     or both will have to be pre-computed for speed.  A previous attempt saved vector information
     for drawing the entire flight but the space requirements were so large that only one
     wave was practical.  Drawing of the rotated ship was also too expensive in previous
     iterations but this code takes the shortcut of using the hardware scaling to remove
     some of the computation (especially expensive divides), so our assumption is that for
     a known spatial position and orientation of a ship, we ony need to pre-compute the ship's
     orientation, screen position, and scale to draw at - we'll assume that with that information
     we can draw a ship correctly.  The precomputed data can be found using 32-bit arithmetic
     and a faster processor.

     So to preconstruct a flight path, we need to record the ship number, and a series of
       x,y for center of ship
       rx,ry,rz for rotation of ship around center on each axis
       scale

     which will require 6 bytes per position, so at 30 fps and say 3 seconds per flight, that's
     approximately 6*30*3 = 540 bytes per flightpath.  Three ships per wave comes to 1620 bytes
     so if we assume 8K for code and 24K for data we can expect roughly 15 waves of precomputed
     flight paths for 24K of Eprom.  More if we have smaller code or a 48K Eprom.  This seems
     to be ballpark so on that basis I am going to continue the project using those assumptions.
 */

static inline void project_object(const object_t *object,
                                  uint8_t angle_x, uint8_t angle_y, uint8_t angle_z,
                                  uint8_t scale,
                                  int8_t x, int8_t y) {
  Intensity_a(((255U-scale)>>2)+48U);
  for (uint8_t i = 0; i < object->vertex_count; i++) {
    project_vertex(&object->vertex[i], angle_x, angle_y, angle_z, &screen_vertex[i]);
  }
  draw_object(object, scale, x, y);
}

int main(void) {
  object_t *ship[4] = { &ship1, &ship2, &ship3, &ship4 };
  int8_t x[4] = {-120, 0, 120, 0}, y[4] = {-40, 92, -40, -80};
  // rotational values for 4 ships. (Though TG only needs 3 copies of the same ship)
  uint8_t angle_x[4] = {5,5,5,0}, angle_y[4] = {5,0,4,0}, angle_z[4] = {5,0,250,0};  // 0:255 angles make a circle.
  uint8_t scale = 5;  // We'll use hardware scaling and adjust it separately from the viewing angle transformation

#ifndef VECTREX
  (void)InitGraphics();
#endif

  for (;;) {
    
#ifdef USE_FB
    fbgl_key_t key = fbgl_poll_key(); // poor library interface - short term hack
    if (key == FBGL_KEY_ESCAPE) {
      fbgl_destroy(&fb);
      exit(EXIT_SUCCESS);
    }
#endif
    
    Wait_Recal();
    Reset0Ref();
    Intensity_7F();

    // staggering the ships (as in the real game) avoids having all 3 on screen at
    // full magnification at the same time and so cuts down on the maximum cycles.

    x[1] = 0; y[1] = 60+(int8_t)(++scale>>2U);
    project_object(ship[0], angle_x[0], angle_y[0], angle_z[0], scale+40, -120, -40);
    project_object(ship[1], angle_x[1], angle_y[1], angle_z[1], scale,       0,  60+(int8_t)(scale>>2U));
    project_object(ship[2], angle_x[2], angle_y[2], angle_z[2], scale+80,  120, -40);
    //project_object(ship[3], angle_x[3], angle_y[3], angle_z[3], scale+40,  0, -90);

    if (scale == 255) scale = 10;
  }
}
