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

#ifndef TRUE
#define TRUE (0==0)
#define FALSE (!TRUE)
#endif

static unsigned char current_intensity = 127;

#define HEIGHT 30
#define HALF_HEIGHT 15
#define WIDTH 30
#define HALF_WIDTH 15

#define tx(x) (x)
#define ty(y) (y)
void line(int xl, int yb, int xr, int yt) {
  int a,b;
  int dx, dy;
  Intensity_a(current_intensity);
  Reset0Ref();
  a=ty(yb); b=tx(xl);
  Moveto_d((int)(a<<2), (int)(b<<2));
  dy = ty(yt)-a; dx = tx(xr)-b;
  Draw_Line_d((int)(dy<<2), (int)(dx<<2));
}

// original author 'CatOnTreeStudio' at scratch.mit.edu
// hacked by gtoal@gtoal.com for Vectrex

// To do: use perspective spacing for walls as in
// https://scratch.mit.edu/projects/157841571/
// (Currently spacing is linear and doesn't look quite right)

#define KEY_RIGHT 0
#define KEY_LEFT 1
#define KEY_UP 2
#define KEY_DOWN 3
#define KEY_ANY 4

int key(int code) {
  unsigned int bit[5] = { 0x88, 0x44, 0x22, 0x11, 0xFF };
  return (int)buttons_pressed() & (int)bit[code];
}

#define FADE 8
void intensity(int i) {
  Intensity_a(current_intensity = (unsigned char)i);
}

static int
  rotation=1, KeyPressOn=0;

static int
  dist,
  stepX=0,
  stepY=0,
  Size_X,
  Size_Y,
  HeroPosY,
  HeroPosX,
  ScreenSizeX,
  ScreenSizeY,
  ScreenPosX,
  ScreenPosY;

// The original scratch code supported coloured blocks and the different
// codes (1:7) below represent different coloured blocks (walls).
// (9 is a corridor space and I think 8 was for an object in a space)
// If the maze is pre-built like this, using a large array is OK as it
// is const and therefore in ROM space.  However if you need dynamic
// dungeon generation then the array could be shrunk considerably by
// replacing each byte with a single bit (since the vectrex only
// supports one colour), and packing 8 bits per byte.

// (the fenceposts are because Scratch's arrays are based at 1 rather than 0.
// - easier to add an exra element than to adjust all the indexes)
const char *Level[] = {
  "1111111111111111111111"
  "1111111111111111111111",
  "1199919119991199991111", // <-- starts in northwest corner (2,2)
  "1191119999199994491111",
  "1191111119194444491111",
  "1191111119199999499911",
  "1199999989111944444911",
  "1191111939111999494911",
  "1191199939991119994911",
  "1111193333391119199911",
  "1199199933399919111111",
  "1191111933333919999111",
  "1199999939933919119911",
  "1191111933933919111911",
  "1191111933933911111911",
  "1191191993999999199911",
  "1199991193933919991911",
  "1191191993933911111911",
  "1191191933933911999911",
  "1191191993999911111211",
  "1111111113333111111111",
  "1111111111111111111111",
};

// left and right wall code could be merged into one procedure at the expense of clarity.
// I'm OK with the redundancy for now.

