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

package pp.battleship.client;

import com.jme3.network.Client;
import com.jme3.network.ClientStateListener;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.Network;
import pp.battleship.game.client.ServerConnection;
import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.server.ServerMessage;

import java.io.IOException;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;

import static pp.battleship.Resources.lookup;

/**
 * Manages the network connection for the Battleship application.
 * Handles connecting to and disconnecting from the server, and sending messages.
 */
class NetworkSupport implements MessageListener<Client>, ClientStateListener, ServerConnection {
    private static final Logger LOGGER = System.getLogger(NetworkSupport.class.getName());
    private final BattleshipApp app;
    private Client client;

    /**
     * Constructs a NetworkSupport instance for the given Battleship application.
     *
     * @param app The Battleship application instance.
     */
    public NetworkSupport(BattleshipApp app) {
        this.app = app;
    }

    /**
     * Returns the Battleship application instance.
     *
     * @return Battleship application instance
     */
    BattleshipApp getApp() {
        return app;
    }

    /**
     * Checks if there is a connection to the game server.
     *
     * @return true if there is a connection to the game server, false otherwise.
     */
    @Override
    public boolean isConnected() {
        return client != null && client.isConnected();
    }

    /**
     * Attempts to join the game if there is no connection yet.
     * Opens a dialog for the user to enter the host and port information.
     */
    @Override
    public void connect() {
        if (client == null)
            new NetworkDialog(this).open();
    }

    /**
     * Closes the client connection.
     */
    @Override
    public void disconnect() {
        if (client == null) return;
        client.close();
        client = null;
        LOGGER.log(Level.INFO, "client closed"); //NON-NLS
    }

    /**
     * Initializes the network connection.
     *
     * @param host The server's address.
     * @param port The server's port.
     * @throws IOException If an I/O error occurs when creating the client.
     */
    void initNetwork(String host, int port) throws IOException {
        if (client != null)
            throw new IllegalStateException("trying to join a game again");
        client = Network.connectToServer(host, port);
        client.start();
        client.addMessageListener(this);
        client.addClientStateListener(this);
    }

    /**
     * Called when a message is received from the server.
     *
     * @param client  The client instance that received the message.
     * @param message The message received from the server.
     */
    @Override
    public void messageReceived(Client client, Message message) {
        LOGGER.log(Level.INFO, "message received from server: {0}", message); //NON-NLS
        if (message instanceof ServerMessage serverMessage)
            app.enqueue(() -> serverMessage.accept(app.getGameLogic()));
    }

    /**
     * Called when the client has successfully connected to the server.
     *
     * @param client The client that connected to the server.
     */
    @Override
    public void clientConnected(Client client) {
        LOGGER.log(Level.INFO, "Client connected: {0}", client); //NON-NLS
    }

    /**
     * Called when the client is disconnected from the server.
     *
     * @param client         The client that was disconnected.
     * @param disconnectInfo Information about the disconnection.
     */
    @Override
    public void clientDisconnected(Client client, DisconnectInfo disconnectInfo) {
        LOGGER.log(Level.INFO, "Client {0} disconnected: {1}", client, disconnectInfo); //NON-NLS
        if (this.client != client)
            throw new IllegalArgumentException("parameter value must be client");
        LOGGER.log(Level.INFO, "client still connected: {0}", client.isConnected()); //NON-NLS
        this.client = null;
        disconnect();
        app.enqueue(() -> app.setInfoText(lookup("lost.connection.to.server")));
    }

    /**
     * Sends the specified message to the server.
     *
     * @param message The message to be sent to the server.
     */
    @Override
    public void send(ClientMessage message) {
        LOGGER.log(Level.INFO, "sending {0}", message); //NON-NLS
        if (client == null)
            app.errorDialog(lookup("lost.connection.to.server"));
        else
            client.send(message);
    }
}
