import java.io.*;
import java.util.ArrayList;
import java.net.Socket;


public class ClientHandler implements Runnable { //Running on a seperate Thread 
    
    //Tracking a list of clients that are available in the server to communicate one and another while server is running
    public static ArrayList<ClientHandler> clientHandlers = new ArrayList<>(); //static is used to belong to this class
    //Declare objects for the ClientHandler property
    private Socket socket; // Link between client and server through communication on the same network.
    private BufferedReader bufferedReader; //Reads data such as messages
    private BufferedWriter bufferedWriter; //Sends messages that have been sent from clients
    private String clientUsername; //Assign to any name.

    public ClientHandler(Socket socket) {
        //Applying try block allows definition of a block of code to be tested through the I/O exceptions.
        //This is used to set the socket in the server class
        try {
            this.socket = socket; //this method used to make the object from the ClientHandler class
            //Each socket has an output stream that can send data to other clients in same server
            //wrapped I/O for efficiency
            this.bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            //What client is sending to other clients in the server.
            this.bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //Message that can be sent under the client username
            this.clientUsername = bufferedReader.readLine();
            //passed to the arraylist of the ClientHandler
            clientHandlers.add(this);
            broadcast("SERVER: " + clientUsername + " has entered the chat.");
        } catch (IOException e) { //prevent nested try catch block
            shutdownApplication(socket, bufferedReader, bufferedWriter); //Close socket
        }
    }

    //Override method occurs when another class implements a method that is already defined from the Server class.
    @Override
    public void run(){
        String messageFromClient;
        //while connected to a client, listen for messages
        while (socket.isConnected()) { //loop break until client receive a message
            try{
                messageFromClient = bufferedReader.readLine(); 
                broadcast(messageFromClient);
            } catch (IOException e) {
                shutdownApplication(socket, bufferedReader, bufferedWriter); 
                break; //breaks while loop once client leaves
            }
        }
    }

    public void broadcast(String messageToSend) {
        for (ClientHandler clientHandler : clientHandlers) { //for each client connected will be in the arraylist after each loop
            try {
                if (!clientHandler.clientUsername.equals(clientUsername)) { //if not equal then it will broadcast the message from the client
                    clientHandler.bufferedWriter.write(messageToSend); //each clienthandler has bufferedwriter to send data output. They can broadcast a message
                    clientHandler.bufferedWriter.newLine(); 
                    clientHandler.bufferedWriter.flush(); 
                }
            } catch (IOException e) {
                shutdownApplication(socket, bufferedReader, bufferedWriter);
            }
        }
    }

    public void removeClientHandler() {
        clientHandlers.remove(this); //removes current handler after client leaves
        broadcast("Server INFO: " + clientUsername + " has left the chat.");
    }

    public void shutdownApplication(Socket socket, BufferedReader bufferedReader, BufferedWriter bufferedWriter) { //close stream
        removeClientHandler(); //removes client from the arraylist
        try { //try catch block to shutdown the server
            if (bufferedReader != null) {
                bufferedReader.close();
            }
            if (bufferedWriter != null) {
                bufferedWriter.close();
            }
            if (socket != null) {
                socket.close();
            }
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
}
