#include <vectrex.h>
#include "controller.h"

// set -O3 in the gcc options.

// This creates a 6x6 unit play area similar to Spike Gets Squished except
// that the procedurally-generated world is static, meaning that you can
// return to any part of the map that you moved away from.

// It's quite complex code to get right so I'm sharing this with Brett just
// as an example of the technique, not to put in his game which is fine the
// way it is.  It might come in useful for some new game some day in the future.

// Performance is good, staying close to 30K cycles throughout.

static inline void displaylist(const int *list) { Draw_VLp((int *)list); }

#define IN_ROM
static inline void set_scale(unsigned int scale) {*(volatile unsigned char *)0xD004 = scale;}
#define DEFINE(name) static const int name[] IN_ROM = {
#define move_rel_xy(x,y) 0 /*MOVE*/, ((int)y), ((int)x)
#define line_rel_xy(x,y) (int)-1 /*LINE*/, ((int)y), ((int)x)
#define ENDDEF(name) 1 /*STOP*/ }; static void SHOW_##name(void) { displaylist(name); }

DEFINE(MINUS)
  move_rel_xy(4,8),  // to 8,4
  line_rel_xy(14,0), // to 8,20
  move_rel_xy(4,-8), // to 0,24
ENDDEF(MINUS)

DEFINE(N0)
  move_rel_xy(0,24), // to 0,24
  line_rel_xy(16,0), // to 16,24
  line_rel_xy(0,-24), // to 16,0
  line_rel_xy(-16,0), // to 0,0
  line_rel_xy(0,24), // to 0,24
  move_rel_xy(24,-24), // to 24,0
ENDDEF(N0)

DEFINE(N1)
  move_rel_xy(8,0), // to 8,0
  line_rel_xy(0,24), // to 8,24
  move_rel_xy(16,-24), // to 24,0
ENDDEF(N1)

DEFINE(N2)
  move_rel_xy(0,24), // to 0,24
  line_rel_xy(16,0), // to 16,24
  line_rel_xy(0,-12), // to 16,12
  line_rel_xy(-16,0), // to 0,12
  line_rel_xy(0,-12), // to 0,0
  line_rel_xy(16,0), // to 16,0
  move_rel_xy(8,0), // to 24,0
ENDDEF(N2)

DEFINE(N3)
  line_rel_xy(16,0), // to 16,0
  move_rel_xy(-8,12), // to 8,12
  line_rel_xy(8,0), // to 16,12
  move_rel_xy(0,-12), // to 16,0
  line_rel_xy(0,24), // to 16,24
  line_rel_xy(-16,0), // to 0,24
  move_rel_xy(24,-24), // to 24,0
ENDDEF(N3)

DEFINE(N4)
  move_rel_xy(0,24), // to 0,24
  line_rel_xy(0,-12), // to 0,12
  line_rel_xy(16,0), // to 16,12
  move_rel_xy(-8,0), // to 8,12
  line_rel_xy(0,-12), // to 8,0
  move_rel_xy(16,0), // to 24,0
ENDDEF(N4)

DEFINE(N5)
  line_rel_xy(16,0), // to 16,0
  line_rel_xy(0,12), // to 16,12
  line_rel_xy(-16,0), // to 0,12
  line_rel_xy(0,12), // to 0,24
  line_rel_xy(16,0), // to 16,24
  move_rel_xy(8,-24), // to 24,0
ENDDEF(N5)

DEFINE(N6)
  move_rel_xy(0,12), // to 0,12
  line_rel_xy(16,0), // to 16,12
  line_rel_xy(0,-12), // to 16,0
  line_rel_xy(-16,0), // to 0,0
  line_rel_xy(0,24), // to 0,24
  move_rel_xy(24,-24), // to 24,0
ENDDEF(N6)

DEFINE(N7)
  move_rel_xy(8,0), // to 8,0
  line_rel_xy(8,24), // to 16,24
  line_rel_xy(-16,0), // to 0,24
  move_rel_xy(24,-24), // to 24,0
ENDDEF(N7)

