package gltronic.tronio.business;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.WebSocketSession;

import gltronic.tronio.model.Game;
import gltronic.tronio.model.Player;
import gltronic.tronio.model.Wall;

@Service
public class GameManager implements IGameManager {
  @Autowired
  Game game;

  @Override
  public void login(WebSocketSession session) throws InterruptedException, IOException {
    if (game.getSessions().containsKey(session.getId())) {
      SocketUtils.sendMessage(session, "error", "cant login twice");
      return;
    }

    Player player = initPlayer(new Player());

    game.getSessions().put(session.getId(), session);
    game.getPlayers().put(session.getId(), player);

    System.out.println("[GAME] Player " + session.getId() + " logged in | status: " + player.getX() + " " + player.getY() + " " + player.getColor());

    SocketUtils.sendObject(session, "login", new ObjectMapper().writeValueAsString(player));
    SocketUtils.sendObject(session, "gameSettings", new ObjectMapper().writeValueAsString(game.getSettings()));
    SocketUtils.sendObject(session, "gameUpdate", new ObjectMapper().writeValueAsString(game.getPlayers().values()));
  }

  @Override
  public void leave(WebSocketSession session) throws InterruptedException, IOException {
    game.getSessions().remove(session.getId());
    game.getPlayers().remove(session.getId());
  }

  @Override
  public void updatePlayer(WebSocketSession session, Player player) throws InterruptedException, IOException {
    Player playerToUpdate = game.getPlayers().get(session.getId());
    playerToUpdate.setAngle(player.getAngle());
    playerToUpdate.setTargetAngle(player.getTargetAngle());
  }

  @Override
  @Scheduled(fixedDelay = 1000 / 60)
  public void step() throws InterruptedException, IOException {
    // CHECK OUT OF BORDERS & COLISIONS
    game.getPlayers().forEach((id, player) -> {
      // OUT OF BORDERS
      if (player.getX() - game.getSettings().getPlayerSize() < 0
          || player.getX() + game.getSettings().getPlayerSize() > game.getSettings().getArenaSize()
          || player.getY() - game.getSettings().getPlayerSize() < 0
          || player.getY() + game.getSettings().getPlayerSize() > game.getSettings().getArenaSize()) {
        System.out.println("[GAME] border");
        killPlayer(id);
        return;
      }

      // COLLISIONS
      game.getPlayers().forEach((id2, player2) -> {
        for (var i = 0; i < player2.getWalls().size() - 2; i++) {
          // Pour evité la collision avec un mur venant d'être placé par le même joueur
          if (id.equals(id2) && i >= player2.getWalls().size() - 4) break;

          Wall wallA = player2.getWalls().get(i);
          Wall wallB = player2.getWalls().get(i + 1);
          if (isCloseToWall(wallA.getX(), wallA.getY(), wallB.getX(), wallB.getY(), player.getX(), player.getY(),
              game.getSettings().getPlayerSize())) {
            System.out.println("[GAME] close to wall");
            if (this.isCrossingLine(wallA.getX(), wallA.getY(), wallB.getX(), wallB.getY(), player.getX(),
                player.getY(), game.getSettings().getPlayerSize())) {
              System.out.println("[GAME] touch da wall");
              killPlayer(id);
              return;
            }
          }
        }
      });
    });

    // ADD WALLS & MOVE PLAYER
    game.getPlayers().forEach((id, player) -> {
      // WALL
      player.setLastWall(player.getLastWall() + 1);
      if (player.getLastWall() > game.getSettings().getWallUpdate()) {
        player.getWalls().add(new Wall(player.getX(), player.getY()));
        player.setLastWall(0);
      }

      // MOVE
      player.setX(player.getX() + game.getSettings().getPlayerSpeed() * Math.cos(player.getAngle()));
      player.setY(player.getY() + game.getSettings().getPlayerSpeed() * Math.sin(player.getAngle()));
    });

    // Broadcast une fois sur deux
    if (game.isUpdateNeeded()) {
      SocketUtils.broadcast(game, "gameUpdate", new ObjectMapper().writeValueAsString(game.getPlayers().values()));
      game.setUpdateNeeded(false);
    } else game.setUpdateNeeded(true);
  }

  private void killPlayer(String id) {
    Player player = game.getPlayers().get(id);
    initPlayer(player);
    System.out.println("[GAME] Player " + id + " is dead | status: " + player.getX() + " " + player.getY() + " " + player.getColor());
    try {
      SocketUtils.sendObject(game.getSessions().get(id), "gamePlayerDead",
          new ObjectMapper().writeValueAsString(player));
    } catch (JsonProcessingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  private boolean isCloseToWall (double xa, double ya, double xb, double yb, double xc, double yc, double radius) {
    var xar = Math.min(xa, xb) - radius;
    var yar = Math.min(ya, yb) - radius;
    var xbr = Math.max(xa, xb) + radius;
    var ybr = Math.max(ya, yb) + radius;

    return ((xc >= xar && xc <= xbr) && (yc >= yar && yc <= ybr));
  }

  private boolean isCrossingLine (double xa, double ya, double xb, double yb, double xc, double yc, double radius) {
    /*
    var a = xc - xa;
    var b = xb - xa;
    var c = yc - ya;
    var d = yb - ya;
    var result = (a * d - b * c) / Math.sqrt(Math.pow(xa - xb, 2) + Math.pow(ya - yb, 2));
    */

    var result = Math.abs((yb - ya) * xc - (xb - xa) * yc + xb * ya - yb * xa)/ Math.sqrt(Math.pow(xb - xa, 2) + Math.pow(yb - ya, 2));

    return result < radius;
  }

  private Player initPlayer(Player player) {
    Random rand = new Random();
    double x = game.getSettings().getArenaSize() * (0.25 + Math.random() * 0.5);
    double y = game.getSettings().getArenaSize() * (0.25 + Math.random() * 0.5);

    // player.setAngle(0);
    // player.setTargetAngle(0);
    player.setLastWall(0);
    player.setWalls(new ArrayList<Wall>());
    player.setColor(String.format("#%06x", rand.nextInt(0xffffff + 1)));
    player.setX(x);
    player.setY(y);

    return player;
  }
}