let width = 800;
let height = 625;
let gridSize = 32;
let offset = parseInt(gridSize / 2);
let config = {
  type: Phaser.CANVAS,
  width: width,
  height: height,
  canvas: document.getElementById("mycanvas"),
  physics: {
    default: "arcade",
    arcade: {
      debug: false,
      gravity: {
        x: 0,
        y: 0
      }
    }
  },
  scene: {
    preload: preload,
    create: create,
    update: update
  }
};

let game = new Phaser.Game(config);
let cursors;
let player;
let ghosts = [];
let pills;
let pillsCount = 0;
let pillsAte = 0;
let map;
let layer1;
let layer2;
let graphics;
let scoreText;
let livesImage = [];
let tiles = "pacman-tiles";
let spritesheet = "pacman-spritesheet";
let customPacman = "customPacman";
let ensr = "ENSR";
let ensl = "ENSL";
let ensu = "ENSU";
let enss = "ENSS";
let spritesheetPath = "https://raw.githubusercontent.com/kudchikarsk/phaser-pacman/master/assets/images/pacmansprites.png";
let tilesPath = "https://raw.githubusercontent.com/kudchikarsk/phaser-pacman/master/assets/images/background.png";
let mapPath =
  "https://raw.githubusercontent.com/kudchikarsk/phaser-pacman/master/assets/levels/codepen-level.json";
let Animation = {
  Player: {
    Eat: "player-eat",
    Stay: "player-stay",
    Die: "player-die"
  },
  Ghost: {
    Blue: {
      Move: "ghost-blue-move"
    },

    Orange: {
      Move: "ghost-orange-move"
    },

    White: {
      Move: "ghost-white-move"
    },

    Pink: {
      Move: "ghost-pink-move"
    },

    Red: {
      Move: "ghost-red-move"
    }
  }
};

function preload() {
  this.load.spritesheet(spritesheet, spritesheetPath, {
    frameWidth: gridSize,
    frameHeight: gridSize
  });
  this.load.tilemapTiledJSON("map", mapPath);
  this.load.image(tiles, tilesPath);
  this.load.image("pill", "https://raw.githubusercontent.com/kudchikarsk/phaser-pacman/master/assets/images/pac%20man%20pill/spr_pill_0.png");
  this.load.image("lifecounter", "https://raw.githubusercontent.com/kudchikarsk/phaser-pacman/master/assets/images/pac%20man%20life%20counter/spr_lifecounter_0.png");
  this.load.spritesheet(customPacman, "images/pp.jpg", {
    frameWidth: gridSize,
    frameHeight: gridSize
  });
  this.load.image("0", "./images/0.png");
  this.load.image("1", "./images/1.png");
 
  this.load.spritesheet(ensr, "images/ENSR.png", {
    frameWidth: gridSize,
    frameHeight: gridSize
  });
  this.load.spritesheet(ensl, "images/ENSL.png", {
    frameWidth: gridSize,
    frameHeight: gridSize
  });
  this.load.spritesheet(ensu, "images/ENSU.png", {
    frameWidth: gridSize,
    frameHeight: gridSize
  });
  this.load.spritesheet(enss, "images/ENSS.jpeg", {
    frameWidth: gridSize,
    frameHeight: gridSize
  });
}

