// Stolen from https://codepen.io/loktar00/pen/BaGqXY
// However has modifications to work good as a theme

const VERSION = "1.0";
const REMOTE_UPDATE_PATH =
  "https://git.javalsai.dynv6.net/UwU/haxxor-theme/raw/branch/main/"; // Change this to your remote repository!

("use strict");
function createTetris() {

  let keyMap = {};

  var tetrominos = [
    {
      // box
      colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
      data: [
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 1, 1, 0],
        [0, 0, 0, 0],
      ],
    },
    {
      // stick
      colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
      data: [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [1, 1, 1, 1],
        [0, 0, 0, 0],
      ],
    },
    {
      // z
      colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
      data: [
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 0, 1, 1],
        [0, 0, 0, 0],
      ],
    },
    {
      // T
      colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
      data: [
        [0, 0, 0, 0],
        [0, 1, 1, 1],
        [0, 0, 1, 0],
        [0, 0, 0, 0],
      ],
    },
    {
      // s
      colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
      data: [
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [1, 1, 0, 0],
        [0, 0, 0, 0],
      ],
    },
    {
      // backwards L
      colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
      data: [
        [0, 0, 1, 0],
        [0, 0, 1, 0],
        [0, 1, 1, 0],
        [0, 0, 0, 0],
      ],
    },
    {
      // L
      colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
      data: [
        [0, 1, 0, 0],
        [0, 1, 0, 0],
        [0, 1, 1, 0],
        [0, 0, 0, 0],
      ],
    },
  ];

  var Tetris = function (x, y, width, height) {
    this.posX = x || 0;
    this.posY = y || 0;

    this.width = width || window.innerWidth;
    this.height = height || window.innerHeight;

    this.bgCanvas = document.createElement("canvas");
    this.fgCanvas = document.createElement("canvas");

    this.bgCanvas.width = this.fgCanvas.width = this.width;
    this.bgCanvas.height = this.fgCanvas.height = this.height;

    this.bgCtx = this.bgCanvas.getContext("2d");
    this.fgCtx = this.fgCanvas.getContext("2d");

    this.bgCanvas.style.left = this.posX + "px";
    this.bgCanvas.style.top = this.posY + "px";

    // Stay in background
    this.bgCanvas.style.zIndex = 1;
    this.fgCanvas.style.zIndex = 1;
    this.bgCanvas.style.position = "absolute";
    this.fgCanvas.style.position = "absolute";

    this.fgCanvas.style.left = this.posX + "px";
    this.fgCanvas.style.top = this.posY + "px";

    document.body.insertBefore(this.fgCanvas, document.body.firstChild);
    document.body.insertBefore(this.bgCanvas, document.body.firstChild);
    this.init();
  };

  Tetris.prototype.init = function () {
    this.curPiece = {
      data: null,
      colors: [0, 0, 0],
      x: 0,
      y: 0,
    };

    this.lastMove = Date.now();
    this.curSpeed = 50 + Math.random() * 50;
    this.unitSize = 20;
    this.linesCleared = 0;
    this.level = 0;
    this.loseBlock = 0;

    // init the board
    this.board = [];
    this.boardWidth = Math.floor(this.width / this.unitSize);
    this.boardHeight = Math.floor(this.height / this.unitSize);

    var board = this.board,
      boardWidth = this.boardWidth,
      boardHeight = this.boardHeight,
      halfHeight = boardHeight / 2,
      curPiece = this.curPiece,
      x = 0,
      y = 0;

    // init board
    for (x = 0; x <= boardWidth; x++) {
      board[x] = [];
      for (y = 0; y <= boardHeight; y++) {
        board[x][y] = {
          data: 0,
          colors: ["rgb(0,0,0)", "rgb(0,0,0)", "rgb(0,0,0)"],
        };

        if (Math.random() > 0.15 && y > halfHeight) {
          board[x][y] = {
            data: 1,
            colors:
              tetrominos[Math.floor(Math.random() * tetrominos.length)].colors,
          };
        }
      }
    }

    // collapse the board a bit
    for (x = 0; x <= boardWidth; x++) {
      for (y = boardHeight - 1; y > -1; y--) {
        if (board[x][y].data === 0 && y > 0) {
          for (var yy = y; yy > 0; yy--) {
            if (board[x][yy - 1].data) {
              board[x][yy].data = 1;
              board[x][yy].colors = board[x][yy - 1].colors;

              board[x][yy - 1].data = 0;
              board[x][yy - 1].colors = [
                "rgb(0,0,0)",
                "rgb(0,0,0)",
                "rgb(0,0,0)",
              ];
            }
          }
        }
      }
    }

    var self = this;

    window.addEventListener("keyup", function(e) {
      keyMap[e.code] = e.type == 'keydown';
    });

    window.addEventListener("keydown", function (e) {
      keyMap[e.code] = e.type == 'keydown';
      switch (e.code) {
        case "ArrowLeft":
          if (keyMap["ShiftLeft"] || keyMap["ShiftRight"]) {
            if (self.checkMovement(curPiece, -2, 0)) {
              curPiece.x -= 2;
            };
            break
          }
          if (self.checkMovement(curPiece, -1, 0)) {
            curPiece.x -= 1;
          } 
          break;
        case "ArrowRight":
          if (keyMap["ShiftLeft"] || keyMap["ShiftRight"]) {
            if (self.checkMovement(curPiece, 2, 0)) {
              curPiece.x += 2;
            }
            break;
          }
          if (self.checkMovement(curPiece, 1, 0)) {
            curPiece.x += 1;
          }
          break;
        case "ArrowDown":
          if (self.checkMovement(curPiece, 0, 1)) {
            curPiece.y++;
          }
          break;
        case "ArrowUp":
        case "Space":
          curPiece.data = self.rotateTetrimono(curPiece);
          break;
      }
    });

    // render the board
    this.checkLines();
    this.renderBoard();

    // assign the first tetri
    this.newTetromino();
    this.update();
  };

  Tetris.prototype.update = function () {
    var curPiece = this.curPiece;

    if (!this.checkMovement(curPiece, 0, 1)) {
      if (curPiece.y < -1) {
        // you lose
        this.loseScreen();
        return true;
      } else {
        this.fillBoard(curPiece);
        this.newTetromino();
      }
    } else {
      if (Date.now() > this.lastMove) {
        this.lastMove = Date.now() + this.curSpeed;
        if (this.checkMovement(curPiece, 0, 0.5)) {
          curPiece.y += 0.5;
        } else {
          this.fillBoard(curPiece);
          this.newTetromino();
        }
      }
    }

    this.render();

    var self = this;
    requestAnimationFrame(function () {
      self.update();
    });
  };

  // render only the board.
  Tetris.prototype.renderBoard = function () {
    var canvas = this.bgCanvas,
      ctx = this.bgCtx,
      unitSize = this.unitSize,
      board = this.board,
      boardWidth = this.boardWidth,
      boardHeight = this.boardHeight;

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (var x = 0; x <= boardWidth; x++) {
      for (var y = 0; y <= boardHeight; y++) {
        if (board[x][y].data !== 0) {
          var bX = x * unitSize,
            bY = y * unitSize;
          //ctx.shadowBlur = 5;
          //ctx.shadowColor = "rgba(0,255,0,.6)";
          ctx.fillStyle = board[x][y].colors[0];
          ctx.fillRect(bX, bY, unitSize, unitSize);

          ctx.fillStyle = board[x][y].colors[1];
          ctx.fillRect(bX + 2, bY + 2, unitSize - 4, unitSize - 4);

          ctx.fillStyle = board[x][y].colors[2];
          ctx.fillRect(bX + 4, bY + 4, unitSize - 8, unitSize - 8);
        }
      }
    }
  };

  // Render the current active piece
  Tetris.prototype.render = function () {
    var canvas = this.fgCanvas,
      ctx = this.fgCtx,
      unitSize = this.unitSize,
      curPiece = this.curPiece;

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    for (var x = 0; x < 4; x++) {
      for (var y = 0; y < 4; y++) {
        if (curPiece.data[x][y] === 1) {
          var xPos = (curPiece.x + x) * unitSize,
            yPos = (curPiece.y + y) * unitSize;

          if (yPos > -1) {
            ctx.shadowBlur = 10;
            ctx.shadowColor = "rgba(0,255,0,.6)";
            ctx.fillStyle = curPiece.colors[0];
            ctx.fillRect(xPos, yPos, unitSize, unitSize);

            ctx.fillStyle = curPiece.colors[1];
            ctx.fillRect(xPos + 2, yPos + 2, unitSize - 4, unitSize - 4);

            ctx.fillStyle = curPiece.colors[2];
            ctx.fillRect(xPos + 4, yPos + 4, unitSize - 8, unitSize - 8);
          }
        }
      }
    }
  };

  // Make sure we can mov where we want.
  Tetris.prototype.checkMovement = function (curPiece, newX, newY) {
    var piece = curPiece.data,
      posX = curPiece.x,
      posY = curPiece.y,
      board = this.board,
      boardWidth = this.boardWidth,
      boardHeight = this.boardHeight;

    for (var x = 0; x < 4; x++) {
      for (var y = 0; y < 4; y++) {
        if (piece[x][y] === 1) {
          if (!board[posX + x + newX]) {
            board[posX + x + newX] = [];
          }

          if (!board[posX + x + newX][y + posY + newY]) {
            board[posX + x + newX][y + posY + newY] = {
              data: 0,
            };
          }

          if (
            posX + x + newX >= boardWidth ||
            posX + x + newX < 0 ||
            board[posX + x + newX][y + posY + newY].data == 1
          ) {
            return false;
          }

          if (posY + y + newY > boardHeight) {
            return false;
          }
        }
      }
    }
    return true;
  };

  // checks for completed lines and clears them
  Tetris.prototype.checkLines = function () {
    var board = this.board,
      boardWidth = this.boardWidth,
      boardHeight = this.boardHeight,
      linesCleared = this.linesCleared,
      level = this.level,
      y = boardHeight + 1;

    while (y--) {
      var x = boardWidth,
        lines = 0;

      while (x--) {
        if (board[x][y].data === 1) {
          lines++;
        }
      }

      if (lines === boardWidth) {
        linesCleared++;
        level = Math.round(linesCleared / 20) * 20;

        var lineY = y;
        while (lineY) {
          for (x = 0; x <= boardWidth; x++) {
            if (lineY - 1 > 0) {
              board[x][lineY].data = board[x][lineY - 1].data;
              board[x][lineY].colors = board[x][lineY - 1].colors;
            }
          }
          lineY--;
        }
        y++;
      }
    }
  };

  // Lose animation
  Tetris.prototype.loseScreen = function () {
    var ctx = this.bgCtx,
      unitSize = this.unitSize,
      boardWidth = this.boardWidth,
      boardHeight = this.boardHeight,
      y = boardHeight - this.loseBlock;

    for (var x = 0; x < boardWidth; x++) {
      var bX = x * unitSize,
        bY = y * unitSize;

      ctx.fillStyle = "rgb(0,0,0)";
      ctx.fillRect(bX, bY, unitSize, unitSize);

      ctx.fillStyle = "#54a13c";
      ctx.fillRect(bX + 2, bY + 2, unitSize - 4, unitSize - 4);

      ctx.fillStyle = "rgb(0,0,0)";
      ctx.fillRect(bX + 4, bY + 4, unitSize - 8, unitSize - 8);
    }

    if (this.loseBlock <= boardHeight + 1) {
      this.loseBlock++;

      var self = this;
      requestAnimationFrame(function () {
        self.loseScreen();
      });
    } else {
      this.init();
    }
  };

  // adds the piece as part of the board
  Tetris.prototype.fillBoard = function (curPiece) {
    var piece = curPiece.data,
      posX = curPiece.x,
      posY = curPiece.y,
      board = this.board;

    for (var x = 0; x < 4; x++) {
      for (var y = 0; y < 4; y++) {
        if (piece[x][y] === 1) {
          board[x + posX][y + posY].data = 1;
          board[x + posX][y + posY].colors = curPiece.colors;
        }
      }
    }

    this.checkLines();
    this.renderBoard();
  };

  // rotate a piece
  Tetris.prototype.rotateTetrimono = function (curPiece) {
    var rotated = [];

    for (var x = 0; x < 4; x++) {
      rotated[x] = [];
      for (var y = 0; y < 4; y++) {
        rotated[x][y] = curPiece.data[3 - y][x];
      }
    }

    if (
      !this.checkMovement(
        {
          data: rotated,
          x: curPiece.x,
          y: curPiece.y,
        },
        0,
        0
      )
    ) {
      rotated = curPiece.data;
    }

    return rotated;
  };

  // assign the player a new peice
  Tetris.prototype.newTetromino = function () {
    var pieceNum = Math.floor(Math.random() * tetrominos.length),
      curPiece = this.curPiece;

    curPiece.data = tetrominos[pieceNum].data;
    curPiece.colors = tetrominos[pieceNum].colors;
    curPiece.x = Math.floor(
      Math.random() * (this.boardWidth - curPiece.data.length + 1)
    );
    curPiece.y = -4;
  };

  let boardDiv = 20 * Math.round(window.innerWidth / 20),
    boards = 1,
    bWidth = boardDiv / boards,
    tetrisInstances = [];

  let second_nav = 0;
  if (document.getElementsByClassName("secondary-nav")[0] !== undefined)
    second_nav =
      document.getElementsByClassName("secondary-nav")[0].clientHeight;
  let barsHeight = second_nav + document.getElementById("navbar").clientHeight;

  let bheight = determineFullHeight() - barsHeight;
  console.log(
    determineFullHeight(),
    document.getElementsByClassName("full height")[0].scrollHeight,
    second_nav,
    document.getElementById("navbar").clientHeight
  );

  for (var w = 0; w < boards; w++) {
    tetrisInstances.push(
      new Tetris(
        20 * Math.round((w * bWidth) / 20),
        barsHeight,
        bWidth,
        bheight
      )
    );
  }
}