void DrawLeftWall (int WallPosXLeft, int WallPosYUp, int WallPosYDown, int stepX, int stepY) {
  if (Level[(HeroPosY + dist * stepY - stepX)][(HeroPosX + dist * stepX + stepY)] > '7') {
    if (Level[(HeroPosY + (dist + 1) * stepY - stepX)][(HeroPosX + (dist + 1) * stepX + stepY)] > '7') {
      if (WallPosYUp - (HALF_WIDTH + (dist + 1) * Size_X) * 3/8 != 0) {
        line(WallPosXLeft + dist * Size_X, WallPosYUp - (HALF_WIDTH + dist * Size_X) * 3/8,
             WallPosXLeft + (dist + 1) * Size_X, WallPosYUp - (HALF_WIDTH + (dist + 1) * Size_X) * 3/8);
      }
      if (WallPosYDown + (dist + 1) * Size_Y != 0) {
        line(WallPosXLeft + (dist + 1) * Size_X, WallPosYUp - (dist + 1) * Size_Y,
             WallPosXLeft + (dist + 1) * Size_X, WallPosYDown + (dist + 1) * Size_Y);
        line(WallPosXLeft + (dist + 1) * Size_X, WallPosYDown + (HALF_WIDTH + (dist + 1) * Size_X) * 3/8,
             WallPosXLeft + dist * Size_X, WallPosYDown + (HALF_WIDTH + dist * Size_X) * 3/8);
      }
    } else {
      if (WallPosYUp - (dist + 1) * Size_Y != 0) {
        line(WallPosXLeft + dist * Size_X, WallPosYUp - (dist + 1) * Size_Y,
             WallPosXLeft + (dist + 1) * Size_X, WallPosYUp - (dist + 1) * Size_Y);
        line(WallPosXLeft + (dist + 1) * Size_X, WallPosYUp - (dist + 1) * Size_Y,
             WallPosXLeft + (dist + 1) * Size_X, WallPosYDown + (dist + 1) * Size_Y);
        line(WallPosXLeft + (dist + 1) * Size_X, WallPosYDown + (dist + 1) * Size_Y,
             WallPosXLeft + dist * Size_X, WallPosYDown + (dist + 1) * Size_Y);
      }
    }
  } else {
    if (WallPosYDown + (dist + 1) * Size_Y != 0) {
      line(WallPosXLeft + dist * Size_X, WallPosYUp - dist * Size_Y,
           WallPosXLeft + (dist + 1) * Size_X, WallPosYUp - (dist + 1) * Size_Y);
      line(WallPosXLeft + (dist + 1) * Size_X, WallPosYUp - (dist + 1) * Size_Y,
           WallPosXLeft + (dist + 1) * Size_X, WallPosYDown + (dist + 1) * Size_Y);
      line(WallPosXLeft + (dist + 1) * Size_X, WallPosYDown + (dist + 1) * Size_Y,
           WallPosXLeft + dist * Size_X, WallPosYDown + dist * Size_Y);
    }
  }
}

void DrawRightWall (int WallPosXRight, int WallPosYUp, int WallPosYDown, int stepX, int stepY) {
  if (Level[(HeroPosY + dist * stepY + stepX)][(HeroPosX + dist * stepX - stepY)] > '7') {
    if (Level[(HeroPosY + (dist + 1) * stepY + stepX)][(HeroPosX + (dist + 1) * stepX - stepY)] > '7') {
      if (WallPosYUp - (dist + 1) * Size_Y != 0) {
        line(WallPosXRight - dist * Size_X, WallPosYUp - (HALF_WIDTH + dist * Size_X) * 3/8,
             WallPosXRight - (dist + 1) * Size_X, WallPosYUp - (HALF_WIDTH + (dist + 1) * Size_X) * 3/8);
        line(WallPosXRight - (dist + 1) * Size_X, WallPosYUp - (dist + 1) * Size_Y,
             WallPosXRight - (dist + 1) * Size_X, WallPosYDown + (dist + 1) * Size_Y);
        line(WallPosXRight - (dist + 1) * Size_X, WallPosYDown + (HALF_WIDTH + (dist + 1) * Size_X) * 3/8,
             WallPosXRight - dist * Size_X, WallPosYDown + (HALF_WIDTH + dist * Size_X) * 3/8);
      }
    } else {
      if (WallPosYUp - (dist + 1) * Size_Y != 0) {
        line(WallPosXRight - dist * Size_X, WallPosYUp - (dist + 1) * Size_Y,
             WallPosXRight - (dist + 1) * Size_X, WallPosYUp - (dist + 1) * Size_Y);
        line(WallPosXRight - (dist + 1) * Size_X, WallPosYUp - (dist + 1) * Size_Y,
             WallPosXRight - (dist + 1) * Size_X, WallPosYDown + (dist + 1) * Size_Y);
        line(WallPosXRight - (dist + 1) * Size_X, WallPosYDown + (dist + 1) * Size_Y,
             WallPosXRight - dist * Size_X, WallPosYDown + (dist + 1) * Size_Y);
      }
    }
  } else {
    if (WallPosYDown + (dist + 1) * Size_Y != 0) {
      line(WallPosXRight - dist * Size_X, WallPosYDown + dist * Size_Y,
           WallPosXRight - dist * Size_X, WallPosYUp - dist * Size_Y);
      line(WallPosXRight - dist * Size_X, WallPosYUp - dist * Size_Y,
           WallPosXRight - (dist + 1) * Size_X, WallPosYUp - (dist + 1) * Size_Y);
      line(WallPosXRight - (dist + 1) * Size_X, WallPosYUp - (dist + 1) * Size_Y,
           WallPosXRight - (dist + 1) * Size_X, WallPosYDown + (dist + 1) * Size_Y);
      line(WallPosXRight - (dist + 1) * Size_X, WallPosYDown + (dist + 1) * Size_Y,
           WallPosXRight - dist * Size_X, WallPosYDown + dist * Size_Y);
    }
  }
}