function create() {
  this.anims.create({
    key: Animation.Player.Stay,
    frames: this.anims.generateFrameNumbers(customPacman, {frame: 0}),
    frameRate: 20
  });

  this.anims.create({
    key: Animation.Ghost.Blue.Move,
    frames: this.anims.generateFrameNumbers(ensl, { start: 0, end: 0 }),
    frameRate: 10,
    repeat: -1
  });

  this.anims.create({
    key: Animation.Ghost.Orange.Move,
    frames: this.anims.generateFrameNumbers(enss, { start: 0, end: 0 }),
    frameRate: 10,
    repeat: -1
  });

  //this.anims.create({
  //  key: Animation.Ghost.White.Move,
  //  frames: this.anims.generateFrameNumbers(ensl, { start: 0, end: 0 }),
  //  frameRate: 10,
  //  repeat: -1
  //});

  this.anims.create({
    key: Animation.Ghost.Pink.Move,
    frames: this.anims.generateFrameNumbers(ensu, {
      start: 0,
      end: 0
    }),
    frameRate: 10,
    repeat: -1
  });

  this.anims.create({
    key: Animation.Ghost.Red.Move,
    frames: this.anims.generateFrameNumbers(ensr, {
      start: 0,
      end: 0
    }),
    frameRate: 10,
    repeat: -1
  });

  map = this.make.tilemap({
    key: "map",
    tileWidth: gridSize,
    tileHeight: gridSize
  });
  const tileset = map.addTilesetImage(tiles);

  layer1 = map.createStaticLayer("Layer 1", tileset, 0, 0);
  layer1.setCollisionByProperty({ collides: true });

  layer2 = map.createStaticLayer("Layer 2", tileset, 0, 0);
  layer2.setCollisionByProperty({ collides: true });

  let spawnPoint = map.findObject("Objects", obj => obj.name === "Player");
  let position = new Phaser.Geom.Point(
    spawnPoint.x + offset,
    spawnPoint.y - offset
  );
  player = new Player(this, position, Animation.Player, function() {
    if (player.life <= 0) {
      newGame();
    } else {
      respawn();
    }
  });

  let scene = this;

  pills = this.physics.add.group();
  map.filterObjects("Objects", function(value, index, array) {
    if (value.name == "Pill") {
      let pill;
      let randomValue = Phaser.Math.Between(0,1);
      if (randomValue > 0.5) {
          pill = scene.physics.add.sprite(
              value.x + offset,
              value.y - offset,
              "0"
          );
          pill.setScale(0.07);
      } else {
          pill = scene.physics.add.sprite(
              value.x + offset,
              value.y - offset,
              "1"
          );
          pill.setScale(0.1);
      }
      pills.add(pill);
      pillsCount++;
    }
  });

  let ghostsGroup = this.physics.add.group();
  let i = 0;
  let skins = [
    Animation.Ghost.Blue,
    Animation.Ghost.Red,
    Animation.Ghost.Orange,
    Animation.Ghost.Pink
  ];
  map.filterObjects("Objects", function(value, index, array) {
    if (value.name == "Ghost") {
      let position = new Phaser.Geom.Point(value.x + offset, value.y - offset);
      let ghost = new Ghost(scene, position, skins[i]);
      ghosts.push(ghost);
      ghostsGroup.add(ghost.sprite);
      i++;
    }
  });

  this.physics.add.collider(player.sprite, layer1);
  this.physics.add.collider(player.sprite, layer2);
  this.physics.add.collider(ghostsGroup, layer1);
  this.physics.add.overlap(
    player.sprite,
    pills,
    function(sprite, pill) {
      pill.disableBody(true, true);
      pillsAte++;
      player.score += 10;
      if (pillsCount == pillsAte) {
        reset();
      }
    },
    null,
    this
  );

  this.physics.add.overlap(
    player.sprite,
    ghostsGroup,
    function(sprite, ghostSprite) {
      if (player.active) {
        player.die();
        for (let ghost of ghosts) {
          ghost.freeze();
        }
      }
    },
    null,
    this
  );

  cursors = this.input.keyboard.createCursorKeys();

  graphics = this.add.graphics();

  scoreText = this.add
    .text(25, 595, "Score: " + player.score)
    .setFontFamily("Arial")
    .setFontSize(18)
    .setColor("#ffffff");
  this.add
    .text(630, 595, "Lives:")
    .setFontFamily("Arial")
    .setFontSize(18)
    .setColor("#ffffff");
  for (let i = 0; i < player.life; i++) {
    livesImage.push(this.add.image(700 + i * 25, 605, "lifecounter"));
  }
}

function respawn() {
  player.respawn();
  for (let ghost of ghosts) {
    ghost.respawn();
  }
}

function reset() {
  respawn();
  for (let child of pills.getChildren()) {
    child.enableBody(false, child.x, child.y, true, true);
  }
  pillsAte = 0;
}

function newGame() {
  reset();
  player.life = 3;
  player.score = 0;
  for (let i = 0; i < player.life; i++) {
    let image = livesImage[i];
    if (image) {
      image.alpha = 1;
    }
  }
}

