#include <vectrex.h>  // I think this is Chris's version.  Planning to switch to Peer's C library

#define debugstr(s) /* ignore */

#define signed16 long
#define unsigned16 unsigned long
#define signed8 signed char
#define unsigned8 unsigned char
#define boolean int

// Under GCC, using default header file/library.

// some assembly required...
#define jsrx(v, func)       asm("ldx %0\n\t" \
                                "jsr " #func "\n\t" : : "g" (v) : "d", "x")
#define Draw_VLp(ra) jsrx(ra, 0xF410)   // (pattern y x)* 0x01 

static inline void intensity(unsigned8 brightness) {Intensity_a(brightness);}
static inline void dot(signed8 y, signed8 x) {Dot_d((int)y, (int)x);}
static inline void move(signed8 y, signed8 x) {Moveto_d((int)y, (int)x);}
static inline void reset_beam(void) {Reset0Ref();}
static inline void set_scale(unsigned8 scale) {/*VIA_t1_cnt_lo*/ *(volatile unsigned char *)0xD004 = scale;}
static inline void line(signed8 y, signed8 x) {*(volatile unsigned char *)0xC823 = 0; Draw_Line_d((int)y, (int)x);}
static inline void dots(unsigned8 size, const signed8 *array) { *(volatile char *)0xC823 = size-1; Dot_List((void *)array); }
static inline void displaylist(const signed8 *list) { Draw_VLp(list); }
static inline void wait_retrace(void) {Wait_Recal();}

static inline void rotate_vl(unsigned8 angle, unsigned8 points, const void *original, void *rotated)
{
  *(volatile unsigned int *)0xC836 = angle;
  *(volatile unsigned int *)0xC823 = points-1;
  Rot_VL((int *)original, rotated);
}

#define joybit Joy_Digital
#define tstat (*((volatile unsigned8 *) 0xc856))

#define pot0 (*((volatile signed8 *) 0xc81b))
#define pot1 (*((volatile signed8 *) 0xc81c))
#define pot2 (*((volatile signed8 *) 0xc81d))
#define pot3 (*((volatile signed8 *) 0xc81e))

#define epot0 (*((volatile unsigned8 *) 0xc81f))
#define epot1 (*((volatile unsigned8 *) 0xc820))
#define epot2 (*((volatile unsigned8 *) 0xc821))
#define epot3 (*((volatile unsigned8 *) 0xc822))

#define t1lolc (*((volatile unsigned8 *) 0xd004))

#define read_ram(x) (*((volatile signed8 *) x))

#define joystick1_button1 (signed8)(read_ram(0xC80F) & 1U) /* A */
#define joystick1_button2 (signed8)(read_ram(0xC80F) & 2U) /* S */
#define joystick1_button3 (signed8)(read_ram(0xC80F) & 4U) /* D */
#define joystick1_button4 (signed8)(read_ram(0xC80F) & 8U) /* F */

static signed8 XJoy, YJoy, button1, button2, button3, button4;

static void poll_joystick(void)
{
  joybit();
  if (pot0 < -10) {
    // Left
    XJoy = -1;
  } else if (pot0 > 10) {
	// Right
	XJoy = 1;
  } else {
    XJoy = 0;
  }
  if (pot1 < -10) {
	// Down
	YJoy = -1;
  } else if (pot1 > 10) {
    // Up
	YJoy = 1;
  } else {
    YJoy = 0;
  }
  asm("pshs x,y,u,dp");
  asm("lda  #0");
  asm("jsr  0HF1B4 ; read_btns_mask":::"a","b","d");
  asm("puls dp,u,y,x");
  button1 = joystick1_button1;
  button2 = joystick1_button2;
  button3 = joystick1_button3;
  button4 = joystick1_button4;
}

static void init_hardware(void)
{
	// setup joystick read function to read only joystick 1
	epot0 = 1;
	epot1 = 3;
	epot2 = 0;
	epot3 = 0;
}

#define signed16 long
#define unsigned16 unsigned long
#define signed8 signed char
#define unsigned8 unsigned char
#define boolean int