DEFINE(N8)
  line_rel_xy(16,0), // to 16,0
  move_rel_xy(-16,24), // to 0,24
  line_rel_xy(16,0), // to 16,24
  move_rel_xy(-16,-12), // to 0,12
  line_rel_xy(16,0), // to 16,12
  move_rel_xy(0,12), // to 16,24
  line_rel_xy(0,-24), // to 16,0
  move_rel_xy(-16,24), // to 0,24
  line_rel_xy(0,-24), // to 0,0
  move_rel_xy(24,0), // to 24,0
ENDDEF(N8)

DEFINE(N9)
  move_rel_xy(16,24), // to 16,24
  line_rel_xy(-16,0), // to 0,24
  line_rel_xy(0,-12), // to 0,12
  line_rel_xy(16,0), // to 16,12
  move_rel_xy(0,12), // to 16,24
  line_rel_xy(0,-24), // to 16,0
  move_rel_xy(4,0), // to 24,0
ENDDEF(N9)

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

static inline void show_digit(int digit)
{
  displaylist(digitlist[(int)digit]);
}
static void position_and_scale(int absx, int absy, unsigned int scale)
{
  Reset0Ref();
  set_scale(0x7f);
  Moveto_d(absy, absx);
  set_scale(scale);
}
static void SHOW_SIGNED_NUM(long int num, int absx, int absy, unsigned int scale) { // left-aligned
  int 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((int)-num);
}

static void SHOW_UNSIGNED_NUM(unsigned long int num, int absx, int absy, unsigned int scale) { // left-aligned
  int 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

  digit = 0;
  zeroes = 1; // CLRing is shorter
  // max 11 add/subtracts...
  if (num >= 40000) { num -= 40000UL; digit += 4; zeroes = 0; }
  if (num >= 20000) { num -= 20000UL; digit += 2; zeroes = 0; }
  if (num >= 10000) { num -= 10000UL; 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((int)num);
}

static unsigned int horiz[8]; // each byte is 8 x's
static unsigned int vert[8];  // each byte is 8 y's

static const unsigned int bitpos[8] = { 128U, 64U, 32U, 16U, 8U, 4U, 2U, 1U };
static const unsigned int notbitpos[8] = { ~128U, ~64U, ~32U, ~16U, ~8U, ~4U, ~2U, ~1U };

static inline unsigned int vertical(unsigned int x, unsigned int y) {
  // each byte is 8 y's
  return (vert[x]&bitpos[y]); // 0 or non-zero
}

static inline void setvertical(unsigned int x, unsigned int y) {    
  // each byte is 8 y's
  vert[x] |= bitpos[y];
}
static inline void clrvertical(unsigned int x, unsigned int y) {
  // each byte is 8 y's
  vert[x] &= notbitpos[y];
}
static inline void assignvertical(unsigned int x, unsigned int y, unsigned int bitvalue) {
  // each byte is 8 y's
  vert[x] &= notbitpos[y];
  if (bitvalue) vert[x] |= bitpos[y];
}


static inline unsigned int horizontal(unsigned int x, unsigned int y) {
  // each byte is 8 x's
  return horiz[y]&bitpos[x]; // 0 or non-zero
}

static inline void sethorizontal(unsigned int x, unsigned int y) {    
  // each byte is 8 x's
  horiz[y] |= bitpos[x];
}
static inline void clrhorizontal(unsigned int x, unsigned int y) {
  // each byte is 8 x's
  horiz[y] &= notbitpos[x];
}
static inline void assignhorizontal(unsigned int x, unsigned int y, unsigned int bitvalue) {
  // each byte is 8 x's
  horiz[y] &= notbitpos[x];
  if (bitvalue) horiz[y] |= bitpos[x];
}

#define XMAG 15 // See https://www.facebook.com/groups/vectrex/posts/1523916847818687/
#define YMAG 13
#define XBASE 16
#define YBASE 8
static inline void DrawHorizontal(int xl, int xr, int y) { // coordinate range is 0:15,0:15
    Reset0Ref();
    Moveto_d((y-4)*YMAG+YBASE, (xl-4)*XMAG+XBASE);//  0..7 -> -4..3
    Draw_Line_d(0, (xr-xl)*XMAG); // draw longest line possible acrld
}

static inline void DrawVertical(int x, int yb, int yt) { // coordinate range is 0:15,0:15
    Reset0Ref();
    Moveto_d((yb-4)*YMAG+YBASE, (x-4)*XMAG+XBASE);//  0..7 -> -4..3
    Draw_Line_d((yt-yb)*YMAG, 0);
}

unsigned int _x;
unsigned int _a;
unsigned int _b;
unsigned int _c;
void initRandom (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);
}