void BuildBackgroundWall (int WallPosXLeft, int WallPosXRight, int WallPosYDown, int WallPosYUp, int stepX, int stepY) {
  dist = 0;
  intensity ((int)(64 - dist * FADE));
  while (!( (Level[(HeroPosY + dist * stepY)][(HeroPosX + dist * stepX)] < '8') || (dist > 4) )) {
    DrawLeftWall (WallPosXLeft, WallPosYUp, WallPosYDown, stepX, stepY);
    DrawRightWall (WallPosXRight, WallPosYUp, WallPosYDown, stepX, stepY);
    dist += 1;
    intensity ((int)(64 - dist * FADE));
  }
  intensity ((int)(64 - (dist - 1) * FADE));
  if (WallPosYDown + dist * Size_Y != 0) {
    line(WallPosXLeft + dist * Size_X, WallPosYDown + dist * Size_Y,
         WallPosXRight - dist * Size_X, WallPosYDown + dist * Size_Y);
    line(WallPosXRight - dist * Size_X, WallPosYUp - dist * Size_Y,
         WallPosXLeft + dist * Size_X, WallPosYUp - dist * Size_Y);
  }
}
#define set_scale(i) VIA_t1_cnt_lo = i
void DrawFrame(void) {
  Wait_Recal();
  set_scale(0x7F);
  Read_Btns();
  enable_controller_1_x();
  enable_controller_1_y();
  Joy_Analog();

  Intensity_a(60);
  Print_Str_d(-100,-120, "BACK FORWARD LEFT RIGHT\x80");

  if (rotation == 0) {
    stepX = 1; stepY = 0;
  } else if (rotation == 1) {
    stepX = 0; stepY = 1;
  } else if (rotation == 2) {
    stepX = -1; stepY = 0;
  } else /* rotation == 3 */ {
    stepX = 0; stepY = -1;
  }

  if (KeyPressOn && !key(KEY_ANY)) {
    KeyPressOn = 0;
  } else if (key(KEY_LEFT)) {
    if (KeyPressOn == 0) { rotation += 3; KeyPressOn = 1; }
  } else if (key(KEY_RIGHT)) {
    if (KeyPressOn == 0) { rotation += 1; KeyPressOn = 1; }
  } else if (key(KEY_UP)) {
    if (KeyPressOn == 0) {
      if (Level[(HeroPosY + stepY)][(HeroPosX + stepX)] > '7') { HeroPosX += stepX; HeroPosY += stepY; }
      KeyPressOn = 1;
    }
  } else if (key(KEY_DOWN)) {
    if (KeyPressOn == 0) {
      if (Level[(HeroPosY - stepY)][(HeroPosX - stepX)] > '7') { HeroPosX -= stepX; HeroPosY -= stepY; }
      KeyPressOn = 1;
    }
  }
  rotation &= 3;

{ int a,b,c,d; // avoid gcc bug
  a = (rotation^1)&1;
  b = -((rotation&2)-1);
  c = (rotation&1);
  d = -((rotation&2)-1);
  BuildBackgroundWall (ScreenPosX, ScreenPosX + ScreenSizeX, ScreenPosY, ScreenPosY + ScreenSizeY,
		       a * b, c * d);
}
}


int main(void) {
  HeroPosY = 2; HeroPosX = 2; // initial square
  ScreenSizeX = WIDTH; ScreenSizeY = HEIGHT; // size of scratch display
  ScreenPosX = -HALF_WIDTH; ScreenPosY = -HALF_HEIGHT; // center
  Size_X = ScreenSizeX / 10L; Size_Y = ScreenSizeY / 10L;

  for (;;) DrawFrame();

  return 0;
}