package it.polimi.ingsw.gc14.Network.TCP.Client;

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 java.io.*;
import java.net.*;

/**
 * Client TCP. Sends and receives messages with the TCP server.
 */
public class TCPClient {

    /** Socket TCP */
    Socket communicationSocket;

    /** Input stream used receive objects from the server */
    ObjectInputStream socketReceive;

    /** Output stream used to send objects to the server */
    ObjectOutputStream socketSend;

    /** Client game's controller */
    GameController controller;

    /** IP address of the server to connect to */
    String hostname;

    /** TCP port */
    int port;


    /**
     * Class constructor that initializes the attributes.
     * @param controller The game controller
     * @param hostname   The IP address of the server
     * @param port       The TCP port of the server
     */
    public TCPClient(GameController controller, String hostname, int port) {
        this.controller = controller;
        this.hostname = hostname;
        this.port = port;
    }


    /**
     * Starts the TCP connection with the server.
     * Sends an {@link AddPlayer} event, if the server responds with {@code -1}, the connection is refused and the method returns {@code false}.
     * Otherwise, a listener thread is started.
     * @param user             The username of the player
     * @param proposedNPlayers The desired number of players for the game
     * @return true if the connection is successful, false otherwise.
     */
    public boolean start(String user, int proposedNPlayers) {
        try {
            communicationSocket = new Socket(hostname, port);
            socketSend = new ObjectOutputStream(communicationSocket.getOutputStream());
            socketReceive = new ObjectInputStream(communicationSocket.getInputStream());

            sendEvent(new AddPlayer(user, proposedNPlayers));
            if (communicationSocket.getInputStream().read() == -1) {
                System.out.println("Could not connect to server");
                return false;
            } else {
                Thread listener = new Thread(() -> receiveMessage());
                listener.start();
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * Listens continuously for incoming objects from the server.
     *   - If the received object is a {@link NetworkEvent} flagged as an error, it is printed.
     *   - If the received object is a valid {@link NetworkEvent}, it is applied to the game controller.
     *   - If the received object is a {@link Game} model, the controller's model is set.
     */
    private void receiveMessage() {
        while (true) {
            try {
                Object read = socketReceive.readObject();
                if (read instanceof NetworkEvent event) {    //TODO: avoid instanceof
                    if (event.getIsError()) {
                        System.out.println(event);
                    } else {
                        event.apply(controller);
                        //clientController.view.update();   TODO
                    }
                } else if (read instanceof Game model) {
                    controller.setModel(model);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }


    /**
     * Sends a {@link NetworkEvent} to the server.
     * @param event The NetworkEvent to send.
     */
    private void sendEvent(NetworkEvent event) {
        try {
            socketSend.writeObject(event);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}