#define MAX_BRIGHTNESS (0x7f)

static signed16 Debug;

#define IN_ROM __attribute__ ((section(".text")))

/* Shields get a special proc because they're one continuous line */
static const signed8 shieldvec[] IN_ROM = {
   0,  -72,24,
  -1,  24,24,
  -1,  96,-96,
  -1,  24,24,
  -1,  -72,72,
  -1,  -72,-72,
  -1,  24,-24,
  -1,  96,96,
  -1,  24,-24,
  -1,  -72,-72,
  -1,  -72,72,
   1
};

static const signed8 crosshair[] IN_ROM = { 
  0, 17, 40, // trying to move it to the center
  -1, -30, 0,
  0, 15, 20,
  -1, 0, -115,
  0, -15, 20,
  -1, 30, 0,
  0, -10, 20,
  -1, -10, 0,
  0, -10, 10,
  -1, 0, 10,
  0, -15, 10,
  -1, 0, -30,
  0, -20, 15,
  -1, 105, 0,
  0, -20, 15,
  -1, 0, -30,
  0, -20, 10,
  -1, 0, 10,
  0, -10, 10,
  -1, -10, 0,
  1
};


#define DEFINE(name) static const signed8 name[] IN_ROM = {
#define move_rel_xy(x,y) 0 /*MOVE*/, ((signed8)y), ((signed8)x)
#define line_rel_xy(x,y) (signed8)-1 /*LINE*/, ((signed8)y), ((signed8)x)
#define ENDDEF(name) 1 /*STOP*/ }; static void SHOW_##name(void) { displaylist(name); }

#include "font.h"

#include "recording.h"

static const signed8 *digitlist[10] = {
  N0, N1, N2, N3, N4, N5, N6, N7, N8, N9
};

#include "placard.h"

// 16 bit maths, no floats.

static signed16 rseed;
static inline signed16 urandom16(void)
{
  // VECTREX HAS A BUILT-IN RANDOM THAT WE CAN USE!
  // (though it doesn't seem very good - this may be better and faster)
  return (rseed = (rseed * 2053) + 13849);
}

static inline unsigned16 irand(signed16 max) {
  // returns int 0..max-1
  return (unsigned16)(urandom16() % max); // probably not very random, but this is a video game, not crypto
}

static inline signed16 pickrandom(signed16 low, signed16 high)
{
  // We'll just assume that high > low...
  return (signed16)irand(high-low+1)+low;  // pick a random integer between low and high inclusive.
}

static signed8 stardots[8]; // fill with randoms ONCE - could move this into ROM...

static signed8 rotated3[8]; // rotate stardots into place
static signed8 rotated2[8];
static signed8 rotated1[8];
static signed8 rotated0[8];

static signed8 *rotated[4] = {
  rotated0,
  rotated1,
  rotated2,
  rotated3
};

static unsigned8 starfield_scale;

static inline void init_stars(void)
{
  starfield_scale = (unsigned8)0xFF;

  // origin at 0,0
  stardots[0] = -60;                           stardots[1] = (signed8)pickrandom(-45,45);  // LEFT
  stardots[2] = 60;                            stardots[3] = (signed8)pickrandom(-45,45);  // RIGHT
  stardots[4] = (signed8)pickrandom(-45,45);   stardots[5] = 60;                           // TOP
  stardots[6] = (signed8)pickrandom(-45,45);   stardots[7] = -60;                          // BOT

  stardots[6] -= stardots[4]; stardots[7] -= stardots[5]; // abs to rel
  stardots[4] -= stardots[2]; stardots[5] -= stardots[3];
  stardots[2] -= stardots[0]; stardots[3] -= stardots[1];

  rotate_vl((unsigned int)(pickrandom(0,15)), 4U, stardots, rotated0);
  rotate_vl((unsigned int)(pickrandom(16,31)), 4U, stardots, rotated1);
  rotate_vl((unsigned int)(pickrandom(32,47)), 4U, stardots, rotated2);
  rotate_vl((unsigned int)(pickrandom(48,63)), 4U, stardots, rotated3);
}

