#include <vectrex.h>

// compile with -O and display is consistently closer to 30,000 cycles :-)
// (however -O3 crashes on startup)

// WARNING!  This is some pretty horrendous code, and too close examination of
// it may make you go blind.  I've basically enumerated the perspective view from
// each of four quadrants manually.  I have no doubt that this code could be
// greatly simplified, but that is for another day and probably another programmer.
// One in a series of quick & dirty hacks.

// unfortunately I made the size of the display area even, i.e. with block boundaries
// running down x==0 and y==0 which require special cases for various vectors (not
// completely implemented).
//
// If I had chosen an odd-sized playfield there would be blocks running down the
// centers and no ambiguity on viewing the vectors on either side...

// I might handle that in a later revision.  Also need to consider scrolling
// by smaller units than a complete block... could be implemented using 2x2 blocks
// and only stopping on odd boundaries

// would be nice to coalesce multiple vectors into a single line...

// The only mild tweak needed is when a pillar is aligned with a horizontal/vertical in x/y
// - should suppress the shorter vector.  This only happens on the x==0 or y==0 boundary

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

// not in a position to change SIZE at the moment!
#define SIZE 2
#define ROOF_SCALE ((int)(0xFFU/SIZE))

static int sq[SIZE*2][SIZE*2];

static int ix,x;
static int iy,y;

#define MAX_VECS 780L
static int displayfile[MAX_VECS];
static long nextp = 0L;

#define RESET 0
#define RESETANDMOVE 1
#define MOVETOABS 2
#define DRAWTOREL 3
#define INTENSITY 4
#define END 5

static void Display(void) {
  static void *Case[] = { &&ResetL, &&ResetSkipL, &&MovetoAbsL, &&DrawtoRelL, &&IntensityL, &&EndL };
  int dx, dy;
  int *p = displayfile;
  for (;;) {
    // originally was switch but this is much faster
    goto *Case[*p++];
    ResetL:
      Reset0Ref();set_scale(ROOF_SCALE);Moveto_d(0,0);
      // reset0ref has to have a moveto after it when drawing from 0,0
      // most resets are followed by a move anyway...
      // so have replaced reset by reset and move so we only insert once per frame
      // old implementation was too expensive
      continue;
    ResetSkipL:
    MovetoAbsL:
      Reset0Ref();set_scale(ROOF_SCALE);
      dy = *p++; dx = *p++;
      Moveto_d(dy, dx);
      continue;
    DrawtoRelL:
      dy = *p++; dx = *p++;
      Draw_Line_d(dy,dx);
      continue;
    IntensityL:
      Intensity_a((unsigned int)*p++);
      continue;
    EndL:
      return;
  }
}

static int last = 0;
static long pendingx = 0L, pendingy = 0L;
static long beam_x = 0L, beam_y = 0L;

static inline void Moveto(int dy, int dx) {
  pendingx += (long)dx; pendingy += (long)dy;
}

static void Draw_Line(int dy, int dx) {
  if (nextp>=MAX_VECS-6L) return;

  if ((beam_x != pendingx) || (beam_y != pendingy)) {
    displayfile[nextp++] = last = MOVETOABS;
    displayfile[nextp++] = (int)(beam_y = pendingy);
    displayfile[nextp++] = (int)(beam_x = pendingx);
  }

#ifdef NEVER // not making a cycle of difference now 
  if (last == DRAWTOREL) {
    long ody = (long)displayfile[nextp-2];
    long odx = (long)displayfile[nextp-1];
    long ndy = ody+(long)dy;
    long ndx = odx+(long)dx;
    if (-128L <= ndx && ndx <= 127L && -128L <= ndy && ndy <= 127L &&
         ((odx==0L && dx==0) || (ody==0L && dy==0))) {
      displayfile[nextp-2] = (int)ndy;
      displayfile[nextp-1] = (int)ndx;
      pendingx += (long)dx; pendingy += (long)dy;
      beam_x = pendingx; beam_y = pendingy;
      return;
    }
  }
#endif

  displayfile[nextp++] = last = DRAWTOREL;
  displayfile[nextp++] = dy;
  displayfile[nextp++] = dx;
  displayfile[nextp] = END;
  pendingx += (long)dx; pendingy += (long)dy;
  beam_x = pendingx; beam_y = pendingy;
}