function determineFullHeight() {
  if (document.getElementsByClassName("full height")[0] !== undefined) {
    return document.getElementsByClassName("full height")[0].clientHeight; // This is guaranteed to work very fine
  } else {
    let footer_size = 0;
    if (document.getElementsByClassName("page-footer")[0] !== undefined)
      footer_size =
        document.getElementsByClassName("page-footer")[0].clientHeight;
    return document.documentElement.scrollHeight - footer_size; // Fallback response, might not work as well
  }
}

function main() {
  if (!isActive()) {
    return;
  }
  getUpdated();
  setTimeout(createTetris, 50); // Sometimes page isnt completely rendered when tetris is supposed to be loaded, so 50ms timeout is enough to load page on most systems
}

async function getUpdated() {
  try {
    let remote_version = (await fetch(REMOTE_UPDATE_PATH + "VERSION")).text;
    if (remote_version != VERSION) {
      console.log(
        `New version available in remote:\nRemote: v${remote_version}\nCurrent: v${VERSION}`
      );
    }
  } catch {
    console.log("Failed to get updates, is updates repository offline?");
  }
}

function isActive() {
  let style = getComputedStyle(document.body);
  return style.getPropertyValue("--is-haxxorman") == "true";
}

document.addEventListener("DOMContentLoaded", main);