static void stars(unsigned8 speed) // speed must be a power of 2
{
  unsigned8 shifted_scale;
  signed8 i;

  debugstr("stars");
  // New fast algorithm, does approximate fading with distance.

  shifted_scale = starfield_scale-(unsigned8)0xBF;
  for (i = 0; i < 4; i++) {
    if ((unsigned8)shifted_scale > (unsigned8)0x20) { // disappear here
      reset_beam();
      intensity((shifted_scale>>1)-0x10);
      set_scale(shifted_scale);
      dots(4, rotated[i]);
    } else {
      if (((unsigned8)shifted_scale & (unsigned8)0x3F) == 0U) {
        // pull in new ring of stars
        rotate_vl((unsigned int)(pickrandom(0,63)), 4U, stardots, rotated[i]);
      }
    }
    shifted_scale += 0x40;
  }
  starfield_scale -= speed; // must be power of 2
}


static void position_and_scale(signed8 absx, signed8 absy, signed16 scale)
{
  reset_beam();
  set_scale(0x7f);
  move(absy, absx);
  set_scale((unsigned8)scale);
}

static inline void position_and_scale_and_intensity(signed8 absx, signed8 absy, signed16 scale, unsigned8 bright)
{
  position_and_scale(absx, absy, scale);
  intensity(bright);
}

/* Shields on the vectrex are sideways wrt the 'real' tailgunner */
static void draw_shields(unsigned8 scale)
{
  debugstr("draw_shields");

  position_and_scale(0, 0, scale);
  displaylist(shieldvec);
}

static inline void Draw_crosshair(signed8 absx, signed8 absy)
{
  debugstr("Draw_crosshair");

  position_and_scale(absx, absy, 0x18);
  displaylist(crosshair);
}

/* 3d rendering */

static unsigned8 Credits;

static unsigned8 intro_rot;
static signed16 frame_number;
static unsigned8 global_flashing_intensity;

static signed16 HighScore;
static signed16 LastScore;
static signed16 Score;

#ifdef NOTYET
static signed16 UsingShields;
static signed16 Shields;
static signed16 shieldsegment;
static signed16 shieldsubticksleft;
#endif

// The revolving intro placard...

static void drawtgintro(unsigned8 scale, unsigned8 bright) {
  position_and_scale_and_intensity(0,0, (unsigned8)((signed16)scale*(signed16)2/(signed16)5), bright);
  displaylist((signed8 *)placard[((unsigned8)intro_rot+128)&255]);
}

static inline void show_digit(signed8 digit)
{
  displaylist(digitlist[(int)digit]);
}

static void SHOW_NUM(signed16 num, signed8 absx, signed8 absy, unsigned16 scale) { // left-aligned
  signed8 digit, zeroes;

  // If scale is 0, this draws a number starting at the current position, which is at the origin
  // of the previous number offset by the number width.

  if (scale) position_and_scale(absx, absy, 0x20);

  // This replaces code that used divide by 10 and modulo 10.  Much faster.

  // handles full 16 bit range of -32768:32767  -  Uses negative numbers to avoid the issue of negating -32768

  if (num >= 0) num = -num; else SHOW_MINUS();   // TO DO: add a font character for '-'.
  digit = 0;
  zeroes = 1; // CLRing is shorter
  // max 11 add/subtracts...
  if (num <= -20000) { num += 20000; digit += 2; zeroes = 0; }
  if (num <= -10000) { num += 10000; digit += 1; zeroes = 0; }
  if (!zeroes) show_digit(digit);
  digit = 0;
  if (num <= -8000) { num += 8000; digit += 8; zeroes = 0; } else if (num <= -4000) { num += 4000; digit += 4; zeroes = 0; }
  if (num <= -2000) { num += 2000; digit += 2; zeroes = 0; }
  if (num <= -1000) { num += 1000; digit += 1; zeroes = 0; }
  if (!zeroes) show_digit(digit);
  digit = 0;
  if (num <= -800) { num += 800; digit += 8; zeroes = 0; } else if (num <= -400) { num += 400; digit += 4; zeroes = 0; }
  if (num <= -200) { num += 200; digit += 2; zeroes = 0; }
  if (num <= -100) { num += 100; digit += 1; zeroes = 0; }
  if (!zeroes) show_digit(digit);
  digit = 0;
  if (num <= -80) { num += 80; digit += 8; zeroes = 0; } else if (num <= -40) { num += 40; digit += 4; zeroes = 0; }
  if (num <= -20) { num += 20; digit += 2; zeroes = 0; }
  if (num <= -10) { num += 10; digit += 1; zeroes = 0; }
  if (!zeroes) show_digit(digit);
  show_digit((signed8)-num);
}