static void Intensity(int i) {
  if (nextp>=MAX_VECS-2L) return;
  displayfile[nextp++] = last = INTENSITY;
  displayfile[nextp++] = i;
  displayfile[nextp] = END;
}

#ifdef NEVER
static void Reset0(void) {
  if (nextp>=MAX_VECS-1L) return;
  displayfile[nextp++] = last = RESET;
  displayfile[nextp] = END;
  beam_x = pendingx = 0L; beam_y = pendingy = 0L;
}
#endif

static void ResetSkip(int x, int y) {
  if (nextp>=MAX_VECS-1L) return;
  displayfile[nextp++] = last = RESETANDMOVE;
  displayfile[nextp++] = y;
  displayfile[nextp++] = x;
  displayfile[nextp] = END;
  beam_x = pendingx = x; beam_y = pendingy = y;
}

#define RSkip(dx, dy) Moveto((int)(dy), (int)(dx))
#define RLine(dx, dy) Draw_Line((int)(dy), (int)(dx))
#define GSkip(dx, dy) Moveto((int)(dy), (int)(dx))
#define GLine(dx, dy) Draw_Line((int)(dy), (int)(dx))

#define YFAC 5
#define XFAC 6
static int dx(int x) {
  return (int)((long)x*(long)XFAC*(long)8/(long)SIZE);
}

static int dy(int y) {
  return (int)((long)y*(long)YFAC*(long)8/(long)SIZE);
}

// crashes if this is inline or macro!
// starts at roof pos, ends at ground pos
static void Pillar(int x, int y) {
  RLine(-dx(x),-dy(y));
}

static void roof(void) { // floor is half the size of roof 
#define YH ((YFAC*20)/SIZE)
#define XW ((XFAC*20)/SIZE)
  Intensity(0x6F);
  for (iy = -SIZE, y = -SIZE*YH; iy < SIZE; y+=YH, iy+=1) { 
    for (ix = -SIZE, x = -SIZE*XW; ix < SIZE; x+=XW, ix+=1) {
      ResetSkip(ix*XW, iy*YH);

      if ((sq[ix+SIZE][iy+SIZE]) || ((iy > -SIZE) && sq[ix+SIZE][iy+SIZE-1])) {
        if (iy >= 0) {
          if (sq[ix+SIZE][iy+SIZE] != sq[ix+SIZE][iy+SIZE-1]) {
            RLine(XW, 0);
          } else {
            RSkip(XW, 0);
          }
        } else if (iy < 0) { 
          if (    ((iy == -SIZE) && sq[ix+SIZE][iy+SIZE])
               || (iy > -SIZE && !sq[ix+SIZE][iy+SIZE])
               || ((iy > -SIZE) && sq[ix+SIZE][iy+SIZE] && !sq[ix+SIZE][iy+SIZE-1])) { 
            RLine(XW, 0); // horizontal line below block
          } else {
            RSkip(XW, 0);
          }
        } else {
          RSkip(XW, 0);
        }
      } else {
        RSkip(XW, 0); // horizontal below block
      }

      if (    (!sq[ix+SIZE][iy+SIZE] && (sq[ix+SIZE+1][iy+SIZE]) && (ix < SIZE-1)) // lhs of next
           || (sq[ix+SIZE][iy+SIZE] && !sq[ix+SIZE+1][iy+SIZE] && (ix < SIZE-1))
           || (ix == SIZE-1 && sq[ix+SIZE][iy+SIZE])
         ) {                // rhs
        RLine(0, YH);
      } else {
        RSkip(0, YH); // rhs verticals
      }

      if (iy == SIZE-1) {
        if (sq[ix+SIZE][iy+SIZE]) {
          RLine(-XW, 0);
        } else {
          RSkip(-XW, 0); // top row horizontals
        }
        RSkip(XW, 0);
      }
      RSkip(0, -YH);
    }
    // ix would be -SIZE if it were set here.
    ResetSkip(-SIZE*XW, y);
    ix = -SIZE;
    if (sq[ix+SIZE][iy+SIZE]) {
      RLine(0, YH); // leftmost vertical
    }
  }
}