unsigned int random8 (void) {
  _x++;
  _a = (_a ^ _c ^ _x);
  _b = (_b + _a);
  _c = ((_c + (_b >> 1)) ^ _a);
  return _c;
}

static int countdown_timer = 0;
#define autorepeat_rate 12
static int dirx = 0, diry = 0;

// An arbitrary 8x8 rectangle is found within 4 aligned 8x8 rectangles
// The aligned ones use global coordinates aligned on an 8x8 grid to ensure reproducibility.
// Scrolling is done by moving the inner rectangle until its origin is no longer
// within the master lower-left aligned rectangle, at which point the grid is moved
// 8 units at a time
//
//  * * * * * * * *   * * * * * * * *
//  * * * * * * * *   * * * * * * * *
//  * * * * * * * *   * * * * * * * *
//  * * * * * * * *   * * * * * * * *
//  * * * * * * * *   * * * * * * * *
//  * * * * * + - -   - - - - + * * *
//  * * * * * | * *   * * * * | * * * 
//  * * * * * | * *   * * * * | * * *
//
//  * = = = = | = *   * * * * | * * *
//  # * * * * | * #   * * * * | * * *
//  # * * * * | * #   * * * * | * * *
//  # * * * * | * #   * * * * | * * *
//  # * * * * + - -   - - - - + * * * <- yoff=3
//  # * * * * * * #   * * * * * * * *
//  # * * * * * * #   * * * * * * * *
//  * = = = = = = *   * * * * * * * *
//  ^         ^
//  |         xoff=5
//  Origin x,y
//
// The 2x2 rectangles are not stored - they are calculated on
// the fly using an idempotent hash.  The inner rectangle *is*
// stored, and re-generated on any scrolling move.

// the _off variables may vary by 1 at a time until they reach 8 or ffff at which
// point an 8-bit re-basing is required.

// start in the middle of a large virtual world space - I haven't yet
// considered what happens at 0/65535 :-)  I'm assuming no-one would
// ever get there unless they're deliberately trying to crash it, and
// frankly who cares of they do or not!
static unsigned long global_x        = 32768UL;      // Y coord of specific bit
static unsigned long global_y        = 32768UL;      // Y coord of *BASE* of Y array

#define HORIZONTAL 0x55
#define VERTICAL   0xAA

unsigned int hash(unsigned long L1, unsigned long L2, unsigned int i) {
  unsigned long int HASH;
  // This part would have to be tweaked for generating any specific type of layout.

  // 4 is rightmost column 5-6
  // 8 is one left of that 4-5
  // 16 is one left of that 3-4
  // 32 is one left of that 2-3
  // 64 is one left of that 1-2
  // 128 is leftmost column 0-1

  HASH  = (L1^L2) >> 5UL;
  HASH ^= (L1^L2) << 3UL;
  HASH  = (HASH>>8UL) ^ HASH;
  return (unsigned int)(HASH ^ i); 
}