static void Draw_lastscore(void)
{
  debugstr("Draw_lastscore");
  position_and_scale(-100,127,32);
  SHOW_SCORE();
  SHOW_NUM(LastScore, -98,113, 256);
}

static void Draw_highscore(void)
{
  debugstr("Draw_highscore");
  position_and_scale(40,127,32);
  SHOW_HIGH_SCORE();
  SHOW_NUM(HighScore, 52,113, 256);
}

static void Draw_insertcoin(void) // Not yet used
{
  debugstr("Draw_insertcoin");
  position_and_scale_and_intensity(-36,-80,32, global_flashing_intensity<<1);
  SHOW_INSERT_COIN();
}

static void Draw_start(void)
{
  debugstr("Draw_start");
  position_and_scale_and_intensity(-34,-80,32, global_flashing_intensity<<1);
  SHOW_PRESS_START();
}

static void Draw_credits(void)
{
  debugstr("Draw_credits");
  position_and_scale_and_intensity(-39,-30,48,0x5F);
  SHOW_CREDITS();
  if (Credits > 9U) {
    SHOW_NUM(9, 0,0,0); // display any more than 9 as 9. (This is what many arcade machines did in real life)
  } else {
    SHOW_NUM(Credits, 0,0,0);
  }
}

static void Draw_ships_remaining(void)
{
  SHOW_NUM(10, 90,120, 64);
}

static void Draw_shields_remaining(void)
{
  SHOW_NUM(80, -5,127, 128);
}

static void Draw_score(void)
{
  SHOW_NUM(Score, -100,120, 64);
}

static signed8 crosshair_x, crosshair_y;

void follow_crosshair()
{
XJoy+=XJoy;XJoy+=XJoy;YJoy+=YJoy;YJoy+=YJoy;
  // constrain joysticks but carefully avoid integer overflow
  if ((crosshair_x >= 0) && (XJoy > 0) && (crosshair_x >= 127-XJoy)) /* ignore */;
  else if ((crosshair_x < 0) && (XJoy < 0) && (crosshair_x < -127-XJoy)) /* ignore */;
  else crosshair_x += XJoy;

  if ((crosshair_y >= 0) && (YJoy > 0) && (crosshair_y >= 127-YJoy)) /* ignore */;
  else if ((crosshair_y < 0) && (YJoy < 0) && (crosshair_y < -127-YJoy)) /* ignore */;
  else crosshair_y += YJoy;

  Draw_crosshair(crosshair_x, crosshair_y);
}

static signed8 last_button1;
static void next_frame(void)
{
  // do things here that must be done on every frame, eg polling buttons/joystick
  debugstr("next_frame");
  wait_retrace();
  poll_joystick();
  if (button1 && !last_button1) { // 'A' for A Coin ;-)
    Credits += 1;
  }
  last_button1 = button1;
  if ((frame_number & 16) == 0) {
    global_flashing_intensity += 4; // 16 frames of getting brighter
  } else {
    global_flashing_intensity -= 4; // then 16 frames of getting darker
  }
  frame_number++; // wraps.
  //if (Debug) SHOW_NUM(Debug, -100,-100, 128);
}

static boolean firing;
static unsigned16 missile_scale;
static signed8 missile_x, missile_y;

static const signed8 paired_missiles[] IN_ROM = {
  0,   0,-127,
  -1,  -5,-5,                 // left to right pointing
  -1,  5,30, -1,  5,-30,
  -1,  -5,5,
  0,   0,127,    0, 0,127, // (large move in two parts)
  -1,  5,5,
  -1,  -5,-30, -1,  -5,30,   // right to left pointing
  -1,  5,-5,
  1
};

