package it.polimi.ingsw.gc14;


import it.polimi.ingsw.gc14.Controller.GameController;
import it.polimi.ingsw.gc14.Model.Game;
import it.polimi.ingsw.gc14.Network.NetworkEvent;
import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer;
import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer;

import java.rmi.RemoteException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;


/**
 * Main server launcher that handles both TCP and RMI connections.
 * The workflow is divided into two parts: game creation and game execution.
 *
 * The process flow for game creation is as follows:
 *   - The first client (TCP/RMI) requests to join the game by providing a username and the desired number of players
 *   - The TCP/RMI server checks {@link #playerList} and, if it is empty, sets the number of players according to the first user's request using {@link LimitedList#setLimit(int)}
 *   - The TCP/RMI server creates the {@link Game} with the requested number of players and adds the player to {@link #playerList}
 *   - Other players request to join the game (their requested number of players is ignored)
 *   - When the number of players in {@link #playerList} reaches the {@link LimitedList}'s limit, the list calls {@link #run()}
 *   - All players are notified of the {@link Game}
 *
 * The process flow for game execution is as follows:
 *   - The TCP/RMI server receives a {@link NetworkEvent} from a client and adds it to the {@link #actionQueue}
 *   - The {@link #run()} method repeatedly calls {@link #doFirstEvent()}, which takes the first event in the {@link #actionQueue} and tries to apply it
 *   - If the event cannot be successfully applied to the model, its {@code isError} flag is set to {@code true}
 *   - All players are notified of the event
 */
public class ServerLauncher {

    /**
     * Queue containing the events to be applied to the game model.
     * Thread safe by design.
     */
    BlockingQueue<NetworkEvent> actionQueue;

    /** Game controller. Used to apply events */
    GameController gameController;

    /** Server RMI. Handles RMI clients */
    RMIServer serverRMI;

    /** Server TCP. Handles TCP clients */
    TCPServer serverTCP;

    /**
     * List containing the usernames of joined players.
     * {@link LimitedList}'s limit defines at which size the list calls its action
     * Both the limit and the action can be set using {@link LimitedList#setLimit(int)} and {@link LimitedList#setAction(Runnable)}
     * The limit is set by the first player joining the game. The action consists in calling {@link #run()}
     */
    static LimitedList<String> playerList;


    /**
     * Class constructor that initializes the attributes.
     * @param actionQueue The queue containing the events
     * @param gameController The game controller
     * @param serverRMI The server RMI
     * @param serverTCP The server TCP
     */
    public ServerLauncher(BlockingQueue<NetworkEvent> actionQueue, GameController gameController, RMIServer serverRMI, TCPServer serverTCP) {
        this.actionQueue = actionQueue;
        this.serverRMI = serverRMI;
        this.gameController = gameController;
        this.serverTCP = serverTCP;
    }


    /**
     * Takes the first event in the actionQueue and attempts to apply it to the game controller.
     * If the event cannot be applied, its isError flag is set to true; otherwise, it is set to false.
     * All clients (both TCP and RMI) are notified of the event
     * @return the outcome of applying the event to the controller
     * @throws InterruptedException if an error occurs while accessing the actionQueue
     * @throws RemoteException if an RMI error occurs
     */
    public boolean doFirstEvent() throws InterruptedException, RemoteException {
        NetworkEvent event = actionQueue.take();
        event.setIsError(!event.apply(gameController));

        serverRMI.notifyAll(event);
        serverTCP.notifyAll(event);

        return !event.getIsError();
    }


    /**
     * The first method executed when the server program is launched.
     * It creates all the objects needed: playerList, actionQueue, gameController, serverRMI, serverTCP, launcher.
     * Then sets the playerList's action to execute launcher.run() and starts the TCP/RMI servers.
     * Note: the model is initialized and set in the controller in TCP/RMI servers when the first user decides the number of players.
     *
     * @throws InterruptedException if this exception is issued by run method
     * @throws RemoteException if this exception is issued by run method
     */
    public static void main(String[] args) throws InterruptedException, RemoteException {
        playerList = new LimitedList<>(5, ()->{});
        BlockingQueue<NetworkEvent> actionQueue = new LinkedBlockingQueue<>();
        GameController gameController = new GameController();
        RMIServer serverRMI = new RMIServer(gameController, 1099, actionQueue, playerList);
        TCPServer serverTCP = new TCPServer(gameController, 8080, actionQueue, playerList);
        ServerLauncher launcher = new ServerLauncher(actionQueue, gameController, serverRMI, serverTCP);

        playerList.setAction(()->{
            try {
                launcher.run();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        });

        serverRMI.start();
        new Thread(()->{serverTCP.start();}).start();
    }


    /**
     * Creates and executes the game.
     * Game creation: TCP/RMI servers send the game model to all players.
     * Game execution: repeatedly calls doFirstEvent() to process the events in the actionQueue.
     * @throws InterruptedException if the TCP server thread is interrupted
     * @throws RemoteException if an RMI error occurs
     */
    public void run() throws InterruptedException, RemoteException {
        // Game creation
        serverRMI.notifyAll(gameController.getModel());
        serverTCP.notifyAll(gameController.getModel());

        // Game execution
        while (true) {
            try {
                this.doFirstEvent();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