static void support_nearest_center(void) {
  Intensity(0x40);
  for (iy = -SIZE, y = -SIZE*YH; iy <= SIZE; y+=YH, iy+=1) {
    for (ix = -SIZE, x = -SIZE*XW; ix <= SIZE; x+=XW, ix+=1) {
      if (ix||iy) {
        ResetSkip(x, y);
        if (ix <= 0) {
          if (iy <= 0) {
            // diagonals at bottom left of sq[ix+SIZE][iy+SIZE]         
            if (!sq[ix+SIZE][iy+SIZE]) { // solid squares occlude themselves
              if (ix == -SIZE) {
                if (iy != -SIZE) {
                  if (sq[ix+SIZE][iy+SIZE-1]) {
                    Pillar(ix, iy);
                  }
                }
              } else {
                if (iy == -SIZE) {
                  if (sq[ix+SIZE-1][iy+SIZE]) Pillar(ix, iy);
                } else {
                  if (!sq[ix+SIZE-1][iy+SIZE] && !sq[ix+SIZE-1][iy+SIZE-1] && sq[ix+SIZE][iy+SIZE-1]) {
                    Pillar(ix, iy);
                  } if (sq[ix+SIZE-1][iy+SIZE] && !sq[ix+SIZE-1][iy+SIZE-1] && !sq[ix+SIZE][iy+SIZE-1]) {
                    Pillar(ix, iy);
                  } else if (sq[ix+SIZE-1][iy+SIZE] && sq[ix+SIZE][iy+SIZE-1]) {
                    Pillar(ix, iy);
                  } else if (sq[ix+SIZE-1][iy+SIZE-1] && !sq[ix+SIZE-1][iy+SIZE] && !sq[ix+SIZE][iy+SIZE-1]) {
                    Pillar(ix, iy);
                  }
                }
              }
            }
          } else if (iy > 0) {
            // diagonal is at lower left corner of sq[ix][iy]
            if (ix == -SIZE) {
              if (iy < SIZE) {
                if (sq[ix+SIZE][iy+SIZE] && !sq[ix+SIZE][iy+SIZE-1]) {
                  Pillar(ix, iy);
                }
              }
            } else {
              if (iy == SIZE) {
                if (sq[ix+SIZE-1][iy+SIZE-1] && !sq[ix+SIZE][iy+SIZE-1]) {
                  Pillar(ix, iy);
                }
              } else {
                // diagonal is at lower left corner of sq[ix][iy]
                if (!sq[ix+SIZE][iy+SIZE-1]) {
                  if (sq[ix+SIZE-1][iy+SIZE-1] && !sq[ix+SIZE-1][iy+SIZE] && !sq[ix+SIZE][iy+SIZE-1]) {
                    Pillar(ix, iy);
                  } else if (sq[ix+SIZE-1][iy+SIZE] && !sq[ix+SIZE][iy+SIZE] && !sq[ix+SIZE-1][iy+SIZE-1]) {
                    Pillar(ix, iy);
                  } else if (sq[ix+SIZE-1][iy+SIZE-1] && sq[ix+SIZE][iy+SIZE]) {
                    Pillar(ix, iy);
                  } else if (!sq[ix+SIZE-1][iy+SIZE] && sq[ix+SIZE][iy+SIZE]) {
                    Pillar(ix, iy);
                  } else if (sq[ix+SIZE-1][iy+SIZE-1] && !sq[ix+SIZE][iy+SIZE] && !sq[ix+SIZE-1][iy+SIZE-1]) {
                    Pillar(ix, iy);
                  }
                }
              }
            }
          } 
        } else if (ix > 0) { // ALL DONE EXCEPT THE ZERO SPECIAL CASES
          if (iy > 0) { // DONE
            // iy == SIZE is the invisible row above the area
            // diagonals are drawn at bottom left corner of sq[ix+SIZE][iy+SIZE]
            if (ix == SIZE) {
              if ((iy < SIZE) && sq[ix+SIZE-1][iy+SIZE] && !sq[ix+SIZE-1][iy+SIZE-1]) {
                Pillar(ix, iy);
              }
            } else {
              if (iy == SIZE) {
                if (sq[ix+SIZE][iy+SIZE-1] && !sq[ix+SIZE-1][iy+SIZE-1]) {
                  Pillar(ix, iy);
                }
              } else {
                if (sq[ix+SIZE][iy+SIZE] && !sq[ix+SIZE-1][iy+SIZE-1] && !sq[ix+SIZE][iy+SIZE-1]) {
                  if (!sq[ix+SIZE-1][iy+SIZE]) {
                    Pillar(ix, iy);
                  }
                } else if (!sq[ix+SIZE][iy+SIZE] && !sq[ix+SIZE-1][iy+SIZE-1] &&
                     (sq[ix+SIZE-1][iy+SIZE] || sq[ix+SIZE][iy+SIZE-1])) {
                  Pillar(ix, iy);
                } else if (!sq[ix+SIZE-1][iy+SIZE-1]) {
                  // diagonals are drawn at bottom left corner of sq[ix+SIZE][iy+SIZE]
                  // space left and below is free to draw into
                  if (  ((sq[ix+SIZE-1][iy+SIZE] && sq[ix+SIZE][iy+SIZE-1]))
                     || (sq[ix+SIZE][iy+SIZE] && !sq[ix+SIZE-1][iy+SIZE] && !sq[ix+SIZE][iy+SIZE-1])) {
                    Pillar(ix, iy);
                  }
                }
              }
            }
          } else if (iy <= 0) { // DONE!
            // diagonal at top-left corner of sq[ix+SIZE][iy+SIZE]
            if (ix == SIZE) { // absent row to right of area
              if (iy == -SIZE) { // absent row below area
              } else {
                if (sq[ix+SIZE-1][iy+SIZE-1] && !sq[ix+SIZE-1][iy+SIZE]) {
                  Pillar(ix, iy);
                }
              }
            } else {
              // ALWAYS draw diagonal at bottom-left corner of sq[ix+SIZE][iy+SIZE]
              if (iy == -SIZE) {
                if (!sq[ix+SIZE-1][iy+SIZE] && sq[ix+SIZE][iy+SIZE]) {
                  Pillar(ix, iy);
                }
              } else if (!sq[ix+SIZE-1][iy+SIZE]) {
                // room to draw into
                if (sq[ix+SIZE][iy+SIZE]) {
                  if (sq[ix+SIZE-1][iy+SIZE-1]) {
                    Pillar(ix, iy);
                  } else {
                    if (!sq[ix+SIZE][iy+SIZE-1]) {
                      Pillar(ix, iy);
                    }
                  }
                } else if (sq[ix+SIZE-1][iy+SIZE-1]) {
                  if (!sq[ix+SIZE][iy+SIZE-1]) {
                    Pillar(ix, iy);
                  }
                } else if (sq[ix+SIZE][iy+SIZE-1]) {
                  Pillar(ix, iy);
                }
              }
            }
          } else {
            // iy == 0 - special case?
          }
        } else {
          // center - ignore
        }
      }
    }
  }
}