void Draw_missiles(void)
{
  position_and_scale_and_intensity(missile_x,missile_y, (unsigned8)missile_scale, MAX_BRIGHTNESS);
  displaylist(paired_missiles);

// This hack to draw both left and right missiles in one call doesn't
// look as nice as I hoped it would on the screen.  I think I am going
// to have to go back to individual left and right missiles, and
// also orient them toward the crosshair using trig, then rotate
// them using the built-in bios procedure.
}

static int *waveptr;
static int ship_scale = 1;
static int speed_toggle = 0;
void SHIPWAVE(void)
{
  int x,y;
  if (ship_scale == 1) {
    waveptr = (int *)Ship0Wave0;  // ( scale x y (drawing commands)* 1 )* 1
  }
  ship_scale = *waveptr++;
  x = *waveptr++; y = (int)((*waveptr++ + 128) * 2 - 128); // deliberately exaggerate ship motion in Y

  position_and_scale_and_intensity(x, y-60, (unsigned char)((long)ship_scale*3L/8L), MAX_BRIGHTNESS); // should a y offset be stored in the structure?
 
  displaylist((signed8 *)waveptr);

  if (speed_toggle == 0) {
    waveptr -= 3;
  } else {
    do { waveptr += 3; } while (*waveptr != 1); // step over display list
    waveptr += 1; // step over 'end of list'
    // scale, or 1 for end of wave
    if (*waveptr == 1) ship_scale = 1; // end of a sequence of frames
  }
  speed_toggle = 1-speed_toggle;
}

