////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////

package pp.battleship.game.singlemode;

import pp.battleship.game.client.BattleshipClient;
import pp.battleship.game.client.ServerConnection;
import pp.battleship.game.server.ServerGameLogic;
import pp.battleship.game.server.ServerSender;
import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.server.ServerInterpreter;
import pp.battleship.message.server.ServerMessage;
import pp.battleship.model.dto.ShipMapDTO;

import java.io.IOException;

import static pp.battleship.game.singlemode.Copycat.copy;

/**
 * A mock implementation of the ServerConnection interface for single mode.
 * Simulates a server connection without actual network communication.
 * Handles a mock player named RobotClient and schedules its shots when due.
 */
public class ServerConnectionMockup implements ServerConnection, ServerSender {
    /**
     * The id of the player client.
     */
    private static final int PLAYER_CLIENT = 1;
    /**
     * The id of the robot client.
     */
    private static final int ROBOT_CLIENT = 2;

    private final BattleshipClient playerClient;
    private final RobotClient robotClient;
    private final InterpreterProxy playerProxy;
    private final ServerGameLogic serverGameLogic;

    /**
     * Constructs a ServerConnectionMockup instance for the given Battleship application.
     * Creates a RobotClient instance and an instance of ServerGameLogic.
     *
     * @param playerClient The Battleship client instance, e.g., a BattleshipApp instance.
     */
    public ServerConnectionMockup(BattleshipClient playerClient) {
        this.playerClient = playerClient;
        robotClient = new RobotClient(playerClient, this, playerClient.getConfig(), getShipMapDTO());
        serverGameLogic = new ServerGameLogic(this, playerClient.getConfig());
        playerProxy = new InterpreterProxy(playerClient);
    }

    /**
     * Always returns true as this is a mock connection.
     *
     * @return true, indicating the mock connection is always considered connected.
     */
    @Override
    public boolean isConnected() {
        return true;
    }

    /**
     * Simulates connecting to a server by adding the PlayerClient and the RobotClient to the serverGameLogic.
     * Loads the map of the PlayerClient and triggers sending it to the serverGameLogic.
     */
    @Override
    public void connect() {
        serverGameLogic.addPlayer(PLAYER_CLIENT);
        serverGameLogic.addPlayer(ROBOT_CLIENT);
    }

    /**
     * Does nothing upon shutdown of the app.
     */
    @Override
    public void disconnect() {
        //do nothing
    }

    /**
     * Forwards the specified message received from the player client to the server logic.
     *
     * @param message The message from the player client to be processed.
     */
    @Override
    public void send(ClientMessage message) { // from PlayerClient, as this is the PlayerClients 'serverConnection'
        copy(message).accept(serverGameLogic, PLAYER_CLIENT);
    }

    /**
     * Forwards the specified message received from the robot client to the server logic.
     *
     * @param message The message from the robot client to be processed.
     */
    void sendRobotMessage(ClientMessage message) {
        message.accept(serverGameLogic, ROBOT_CLIENT);
    }

    /**
     * Forwards the specified message received from the server logic either to the player client or to the
     * robot client, depending on the specified id.
     *
     * @param id      The recipient id
     * @param message The server message to be processed
     * @see #PLAYER_CLIENT
     * @see #ROBOT_CLIENT
     */
    @Override
    public void send(int id, ServerMessage message) {
        message.accept(getInterpreter(id));
    }

    /**
     * Retrieves the ServerInterpreter of the client with the specified id.
     *
     * @param clientId the id of the client whose ServerInterpreter shall be retrieved.
     * @return the ServerInterpreter of the client
     * @throws java.lang.IllegalArgumentException if there is no client with the specified id.
     */
    private ServerInterpreter getInterpreter(int clientId) {
        return switch (clientId) {
            case PLAYER_CLIENT -> playerProxy;
            case ROBOT_CLIENT -> robotClient;
            default -> throw new IllegalArgumentException("Unexpected value: " + clientId);
        };
    }

    /**
     * Loads the ShipMapDTO from the opponent map file.
     *
     * @return the loaded ShipMapDTO.
     */
    private ShipMapDTO getShipMapDTO() {
        try {
            return ShipMapDTO.loadFrom(playerClient.getConfig().getOpponentMap());
        }
        catch (IOException e) {
            throw new RuntimeException("Failed to load RobotClient map", e);
        }
    }
}