package it.polimi.ingsw.gc14.Network.RMI.Server;

import it.polimi.ingsw.gc14.Controller.GameController;
import it.polimi.ingsw.gc14.LimitedList;
import it.polimi.ingsw.gc14.Model.Game;
import it.polimi.ingsw.gc14.Network.NetworkEvent;
import it.polimi.ingsw.gc14.Network.RMI.Common.IClientCallback;
import it.polimi.ingsw.gc14.Network.RMI.Common.IGameServer;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;

import java.rmi.*;


/**
 * Server RMI. Exposes a method to join the game and one to execute an event.
 */
public class RMIServer implements IGameServer {

    /** Server game's controller */
    private GameController controller;

    /** Server game's model */
    private Game model;

    /** RMI registry */
    private Registry registry;

    /** RMI port */
    private int nPort;

    /** Map containing the associations between a player's username and its callback */
    private final Map<String, IClientCallback> clients = new ConcurrentHashMap<>();

    /** Queue containing the events to be applied to the game model */
    BlockingQueue<NetworkEvent> actionQueue;

    /**
     * List containing the usernames of joined players.
     * {@link LimitedList}'s limit defines at which size the list calls its action. The limit can be set using {@link LimitedList#setLimit(int)}.
     */
    private LimitedList<String> playerList;


    /**
     * Class constructor that initializes the attributes.
     * @param controller The game controller
     * @param nPort The RMI port
     * @param actionQueue The action queue
     * @param playerList The player's usernames list
     * @throws RemoteException if an RMI error occurs
     */
    public RMIServer(GameController controller, int nPort, BlockingQueue<NetworkEvent> actionQueue,LimitedList<String> playerList) throws RemoteException {
        this.controller = controller;
        this.nPort = nPort;
        this.actionQueue = actionQueue;
        this.playerList = playerList;
    }



    // RMI's exposed methods
    /**
     * Allows a player to join the game.
     * If the desired number of player is invalid, the request is rejected.
     * If this is the first player, a new game model is created and passed to the controller. Additionally, the playerList's limit is set.
     * Then, if the controller successfully adds the player, the username is added to {@link #playerList} and {@link #clients}.
     * @param username The player's name
     * @param preferredInt The desired number of players
     * @param callback The client's callback interface
     * @return true if the player successfully joined the game, false otherwise
     */
    @Override
    public boolean joinGame(String username, int preferredInt, IClientCallback callback) {
        if (preferredInt<2 || preferredInt>5) {
            return false;
        }
        synchronized (controller) {
            if(playerList.isEmpty()){
                model = new Game(preferredInt);
                controller.setModel(model);
                playerList.setLimit(preferredInt);
            }
            if (controller.addPlayer(username)) {
                playerList.add(username);
                clients.put(username, callback);
                return true;
            }
            return false;
        }
    }


    /**
     * Push an action in actionQueue.
     * @param action The desired actio
     * @return true if the action was successfully added, false otherwise
     */
    @Override
    public boolean doEvent(NetworkEvent action) {
        return actionQueue.offer(action);
    }



    // RMI's internal methods
    /**
     * Sends an action to all RMI clients.
     * @param action The desired action
     */
    public void notifyAll(NetworkEvent action) throws RemoteException {
        for (IClientCallback cb : clients.values()) {
            cb.onAction(action);
        }
    }


    /**
     * Sends a game model to all RMI clients.
     * @param model The desired model
     */
    public void notifyAll(Game model) throws RemoteException {
        for (IClientCallback cb : clients.values()) {
            cb.onGameInit(model);
        }
    }



    // Metodi per avviare server RMI
    /**
     * Starts the RMI server.
     * @return true if the server starts successfully, false otherwise
     */
    public boolean start() {
        try {
            registry = LocateRegistry.createRegistry(nPort);
            registry.rebind("RMIGameServer", this);
            System.out.println("RMI Server started on port: "+nPort);
            return true;
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * Stops the RMI server.
     * @return true if the server stops successfully, false otherwise
     */
    public boolean stop() {
        try {
            registry.unbind("RMIGameServer");
            UnicastRemoteObject.unexportObject(this, true);
            System.out.println("RMI Server fermato");
            return true;
        } catch (RemoteException | NotBoundException e) {
            e.printStackTrace();
            return false;
        }
    }


}