void assign_bitmap(void) { // use 8x8, simpler than our reduced 7x7
                           // i.e scrolls on an 8x8 basis, even though
                           // display is 7x7
  // uses parameters global_x, global_y which are the bottom left corner of our 8x8 window
  long unsigned int x, y;
  unsigned long global_x_origin; // Y coord of leftmost bit if X array
  unsigned int  global_x_off;    // offset of specific bit in 8-bit X byte
  unsigned long global_y_origin; // Y coord of *BASE* of Y array
  unsigned int  global_y_off;    // Y coord of *BASE* of Y array
  unsigned long longi;

  // we handle x and y totally separately - in one, x is row-major,
  // and in the other x is column-major...
  global_x_origin = global_x>>3UL;
  global_x_off    = (unsigned int)global_x&7;

  global_y_origin = global_y>>3UL;
  global_y_off    = (unsigned int)global_y&7;

  // we are getting 8 bits of x data at a time, for 8 different y values.
  // Unless y0 is aligned with the bottom of the base block, we may have
  // to straddle both the base block and the one above it to get all the data

  for (y = global_y; y < global_y+8; y++) {  // (y-global_y) is 0..7
    longi = ((unsigned long)hash(y, global_x_origin, HORIZONTAL)<<8UL)
          | ((unsigned long)hash(y, global_x_origin+1UL, HORIZONTAL));
    // for leftmost bit pos 0..7 we want to rightshift 8..1
    horiz[y-global_y] = (unsigned int)(longi >> (unsigned long)(8-global_x_off));
    // 128 is leftmost, 4 is rightmost
  }
  // now the same with x and y swapped
  for (x = global_x; x < global_x+8; x++) {  // (x-global_x) is 0..7
    longi = ((unsigned long)hash(x, global_y_origin, VERTICAL)<<8UL)
          | ((unsigned long)hash(x, global_y_origin+1UL, VERTICAL));
    vert[x-global_x] = (unsigned int)(longi >> (unsigned long)(8-global_y_off));
  }
  // 0 is the leftmost wall, and the bottom wall
  horiz[0] = 0xfc; horiz[6] = 0xfc; // Add walls.
  vert[0]  = 0xfc; vert[6]  = 0xfc;
}

int main(void) {
  unsigned int y = 0, x = 0;
  int i = 0;
  countdown_timer = 0;
  enable_controller_1_x();
  enable_controller_1_y();
  assign_bitmap();
  for (;;) {
    Wait_Recal();
    check_joysticks(); check_buttons();
    Intensity_a(60);
    if (button_1_1_held()) { // leftmost button for internal debugging
      SHOW_SIGNED_NUM((long)countdown_timer, -18,120,255);
      SHOW_UNSIGNED_NUM(global_x, -88,-100,255);
      SHOW_UNSIGNED_NUM(global_y, -122,-85,255);
    }
    set_scale(255);
    Intensity_a(80);
    // Draw horizontal lines first:
    for (y = 0; y < 7; y++ ) {
      if (horiz[y] == 0) continue;
      x = 0;
      for (;;) {
        if (horizontal(x,y)) {
          unsigned int x0=x;
          do x++; while ((x < 6) && (horizontal(x,y)));
          // A line starts here at x,y.  It may consist of more than one segment.
          DrawHorizontal((int)x0, (int)x, (int)y);
        } else x++;
        if (x >= 6) break;
      }
    }
    // Then vertical lines
    for (x = 0; x < 7; x++ ) {
      if (vert[x] == 0) continue;
      y = 0;
      for (;;) {
        if (vertical(x,y)) {
          unsigned int y0 = y;
          // A line starts here at x,y.  It may consist of more than one segment.
          do y++; while ((y < 6) && (vertical(x,y)));
          DrawVertical((int)x, (int)y0, (int)y); // line from y=0 to y=1 up to y=14 to y=15
        } else y++;
        if (y >= 6) break;
      }
    }
    // scroll?
    if (countdown_timer) {
      countdown_timer -= 1;
    } else {
      i = joystick_1_x();
      if (i < -40) {
        dirx = -1;
      } else if (i > 40) {
        dirx = 1;
      }
      i = joystick_1_y();
      if (i < -40) {
        diry = -1;
      } else if (i > 40) {
        diry = 1;
      }
    }
    if (((dirx|diry) != 0) && (countdown_timer == 0)) {
      global_x += (unsigned long)dirx;
      global_y += (unsigned long)diry;
      dirx = diry = 0; countdown_timer = autorepeat_rate;
      assign_bitmap();
    }
  }
  return 0;
  (void) &SHOW_N0;  (void) &SHOW_N1;  (void) &SHOW_N2;  (void) &SHOW_N3;
  (void) &SHOW_N4;  (void) &SHOW_N5;  (void) &SHOW_N6;  (void) &SHOW_N7;
  (void) &SHOW_N8;  (void) &SHOW_N9;  (void) &SHOW_MINUS;
  (void) &SHOW_SIGNED_NUM;  (void) &SHOW_UNSIGNED_NUM;
}