package pp.monopoly.client;

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

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.monopoly.game.client.ServerConnection;
import pp.monopoly.message.client.ClientMessage;
import pp.monopoly.message.server.ServerMessage;

/**
 * Manages the network connection for the Monopoly 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 MonopolyApp app;
    private Client client;

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

    /**
     * Returns the Monopoly application instance.
     *
     * @return Monopoly application instance
     */
    MonopolyApp 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 connection closed.");
    }

    /**
     * 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("Already connected to the game server.");
        }
        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);
        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, "Successfully connected to server: {0}", client);
    }

    /**
     * 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, "Disconnected from server: {0}", disconnectInfo);
        this.client = null;
        app.enqueue(() -> app.setInfoText("Verbindung zum Server verloren."));
    }

    /**
     * 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 message to server: {0}", message);
        if (client == null) {
            app.errorDialog("Verbindung zum Server verloren.");
        } else {
            client.send(message);
        }
    }
}
