#include <pitrex/pitrexio-gpio.h>
#include <vectrex/vectrexInterface.h>

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

static unsigned char current_intensity = 127;

int tx(int x) { // rescale
  return x*50;
}
int ty(int y) {
  return y*50;
}

void line(int xl, int yb, int xr, int yt) {
  v_directDraw32(tx(xl),ty(yb), tx(xr),ty(yt), current_intensity);
}

// 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) {
  int bit[5] = { 0x88, 0x44, 0x22, 0x11, 0xFF };
  return currentButtonState & bit[code];
}

#define FADE 8
void intensity(int i) {
  v_setBrightness(current_intensity = i);
}

static int
  dist, rotation=1, stepX=0, stepY=0, KeyPressOn=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 - (240 + (dist + 1) * Size_X) * 3/8 != 0) {
        line(WallPosXLeft + dist * Size_X, WallPosYUp - (240 + dist * Size_X) * 3/8,
             WallPosXLeft + (dist + 1) * Size_X, WallPosYUp - (240 + (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 + (240 + (dist + 1) * Size_X) * 3/8,
             WallPosXLeft + dist * Size_X, WallPosYDown + (240 + 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 - (240 + dist * Size_X) * 3/8,
             WallPosXRight - (dist + 1) * Size_X, WallPosYUp - (240 + (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 + (240 + (dist + 1) * Size_X) * 3/8,
             WallPosXRight - dist * Size_X, WallPosYDown + (240 + 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 (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 (64 - dist * FADE);
  }
  intensity (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);
  }
}

void DrawFrame(void) {

  v_WaitRecal();
  //v_doSound();
  v_setBrightness(64);        /* set intensity of vector beam... */
  v_readButtons();
  v_readJoystick1Analog();
  //v_playAllSFX();

  v_setBrightness(60);
  v_printStringRaster(-60,-120, "BACK  FORWARD  LEFT  RIGHT", 30, -7, '\0');

  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;

  BuildBackgroundWall (ScreenPosX, ScreenPosX + ScreenSizeX, ScreenPosY, ScreenPosY + ScreenSizeY,
		       ((rotation^1)&1) * -((rotation&2)-1), (rotation&1) * -((rotation&2)-1));
}


int main(int argc, char **argv) {
  HeroPosY = 2; HeroPosX = 2; // initial square
  ScreenSizeX = 480; ScreenSizeY = 360; // size of scratch display
  ScreenPosX = -240; ScreenPosY = -180; // center
  Size_X = ScreenSizeX / 10; Size_Y = ScreenSizeY / 10;
  vectrexinit(1);
  v_init();
  usePipeline = 1;
  v_setRefresh(60);

  for (;;) DrawFrame();

  return 0;
}