function update() {
  player.setDirections(getDirection(map, layer1, player.sprite));

  if (!player.playing) {
    for (let ghost of ghosts) {
      ghost.freeze();
    }
  }

  for (let ghost of ghosts) {
    ghost.setDirections(getDirection(map, layer1, ghost.sprite));
  }

  player.setTurningPoint(getTurningPoint(map, player.sprite));

  for (let ghost of ghosts) {
    ghost.setTurningPoint(getTurningPoint(map, ghost.sprite));
  }

  if (cursors.left.isDown) {
    player.setTurn(Phaser.LEFT);
  } else if (cursors.right.isDown) {
    player.setTurn(Phaser.RIGHT);
  } else if (cursors.up.isDown) {
    player.setTurn(Phaser.UP);
  } else if (cursors.down.isDown) {
    player.setTurn(Phaser.DOWN);
  } else {
    player.setTurn(Phaser.NONE);
  }

  player.update();

  for (let ghost of ghosts) {
    ghost.update();
  }

  scoreText.setText("Score: " + player.score);

  for (let i = player.life; i < 3; i++) {
    let image = livesImage[i];
    if (image) {
      image.alpha = 0;
    }
  }

  if (player.active) {
    if (player.sprite.x < 0 - offset) {
      player.sprite.setPosition(width + offset, player.sprite.y);
    } else if (player.sprite.x > width + offset) {
      player.sprite.setPosition(0 - offset, player.sprite.y);
    }
  }

  //drawDebug();
}

function drawDebug() {
  graphics.clear();
  player.drawDebug(graphics);
  for (let ghost of ghosts) {
    ghost.drawDebug(graphics);
  }
}

function getDirection(map, layer, sprite) {
  let directions = [];
  let sx = Phaser.Math.FloorTo(sprite.x);
  let sy = Phaser.Math.FloorTo(sprite.y);
  let currentTile = map.getTileAtWorldXY(sx, sy, true);
  if (currentTile) {
    var x = currentTile.x;
    var y = currentTile.y;

    directions[Phaser.LEFT] = map.getTileAt(x - 1, y, true, layer);
    directions[Phaser.RIGHT] = map.getTileAt(x + 1, y, true, layer);
    directions[Phaser.UP] = map.getTileAt(x, y - 1, true, layer);
    directions[Phaser.DOWN] = map.getTileAt(x, y + 1, true, layer);
  }

  return directions;
}

function getTurningPoint(map, sprite) {
  let turningPoint = new Phaser.Geom.Point();
  let sx = Phaser.Math.FloorTo(sprite.x);
  let sy = Phaser.Math.FloorTo(sprite.y);
  let currentTile = map.getTileAtWorldXY(sx, sy, true);
  if (currentTile) {
    turningPoint.x = currentTile.pixelX + offset;
    turningPoint.y = currentTile.pixelY + offset;
  }

  return turningPoint;
}

class Ghost {
  constructor(scene, position, anim) {
    this.sprite = scene.physics.add
      .sprite(position.x, position.y, "ghost")
      .setScale(0.85)
      .setOrigin(0.5);
    this.spawnPoint = position;
    this.anim = anim;
    this.speed = 100;
    this.moveTo = new Phaser.Geom.Point();
    this.safetile = [-1, 19];
    this.directions = [];
    this.opposites = [
      null,
      null,
      null,
      null,
      null,
      Phaser.DOWN,
      Phaser.UP,
      Phaser.RIGHT,
      Phaser.LEFT
    ];
    this.turning = Phaser.NONE;
    this.current = Phaser.NONE;
    this.turningPoint = new Phaser.Geom.Point();
    this.threshold = 5;
    this.rnd = new Phaser.Math.RandomDataGenerator();
    this.sprite.anims.play(anim.Move, true);
    this.turnCount = 0;
    this.turnAtTime = [4, 8, 16, 32, 64];
    this.turnAt = this.rnd.pick(this.turnAtTime);
  }

  freeze() {
    this.moveTo = new Phaser.Geom.Point();
    this.current = Phaser.NONE;
  }

  move() {
    this.move(this.rnd.pick([Phaser.UP, Phaser.DOWN]));
  }

  respawn() {
    this.sprite.setPosition(this.spawnPoint.x, this.spawnPoint.y);
    this.move(this.rnd.pick([Phaser.UP, Phaser.DOWN]));
    this.sprite.flipX = false;
  }

  moveLeft() {
    this.moveTo.x = -1;
    this.moveTo.y = 0;
    this.sprite.flipX = false;
    this.sprite.angle = 0;
  }

  moveRight() {
    this.moveTo.x = 1;
    this.moveTo.y = 0;
    this.sprite.flipX = false;
    this.sprite.angle = 0;
  }

  moveUp() {
    this.moveTo.x = 0;
    this.moveTo.y = -1;
    this.sprite.angle = 0;
  }

  moveDown() {
    this.moveTo.x = 0;
    this.moveTo.y = 1;
    this.sprite.angle = 0;
  }

  update() {
    this.sprite.setVelocity(
      this.moveTo.x * this.speed,
      this.moveTo.y * this.speed
    );
    this.turn();
    if (
      this.directions[this.current] &&
      !this.isSafe(this.directions[this.current].index)
    ) {
      this.sprite.anims.play("faceRight", true);
      this.takeRandomTurn();
    }
  }