static void ground(void) {
#undef YH
#undef XW
// 2/3rds of roof scale
#define YH ((YFAC*12)/SIZE)
#define XW ((XFAC*12)/SIZE)
  Intensity(0x5F); 
  for (iy = -SIZE, y = -SIZE*YH; iy < SIZE; y+=YH, iy+=1) { 
    for (ix = -SIZE, x = -SIZE*XW; ix < SIZE; x+=XW, ix+=1) {
      ResetSkip(ix*XW, iy*YH);     // small fudge factor for positioning
      if (iy > 0) {
        if (ix >= 0) {
          // truncate on left
          if (sq[ix+SIZE][iy+SIZE] && !sq[ix+SIZE][iy+SIZE-1] ) {
            if (sq[ix+SIZE-1][iy+SIZE-1]) {
              GSkip(dx(ix), 0); GLine(XW - dx(ix), 0);  
            } else {
              GLine(XW, 0);
            }
          } else {
            GSkip(XW, 0);
          }
        } else {
          // truncate on right
          if (sq[ix+SIZE][iy+SIZE] && !sq[ix+SIZE][iy+SIZE-1] ) {
            if (sq[ix+SIZE+1][iy+SIZE-1]) { // do we extend under a cube?
              GLine(XW + dx(ix+1), 0); GSkip(-dx(ix+1), 0);
            } else {
              GLine(XW, 0);
            }
          } else {
            GSkip(XW, 0);
          }
        }
      } else if (iy < 0) {
        if ((iy > -SIZE) && sq[ix+SIZE][iy+SIZE-1] && !sq[ix+SIZE][iy+SIZE] ) {
          if (ix >= 0) { // >
            // truncate on left
            if (sq[ix+SIZE-1][iy+SIZE]) {
              GSkip(dx(ix), 0); GLine(XW - dx(ix), 0);  
            } else {
              GLine(XW, 0);
            }
          } else {
            if (sq[ix+SIZE+1][iy+SIZE]) { // do we extend under a cube?
              GLine(XW + dx(ix+1), 0); GSkip(-dx(ix+1), 0);
            } else {
              GLine(XW, 0);
            }
          }
        } else {
          GSkip(XW, 0);
        }
      } else {
        GSkip(XW, 0);
      }

      // now do vertical overshoots...

      if ((ix >= 0) && (ix < SIZE-1)) {
        if (!sq[ix+SIZE][iy+SIZE] && sq[ix+SIZE+1][iy+SIZE]) {
          if (iy >= 0) {
            // extends down
            if (sq[ix+SIZE][iy+SIZE-1]) {
              GSkip(0, dx(iy)); GLine(0, YH-dy(iy));
            } else {
              GLine(0, YH);
            }
          } else {
            // extends up

            // for an unknown reason one in a small number of the vectors below
            // is mis-positioned.  This bodges it back.  COYOTE UGLY
            ResetSkip(x+XW, y);

            if (sq[ix+SIZE][iy+SIZE+1]) {
              GLine(0, YH+dy(iy+1)); GSkip(0, -dy(iy+1));
            } else {
              GLine(0, YH);
            }
          }
        } else {
          GSkip(0, YH);
        }
      } else if (ix < 0) {
        if (sq[ix+SIZE][iy+SIZE] && !sq[ix+1+SIZE][iy+SIZE]) {
          if (iy >= 0) {
            if (sq[ix+SIZE+1][iy+SIZE-1]) {
              GSkip(0, dy(iy)); GLine(0, YH-dy(iy));
            } else {
              GLine(0, YH);
            }
          } else {
            // lower left quadrant incl center line
            if (sq[ix+SIZE+1][iy+SIZE+1]) {
              GLine(0, YH+dy(iy+1)); GSkip(0, dy(iy+1)); 
            } else {
              GLine(0, YH);
            }

          }
        } else {
          GSkip(0, YH);
        }
      } else {
        GSkip(0, YH); // vertical at rhs of block
      }
      GSkip(0, -YH);
    }
    ResetSkip(-SIZE*XW, y);
  }
}

static unsigned int _x, _a, _b, _c; 
static 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);
}

static int 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 (int)_c;
}

static unsigned int Prev_Btn_State = 0;
int main(void)
{
  int x, y;
  initRandom(12,34,56,78); (void)random();
  for (y = -SIZE; y < SIZE; y++) {
    for (x = -SIZE; x < SIZE; x++) {
      sq[x+SIZE][y+SIZE] = random()&1;
    }
  }

  Read_Btns();
  nextp = 0;
  roof();
  support_nearest_center();
  ground();
  for (;;) {
    Prev_Btn_State = Vec_Btn_State;
    Read_Btns();
    Wait_Recal();
    Display();
    if (((Vec_Btn_State&12) == 12) && (Vec_Btn_State != Prev_Btn_State)) {
      for (y = -SIZE; y < SIZE; y++) {
        for (x = -SIZE; x < SIZE; x++) {
          sq[x+SIZE][y+SIZE] = random()&1;
        }
      }
      nextp = 0;
      roof();
      support_nearest_center();
      ground();
    }
  }
  return 0;
}