import { v4 as uuidv4 } from 'uuid';
import WebSocket from 'ws';
import { Player } from './models/Player';
import { GameSettings } from './models/GameSettings';
import { GameWebSocket } from './models/GameWebSocket';

export class Game {
  private gameSettings = new GameSettings();
  private players = new Map<string, Player>();
  private sockets = new Map<string, WebSocket>();

  private computeUpdates = false;
  private updateInterval = setInterval(() => this.step(), 10000000);
  private lastUpdateTime = Date.now();
  private doUpdate = true;

  constructor () {
    clearInterval(this.updateInterval);
  }
  
  public login (connection: GameWebSocket, name: string): void {
    console.log('[GAME] Player ' + name + ' connected | '  + this.players.size);
    const id = uuidv4();
    connection.id = id;
    connection.name = name;
    this.sockets.set(id, connection);
    this.players.set(id, new Player(id, name));
  
    connection.send(JSON.stringify({
      type: 'login',
      player: this.players.get(id)
    }));
  
    connection.send(JSON.stringify({
      type: 'gameSettings',
      gameSettings: this.gameSettings
    }));
  
    if (!this.computeUpdates) {
      console.log('[GAME] Starting updates');
      this.updateInterval = setInterval(() => this.step(), 1000 / 60);
      this.computeUpdates = true;
    }
  }
  
  public logout (connection: GameWebSocket): void {
    console.log('[GAME] Player ' + connection.name + ' disconnected | ' + this.players.size);
    this.sockets.delete(connection.id);
    this.players.delete(connection.id);
  
    if (this.players.size === 0) {
      console.log('[GAME] Stoping updates');
      clearInterval(this.updateInterval);
      this.computeUpdates = false;
    }
  }
  
  public respawn (connection: GameWebSocket): void {
    console.log('[GAME] Player ' + connection.name + ' respawned');
    this.players.get(connection.id)?.reset();
  
    connection.send(JSON.stringify({
      type: 'gamePlayerSpawn',
      player: this.players.get(connection.id)
    }))
  }
  
  public update (connection: GameWebSocket, player: Player): void {
    const playerToUpdate = this.players.get(connection.id);
    if (playerToUpdate) playerToUpdate.angle = player.angle;
  }
  
  public kill (player: Player): void {
    console.log('[GAME] Player ' + player.name + ' died');
    player.kill();
    this.sockets.get(player.id)?.send(JSON.stringify({
      type: 'gamePlayerDead',
      player: player
    }))
  }
  
  private step (): void {
    const currentTime = Date.now()
    const durationSinceLastUpdate = (currentTime - this.lastUpdateTime) / 1000;
    const tickToSimulate = (durationSinceLastUpdate * 60) / 1000;
    this.lastUpdateTime = currentTime;
  
    // console.log('UPDATE ' + currentTime + ' doUpdate ' + doUpdate)
  
    this.players.forEach((player, id) => {
      if (player.isOutOfBorders()) this.kill(player);

      this.players.forEach((player2, id2) => {
        if (player.state === 'DEAD') return
  
        for (let i = 0; i < player2.walls.length - 2; i++) {
          // Prevent self destroy on last wall
          if (player === player2 && i >= player2.walls.length - 1) break
  
          const wallA = player2.walls[i]
          const wallB = player2.walls[i + 1]
  
          if (player.isCloseToWall(wallA, wallB)) {
            if (player.isCrossingLine(wallA, wallB)) {
              if (player !== player2) player2.score += 300
              this.kill(player);
              return
            }
          }
        }
      });
    });
    
    this.players.forEach((player, id) => {
      player.step(tickToSimulate);
    });
  
    if (this.doUpdate) this.broadcastUpdate();
    this.doUpdate = !this.doUpdate;
  }
  
  private broadcastUpdate () {
    const update = {
      type: 'gameUpdate',
      players: [ ...this.players.values() ],
      time: this.lastUpdateTime
    }
  
    this.sockets.forEach((connection, id) => {
      connection.send(JSON.stringify(update));
    });
  }
}
