'use strict';
import PlayerId from './players/player-id.js';
import GamesManager from './game-server/games-manager.js';
import MariadbConnector from './db/mariadb-connector.js';
import ServerToolListner from './tools/server-tool-listener.js';
import io from 'socket.io';

function Server() {
  console.log('Starting 12 heroes server');

  const server = io.listen(process.env.VUE_APP_SERVER_PORT);
  let mariadbConn = new MariadbConnector();
  let gamesManager = new GamesManager(server, mariadbConn);

  let authorizedPlayers = new Map();
  let connectedPlayers = new Map();
  let authorizedPlayersNames = new Set();

  server.on('connection', function (socket) {
    socket.on('disconnect', reason => {
      console.log('A player disconnected, reason : ' + reason);
      if (
        reason === 'client namespace disconnect' ||
        reason === 'server namespace disconnect' ||
        reason === 'transport close'
      ) {
        let username = connectedPlayers.get(socket.id);
        if (username) {
          let player = authorizedPlayers.get(username);

          // If player had created a game, remove it
          removeAllGamesCreatedByPlayer(username)
            .then(res => {
              if (res.affectedRows > 0) {
                // Tell other clients to reload their games if there were
                forceClientsReloadGames();
              }
            })
            .catch(err => console.log('Error removing game :>> ', err.message));
          // Tell game manager a player left, if he was playing
          if (player.isPlayingGameId >= 0) {
            gamesManager.playerLeft(player, true);
          }
          connectedPlayers.delete(socket.id);
          player.setConnected(false);
          player.setSocket(null);
          console.log(username + ' disconnected');
        }
      }
    });

    socket.on('auth', async (playerName, callback) => {
      console.log(' Received auth message, player name : ' + playerName);
      await updatePlayersFromDb();

      let response = {};
      let kickout = false;
      if (!authorizedPlayersNames.has(playerName)) {
        response = {
          res: 'ko',
          message: playerName + ' Not found'
        };
        kickout = true;
      }
      // If the player is already connected and active
      else if (
        authorizedPlayers.get(playerName).isConnected() &&
        authorizedPlayers.get(playerName).getSocket().connected
      ) {
        response = {
          res: 'ko',
          message: playerName + ' already connected'
        };
        kickout = true;
      } else {
        // Player marked as connected but socket is down, clean it
        if (authorizedPlayers.get(playerName).isConnected()) {
          authorizedPlayers
            .get(playerName)
            .getSocket()
            .disconnect(true);
        } else {
          // In case server did not detect disconnection and did not clean the pending created games
          try {
            await removeAllGamesCreatedByPlayer(playerName);
          } catch (err) {
            console.log('Error removing games : ' + err.message);
          }
        }
        response = {
          res: 'ok',
          message: playerName + ' connected'
        };
        authorizedPlayers.get(playerName).setConnected(true);
        authorizedPlayers.get(playerName).setSocket(socket);
        connectedPlayers.set(socket.id, playerName);
      }
      console.log('auth called, result : ', response.message);
      callback(response);
      if (kickout === true) {
        setTimeout(() => {
          socket.disconnect(true);
        }, 600);
      }
    });

    socket.on('games-list', async (playerName, callback) => {
      let response = {};
      try {
        let games = await getJoinableGames(playerName);
        response = {
          res: 'ok',
          message: games
        };
      } catch (error) {
        response = {
          res: 'ko',
          message: 'Error from server'
        };
      }
      callback(response);
    });

    /** Interface to create a game
     *
     * @param
     */
    socket.on('create-game', async (game, callback) => {
      let response = {};
      try {
        let id = await addGameDb(game);
        response = {
          res: 'ok',
          message: id
        };
        gamesManager.addPlayerInGame(
          authorizedPlayers.get(connectedPlayers.get(socket.id)),
          id,
          false,
          true,
          { deck: game.deck, advRules: game.advRules }
        );
        forceClientsReloadGames();
      } catch (error) {
        console.log('In create-game catch error :>> ', error);
        response = {
          res: 'ko',
          message: 'Error from server'
        };
      }
      console.log('create-game called, result : ', response.message);
      callback(response);
    });

    socket.on('join-game', async (gameDetails, callback) => {
      let result = await gamesManager.addPlayerInGame(
        authorizedPlayers.get(connectedPlayers.get(socket.id)),
        gameDetails.id,
        gameDetails.joinCreatedGame
      );
      let response = {};
      if (result === true) {
        response.res = 'ok';
        response.message = 'Game joined';
      } else {
        response.res = 'ko';
        response.message = 'Unable to join game';
      }
      callback(response);
    });

    socket.on('leave-game', (username, callback) => {
      // Remove player from game he is playing
      removeAllGamesCreatedByPlayer(username)
        .then(res => {
          if (res.affectedRows > 0) {
            forceClientsReloadGames();
          }
        })
        .catch(err => {
          console.log('Error removing games : ' + err.message);
        });

      gamesManager.playerLeft(authorizedPlayers.get(username), false);
      callback(true);
    });
  });

  let updatePlayersFromDb = async function () {
    try {
      let usernames = await mariadbConn.getUsernames();
      authorizedPlayersNames = new Set(usernames);
      authorizedPlayersNames.forEach(name => {
        if (!authorizedPlayers.has(name)) {
          authorizedPlayers.set(name, new PlayerId(name));
        }
      });
      return 'ok';
    } catch (error) {
      console.log('Error getting usernames from DB : ', error);
      return error.Error;
    }
  };

  let getCurrentListAuthorizedPlayers = function () {
    return [...authorizedPlayersNames];
  };
  let addPlayerDb = async function (username) {
    console.log('add player in db : ' + username);
    try {
      let response = await mariadbConn.addPlayer(username);
      authorizedPlayersNames.add(username);
      authorizedPlayers.set(username, new PlayerId(username));
      return 'OK ' + JSON.stringify(response);
    } catch (err) {
      return 'KO ' + err.message;
    }
  };
  let removePlayerDb = async function (username) {
    console.log('remove player in db : ' + username);
    try {
      let response = await mariadbConn.removePlayer(username);
      authorizedPlayersNames.delete(username);
      authorizedPlayers.delete(username);
      return 'OK ' + JSON.stringify(response);
    } catch (err) {
      return 'KO ' + err.message;
    }
  };

  let addGameDb = async function (game) {
    try {
      let response = await mariadbConn.addNewGame(game);
      return response;
    } catch (err) {
      console.log('KO ' + err.message);
      throw err;
    }
  };

  let removeGameDb = async function (method, ...args) {
    try {
      let response;
      switch (method) {
        case 'byId':
          response = await mariadbConn.removeGameById(args[0]);
          break;
        case 'byPlayer1':
          response = await mariadbConn.removeGamesByPlayer1(args[0]);
          break;
        case 'byPlayerAny':
          response = await mariadbConn.removeGamesByPlayerAny(args[0]);
          break;
        case 'byStatus':
          response = await mariadbConn.removeGamesByStatus(args[0]);
          break;
        case 'byDays':
          response = await mariadbConn.removeGamesByDays(args[0]);
          break;
        case 'finishedByDays':
          response = await mariadbConn.removeFinishedGamesByDays(args[0]);
          break;

        default:
          return 'KO : Unknown method to remove game from DB : ' + method;
      }
      return 'OK ' + JSON.stringify(response);
    } catch (err) {
      return 'KO ' + err.message;
    }
  };

  let getJoinableGames = async function (username) {
    try {
      let games = await mariadbConn.getJoinableGamesForPlayer(username);
      return games;
    } catch (err) {
      console.log('KO : not able to get games : ' + err);
      throw err;
    }
  };

  let removeAllGamesCreatedByPlayer = async function (playerName) {
    let result = await mariadbConn.removeCreatedGamesByPlayer1(playerName);
    return result;
  };

  let forceClientsReloadGames = function () {
    server.emit('reload-games-list');
  };

  return {
    updatePlayersFromDb,
    getCurrentListAuthorizedPlayers,
    addPlayerDb,
    removePlayerDb,
    addGameDb,
    removeGameDb,
    getJoinableGames
  };
}

let server = new Server();

let serverToolListner = new ServerToolListner(server);
serverToolListner.listen(1664);
