package network.server;

import java.io.IOException;
import java.net.Socket;

import game.Game;
import game.Player;
import game.Game.GameState;
import network.Connexion;
import network.protocol.packets.ChangeCellPacket;
import network.protocol.packets.ConnexionInfoPacket;
import network.protocol.packets.DisconnectPacket;
import network.protocol.packets.EndGamePacket;
import network.protocol.packets.KeepAlivePacket;
import network.protocol.packets.LoginPacket;
import network.protocol.packets.PlayerJoinPacket;
import network.protocol.packets.PlayerLeavePacket;
import network.protocol.packets.StartGamePacket;
import network.protocol.packets.UpdatePlayerScorePacket;
import sudoku.io.SudokuSerializer;
import sudoku.structure.Cell;
import sudoku.structure.MultiDoku;

public class ServerConnexion extends Connexion {

	private final Server server;
	private final KeepAliveHandler keepAliveHandler;
	private boolean shouldClose = false;
	private Player player = null;
	private MultiDoku doku;

	public ServerConnexion(Socket socket, Server server) throws IOException {
		super(socket);
		this.server = server;
		this.keepAliveHandler = new KeepAliveHandler(this);
	}

	public boolean update() {
		if (shouldClose || isClosed())
			return false;
		return this.keepAliveHandler.update();
	}

	public void nukeConnection() {
		if (player != null) {
			sendPacket(new DisconnectPacket("Le serveur a été fermé !"));
			this.server.broadcastPacket(new PlayerLeavePacket(player.getId()));
			this.server.getGame().removePlayer(player.getId());
		}
	}

	@Override
	public synchronized void close() {
		if (shouldClose)
			return;
		super.close();
		shouldClose = true;
		System.out.println("[Server] Closing connexion !");
	}

	private void finishLogin() {
		// send players that have already joined (excluding this one)
		for (Player p : this.server.getGame().getPlayers().values()) {
			if (p.getId() != player.getId()) {
				sendPacket(new PlayerJoinPacket(p));
				sendPacket(new UpdatePlayerScorePacket(p.getId(), p.getRemainingCells()));
			}
		}

		this.server.broadcastPacket(new PlayerJoinPacket(player));
		sendPacket(new ConnexionInfoPacket(player.getId()));

		Game game = this.server.getGame();

		if (game.getGameState() == GameState.GameGoing) {
			setSudoku(game.getDoku().clone());
			sendPacket(
					new StartGamePacket(SudokuSerializer.serializeSudoku(game.getDoku()).toString(),
							game.getStartTime(), game.getGameDuration()));
		}
	}

	@Override
	public void visitPacket(KeepAlivePacket packet) {
		this.keepAliveHandler.recievedKeepAlive(packet.getKeepAliveId());
	}

	@Override
	public void visitPacket(DisconnectPacket packet) {
		close();
	}

	@Override
	public void visitPacket(LoginPacket packet) {
		if (this.player != null)
			return;
		this.player = this.server.addPlayer(packet.getPseudo());
		finishLogin();
	}

	@Override
	public void visitPacket(ConnexionInfoPacket packet) {
		throw new UnsupportedOperationException("Unimplemented method 'visitPacketConnexionInfo'");
	}

	@Override
	public void visitPacket(PlayerJoinPacket packet) {
		throw new UnsupportedOperationException("Unimplemented method 'visitPacketPlayerJoin'");
	}

	@Override
	public void visitPacket(PlayerLeavePacket packet) {
		throw new UnsupportedOperationException("Unimplemented method 'visitPacketPlayerLeave'");
	}

	@Override
	public void visitPacket(StartGamePacket packet) {
		throw new UnsupportedOperationException("Unimplemented method 'visitPacketStartGame'");
	}

	@Override
	public void visitPacket(EndGamePacket packet) {
		throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
	}

	@Override
	public void visitPacket(UpdatePlayerScorePacket packet) {
		throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
	}

	@Override
	public void visitPacket(ChangeCellPacket packet) {
		Cell cell = this.doku.getSubGrid(packet.getSudokuIndex()).getCell(packet.getCellIndex());
		if (cell.getSymbolIndex() == Cell.NOSYMBOL && packet.getNewValue() == Cell.NOSYMBOL)
			return;
		if (cell.getSymbolIndex() != Cell.NOSYMBOL && packet.getNewValue() != Cell.NOSYMBOL) {
			cell.trySetValue(packet.getNewValue());
			return;
		}
		if (cell.getSymbolIndex() != Cell.NOSYMBOL && packet.getNewValue() == Cell.NOSYMBOL) {
			cell.empty();
			this.server.getGame().setPlayerRemainingCells(player, player.getRemainingCells() + 1);
			this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getRemainingCells()));
			return;
		}
		// on rajoute un chiffre à la grille
		if (cell.trySetValue(packet.getNewValue())) {
			this.server.getGame().setPlayerRemainingCells(player, player.getRemainingCells() - 1);
			this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getRemainingCells()));
		}
		checkWin();
	}

	private void checkWin() {
		if (this.player.getRemainingCells() == 0) {
			this.server.stopGame();
		}
	}

	public void setSudoku(MultiDoku doku) {
		this.doku = doku;
		assert (player != null);
		this.server.getGame().setPlayerRemainingCells(player, this.doku.getEmptyCells().size());
		this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getRemainingCells()));
	}

}