  setDirections(directions) {
    this.directions = directions;
  }

  setTurningPoint(turningPoint) {
    this.turningPoint = turningPoint;
  }

  setTurn(turnTo) {
    if (
      !this.directions[turnTo] ||
      this.turning === turnTo ||
      this.current === turnTo ||
      !this.isSafe(this.directions[turnTo].index)
    ) {
      return false;
    }

    //console.log("turning:"+this.turning+" current:"+this.current+" turnTo:"+turnTo);

    if (this.opposites[turnTo] && this.opposites[turnTo] === this.current) {
      this.move(turnTo);
      this.turning = Phaser.NONE;
      this.turningPoint = new Phaser.Geom.Point();
    } else {
      this.turning = turnTo;
    }
  }

  takeRandomTurn() {
    let turns = [];
    for (let i = 0; i < this.directions.length; i++) {
      let direction = this.directions[i];
      if (direction) {
        if (this.isSafe(direction.index)) {
          turns.push(i);
        }
      }
    }

    if (turns.length >= 2) {
      let index = turns.indexOf(this.opposites[this.current]);
      if (index > -1) {
        turns.splice(index, 1);
      }
    }

    let turn = this.rnd.pick(turns);
    this.setTurn(turn);

    this.turnCount = 0;
    this.turnAt = this.rnd.pick(this.turnAtTime);
  }

  turn() {
    if (this.turnCount === this.turnAt) {
      this.takeRandomTurn();
    }
    this.turnCount++;

    if (this.turning === Phaser.NONE) {
      return false;
    }

    //  This needs a threshold, because at high speeds you can't turn because the coordinates skip past
    if (
      !Phaser.Math.Within(this.sprite.x, this.turningPoint.x, this.threshold) ||
      !Phaser.Math.Within(this.sprite.y, this.turningPoint.y, this.threshold)
    ) {
      return false;
    }

    this.sprite.setPosition(this.turningPoint.x, this.turningPoint.y);
    this.move(this.turning);
    this.turning = Phaser.NONE;
    this.turningPoint = new Phaser.Geom.Point();
    return true;
  }

  move(direction) {
    this.current = direction;

    switch (direction) {
      case Phaser.LEFT:
        this.moveLeft();
        break;

      case Phaser.RIGHT:
        this.moveRight();
        break;

      case Phaser.UP:
        this.moveUp();
        break;

      case Phaser.DOWN:
        this.moveDown();
        break;
    }
  }

  isSafe(index) {
    for (let i of this.safetile) {
      if (i === index) return true;
    }

    return false;
  }

  drawDebug(graphics) {
    let thickness = 4;
    let alpha = 1;
    let color = 0x00ff00;
    for (var t = 0; t < 9; t++) {
      if (this.directions[t] === null || this.directions[t] === undefined) {
        continue;
      }

      if (!this.isSafe(this.directions[t].index)) {
        color = 0xff0000;
      } else {
        color = 0x00ff00;
      }

      graphics.lineStyle(thickness, color, alpha);
      graphics.strokeRect(
        this.directions[t].pixelX,
        this.directions[t].pixelY,
        32,
        32
      );
    }

    color = 0x00ff00;
    graphics.lineStyle(thickness, color, alpha);
    graphics.strokeRect(this.turningPoint.x, this.turningPoint.y, 1, 1);
  }
}

class Player {
  constructor(scene, position, anim, dieCallback) {
    this.sprite = scene.physics.add
      .sprite(position.x, position.y, "pacman")
      .setScale(0.9)
      .setOrigin(0.5);
    this.spawnPoint = position;
    this.anim = anim;
    this.dieCallback = dieCallback;
    this.speed = 95;
    this.moveTo = new Phaser.Geom.Point();
    this.sprite.angle = 0;
    this.safetile = [-1, 18];
    this.directions = [];
    this.opposites = [
      null,
      null,
      null,
      null,
      null,
      Phaser.DOWN,
      Phaser.UP,
      Phaser.RIGHT,
      Phaser.LEFT
    ];
    this.turning = Phaser.NONE;
    this.current = Phaser.NONE;
    this.turningPoint = new Phaser.Geom.Point();
    this.threshold = 5;
    this.life = 3;
    this.score = 0;
    this.active = true;
    this.sprite.anims.play(this.anim.Stay, true);
    let ref = this;
    this.sprite.on(
      "animationcomplete",
      function(animation, frame) {
        ref.animComplete(animation, frame);
      },
      scene
    );
    this.playing = false;
  }

