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

import it.polimi.ingsw.gc14.Model.Game;
import it.polimi.ingsw.gc14.Network.NetworkEvent;

import java.io.*;
import java.net.*;
import java.util.List;
import java.util.concurrent.BlockingQueue;

/**
 * Handles the TCP connection with a single client.
 * Each instance runs on a dedicated thread and is responsible for receiving {@link NetworkEvent} from its client and adding them
 * to the {@link #actionQueue}.
 * It is also responsible to send events and game model updates back to the client.
 */
public class ClientHandler implements Runnable {

    /** The TCP socket */
    private Socket clientSocket;

    /** Input stream used to receive objects from the client */
    public ObjectInputStream in;

    /** Output stream used to send objects to the client */
    public ObjectOutputStream out;

    /**
     * Shared list of all client handlers.
     * This handler removes itself from the list when disconnected.
     */
    List<ClientHandler> clientHandlers;

    /** Queue containing the events to be applied to the game model */
    BlockingQueue<NetworkEvent> actionQueue;


    /**
     * Class constructor that initializes the attributes.
     * @param clientSocket   The socket representing the client's TCP connection
     * @param clientHandlers The shared list of all active client handlers
     * @param actionQueue    The queue containing incoming events
     */
    public ClientHandler(Socket clientSocket, List<ClientHandler> clientHandlers, BlockingQueue<NetworkEvent> actionQueue) {
        this.clientSocket = clientSocket;
        this.clientHandlers = clientHandlers;
        this.actionQueue = actionQueue;
    }


    /**
     * Listens for incoming {@link NetworkEvent}s from the client and adds them to the {@link #actionQueue}.
     * Upon disconnection, this handler removes itself from {@link #clientHandlers}.
     */
    @Override
    public void run() {
        try {
            in = new ObjectInputStream(clientSocket.getInputStream());
            out = new ObjectOutputStream(clientSocket.getOutputStream());
            while (true) {
                NetworkEvent event = (NetworkEvent) in.readObject();
                if (!actionQueue.add(event)) {
                    System.out.println("Error inserting action into queue");
                }
            }
        } catch (IOException e) {
            clientHandlers.remove(this);
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * Sends a {@link NetworkEvent} to the client.
     * @param event The network event to send to the client.
     */
    public void notifyEvent(NetworkEvent event) {
        synchronized (out) {
            try {
                out = new ObjectOutputStream(clientSocket.getOutputStream());
                out.writeObject(event);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * Sends the current game model to this client.
     * @param game The current state of the game to send to the client.
     */
    public void notifyModel(Game game) {
        synchronized (out) {
            try {
                out = new ObjectOutputStream(clientSocket.getOutputStream());
                out.writeObject(game);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}