int main(void) { // cmoc doesn't allow argc, argv
#define STAR_SPEED 4
  signed16 frames;
  unsigned8 placard_scale, placard_brightness;
  signed8 Shots_fired;

  // I think all the static data could safely be initialised to 0 in a block
  // by the startup code.  (Possibly excepting the two arrays containing pointers
  // to const arrays).  I don't believe I have any statics that require initialisation
  // to any non-0 values.

  missile_scale = 
    (unsigned16)(Debug = Score = HighScore = LastScore = rseed = frame_number =
      (signed16)(last_button1 = crosshair_x = crosshair_y = XJoy = YJoy =
        (signed8)(Credits = global_flashing_intensity = 0)));

  init_hardware();
  init_stars();

  Score = 72; HighScore = 2047; LastScore = 132; // only for demo.
ship_scale = 1;
speed_toggle = 0;
Debug = sizeof(Ship0Wave0);

  // Attract mode

  for (;;) {
    Shots_fired = button1 = button2 = button3 = button4 = firing = 0;
//Debug=Credits;
    debugstr("main loop");
    // approaching banner:

    if (Credits == 0) {
    placard_scale = (unsigned8)((signed16)(0xFF-160));
    placard_brightness = (unsigned8)((signed16)(((signed16)MAX_BRIGHTNESS+(signed16)MAX_BRIGHTNESS-160)));
    intro_rot = 128;

    // start at 128 step by -2  end at 64 after one full revolution (128 + 32 steps)
    for (frames = 0; frames < 160; frames++) {
      next_frame();
      drawtgintro(placard_scale, placard_brightness>>1);
      placard_scale++; placard_brightness++;
      stars(STAR_SPEED);
      intro_rot -= 2;
      if (button1) break;
    }

    if (!button1) {signed16 i;
      // pulse the placard a few times...
      for (i = 0; i < 2; i++) {
        placard_brightness = MAX_BRIGHTNESS&0xF8;
        while (placard_brightness != 0) {
          next_frame();
          drawtgintro(placard_scale, placard_brightness);
          stars(STAR_SPEED);
          placard_brightness -= 8;
          if (button1) break;
        }
        if (!button1) while (placard_brightness != (MAX_BRIGHTNESS&0xF8)) {
          placard_brightness += 8;
          next_frame();
          drawtgintro(placard_scale, placard_brightness);
          stars(STAR_SPEED);
          if (button1) break;
        }
      }
    }

    // now fade it out
    if (!button1) {
      placard_brightness = MAX_BRIGHTNESS&0xF8;
      while (placard_brightness != 0) {
        next_frame();
        drawtgintro(placard_scale, placard_brightness);
        stars(STAR_SPEED);
        placard_brightness -= 8;
        if (button1) break;
      }
    }
    }

    debugstr("wait for coins...");
    global_flashing_intensity = 0; frame_number = 0; // start flashing from off position
    for (frames = 0; ; frames++) {
      next_frame();
      stars(STAR_SPEED);
      if (Credits == 0) Draw_insertcoin(); else {Draw_start(); Draw_credits(); /* -- TOO SLOW*/}
      intensity(MAX_BRIGHTNESS);
      Draw_lastscore();
      Draw_highscore();
      if (button2 && (Credits > 0)) break;  // 'S' for Start
    }
    Credits -= 1;

    // shields and crosshair - simulated play...
//  for (frames = 0; Shots_fired < 10 || firing; frames++) {
    for (frames = 0; (long)frames < 126L*2L; frames++) {
      next_frame();
      stars(STAR_SPEED);
      intensity(MAX_BRIGHTNESS);
      Draw_ships_remaining();
      Draw_shields_remaining();
      Draw_score();

      SHIPWAVE(); // need to implement passing now

      /*if (!firing) */follow_crosshair();
      if (button3) {  // 'D' for Defend
        if (pickrandom(0,31)) draw_shields(0xF0);
      }
      if (button4 && !firing && !button3) {  // 'F' for Fire. One at a time and not when shields up.
        firing = 1;    // firing position is higher than on original game for a reason :-)
        Shots_fired += 1;
        missile_x = 0; missile_y = 0;  // we treat the missile as being in the center of the ship
                                         // but *draw* missiles to the left and right of the center...
        missile_scale = 0x7f; // start from left and right edges
      }
      if (firing) {
        missile_scale -= 1U;
        if (missile_scale <= 4U) {
           firing = 0;
        } else {
          signed16 m, mx,my, delta_y;

          mx = (signed16)crosshair_x - (signed16)missile_x ;  // re-center on the missile.
          // not sure why it converges on Y so fast.  Ought to start at foot of screem
          my = (signed16)crosshair_y - (signed16)missile_y ;  // offsets may now be outside -128:127 range

          delta_y = (((my) << 5L) - my)>>5L;
          // if (delta_y > 8L) delta_y = 8L; else if (delta_y < -8L) delta_y = -8L;

          missile_x = (signed8)(((((mx) << 5L) - mx)>>5L) + (signed16)missile_x);  // move 15/16ths of the distance to crosshair
          missile_y = (signed8)((signed16)delta_y + (signed16)missile_y);  // add original offset back in

          m = (signed16)missile_scale<<3L;   // 7/8ths
          m -= (signed16)missile_scale;
          missile_scale = (unsigned8)((signed16)m>>3L);
        }
        // stop RHS from going off-screen - these would not be needed if we used individual missiles
        if (((signed16)missile_x + (signed16)missile_scale) > 127L) missile_x = (signed8)(127L-(signed16)missile_scale);
        // stop LHS from going off-screen
        if (((signed16)missile_x - (signed16)missile_scale) < -127L) missile_x = (signed8)(-127L+(signed16)missile_scale);
        Draw_missiles();
      }
    }

    // 'whoosh' when a ship got by us.
    debugstr("whoosh...");
    for (frames = 0; frames < 64; frames++) {
      next_frame();
      stars(STAR_SPEED<<1);
      intensity((unsigned8)(64-frames)*2); // fade out
      Draw_ships_remaining();
      Draw_shields_remaining();
      Draw_score();
    }
  }

  return 0;
  SHOW_N0();
  SHOW_N1();
  SHOW_N2();
  SHOW_N3();
  SHOW_N4();
  SHOW_N5();
  SHOW_N6();
  SHOW_N7();
  SHOW_N8();
  SHOW_N9();
}