  die() {
    this.active = false;
    this.playing = false;
    this.life--;
    this.moveTo = new Phaser.Geom.Point();
    setTimeout(() => {
        this.dieCallback();
    }, 500)
  }

  animComplete(animation, frame) {
    if (animation.key == this.anim.Die) {
      this.dieCallback();
    }
  }

  respawn() {
    this.active = true;
    this.playing = false;
    this.sprite.setPosition(this.spawnPoint.x, this.spawnPoint.y);
    this.moveTo = new Phaser.Geom.Point();
    this.sprite.anims.play(this.anim.Stay, true);
    this.sprite.angle = 0;
    this.turning = Phaser.NONE;
    this.current = Phaser.NONE;
  }

  moveLeft() {
    this.moveTo.x = -1;
    this.moveTo.y = 0;
    this.sprite.anims.play(this.anim.Eat, true);
    this.sprite.angle = 0;
  }

  moveRight() {
    this.moveTo.x = 1;
    this.moveTo.y = 0;
    this.sprite.anims.play(this.anim.Eat, true);
    this.sprite.angle = 0;
  }

  moveUp() {
    this.moveTo.x = 0;
    this.moveTo.y = -1;
    this.sprite.anims.play(this.anim.Eat, true);
    this.sprite.angle = 0;
  }

  moveDown() {
    this.moveTo.x = 0;
    this.moveTo.y = 1;
    this.sprite.anims.play(this.anim.Eat, true);
    this.sprite.angle = 0;
  }

  update() {
    this.sprite.setVelocity(
      this.moveTo.x * this.speed,
      this.moveTo.y * this.speed
    );
    this.turn();
    if (
      this.directions[this.current] &&
      !this.isSafe(this.directions[this.current].index)
    ) {
      this.sprite.anims.play("faceRight", true);
    }
  }

  setDirections(directions) {
    this.directions = directions;
  }

  setTurningPoint(turningPoint) {
    this.turningPoint = turningPoint;
  }

  setTurn(turnTo) {
    if (
      !this.active ||
      !this.directions[turnTo] ||
      this.turning === turnTo ||
      this.current === turnTo ||
      !this.isSafe(this.directions[turnTo].index)
    ) {
      return false;
    }

    if (this.opposites[turnTo] && this.opposites[turnTo] === this.current) {
      this.move(turnTo);
      this.turning = Phaser.NONE;
      this.turningPoint = new Phaser.Geom.Point();
    } else {
      this.turning = turnTo;
    }
  }

  turn() {
    if (this.turning === Phaser.NONE) {
      return false;
    }

    //  This needs a threshold, because at high speeds you can't turn because the coordinates skip past
    if (
      !Phaser.Math.Within(this.sprite.x, this.turningPoint.x, this.threshold) ||
      !Phaser.Math.Within(this.sprite.y, this.turningPoint.y, this.threshold)
    ) {
      return false;
    }

    this.sprite.setPosition(this.turningPoint.x, this.turningPoint.y);
    this.move(this.turning);
    this.turning = Phaser.NONE;
    this.turningPoint = new Phaser.Geom.Point();
    return true;
  }

  move(direction) {
    this.playing = true;
    this.current = direction;

    switch (direction) {
      case Phaser.LEFT:
        this.moveLeft();
        break;

      case Phaser.RIGHT:
        this.moveRight();
        break;

      case Phaser.UP:
        this.moveUp();
        break;

      case Phaser.DOWN:
        this.moveDown();
        break;
    }
  }

  isSafe(index) {
    for (let i of this.safetile) {
      if (i === index) return true;
    }

    return false;
  }

  drawDebug(graphics) {
    let thickness = 4;
    let alpha = 1;
    let color = 0x00ff00;

    for (var t = 0; t < 9; t++) {
      if (this.directions[t] === null || this.directions[t] === undefined) {
        continue;
      }

      if (this.directions[t].index !== -1) {
        color = 0xff0000;
      } else {
        color = 0x00ff00;
      }

      graphics.lineStyle(thickness, color, alpha);
      graphics.strokeRect(
        this.directions[t].pixelX,
        this.directions[t].pixelY,
        32,
        32
      );
    }

    color = 0x00ff00;
    graphics.lineStyle(thickness, color, alpha);
    graphics.strokeRect(this.turningPoint.x, this.turningPoint.y, 1, 1);
  }
}

