package kst4contest.controller;

import kst4contest.model.ChatMember;
import kst4contest.model.ChatPreferences;
import kst4contest.model.ThreadStateMessage;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

public class DXClusterThreadPooledServer implements Runnable{

    private static final Logger LOGGER = Logger.getLogger(DXClusterThreadPooledServer.class.getName());
    private List<Socket> clientSockets = Collections.synchronizedList(new ArrayList<>()); //list of all connected clients

    private ThreadStatusCallback callBackToController;
    private String ThreadNickName = "DXCluster-Server";
    ChatController chatController = null;
    protected int          serverPort   = 8080;
    protected ServerSocket serverSocket = null;
    protected boolean      isStopped    = false;
    protected Thread       runningThread= null;
    protected ExecutorService threadPool =
            Executors.newFixedThreadPool(10);
    Socket clientSocket;

    public DXClusterThreadPooledServer(int port, ChatController chatController, ThreadStatusCallback callback){
        this.serverPort = port;
        this.chatController = chatController;
        this.callBackToController = callback;
    }

    public void run(){

        ThreadStateMessage threadStateMessage = new ThreadStateMessage(this.ThreadNickName, true, "initialized", false);
        callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);

        synchronized(this){
            this.runningThread = Thread.currentThread();
            runningThread.setName("DXCluster-thread-pooled-server");
        }
        openServerSocket();
        while(! isStopped()){
            clientSocket = null;
            try {
                clientSocket = this.serverSocket.accept();

                synchronized(clientSockets) {
                    clientSockets.add(clientSocket);  // add dx cluster client to the "clients list" for broadcasting
                }

            } catch (IOException e) {
                if(isStopped()) {
                    System.out.println("Server Stopped.") ;
                    break;
                }
                throw new RuntimeException(
                        "Error accepting client connection", e);
            }

            DXClusterServerWorkerRunnable worker = new DXClusterServerWorkerRunnable(clientSocket, "Thread Pooled DXCluster Server ", chatController, clientSockets, chatController);

            this.threadPool.execute(worker);

        }
        this.threadPool.shutdown();
        System.out.println("Server Stopped.") ;
    }

    private synchronized boolean isStopped() {
        return this.isStopped;
    }

    public synchronized void stop(){
        this.isStopped = true;
        try {
            this.serverSocket.close();
            synchronized(clientSockets) {
                for (Socket socket : clientSockets) {
                    socket.close();  // close all client connections
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("DXCCSERVER Error closing server", e);
        }
    }

    private void openServerSocket() {
        try {
            this.serverSocket = new ServerSocket(this.serverPort);
        } catch (IOException e) {
            throw new RuntimeException("DXCCSERVER Cannot open port ", e);
        }
    }

    /**
     * Sends a DX cluster message to ALL connected log programs via telnet, returns true if sent
     *
     * @param aChatMember
     * @return boolean true if message had been sent
     */
    public boolean broadcastSingleDXClusterEntryToLoggers(ChatMember aChatMember) {
        synchronized(clientSockets) {

            System.out.println("DXClusterSrvr: broadcasting message to clients: " + clientSockets.size());

            try {

                System.out.println("-------------> ORIGINALEE VAL: " + aChatMember.getFrequency().getValue());
                System.out.println("-------------> NORMALIZED VAL: " + Utils4KST.normalizeFrequencyString(aChatMember.getFrequency().getValue(), chatController.getChatPreferences().getNotify_optionalFrequencyPrefix()) + " ");
            } catch (Exception e) {
                LOGGER.log(Level.SEVERE, "DXCThPooledServer: Error accessing value in chatmember object", e);
            }

            for (Socket socket : clientSockets) {

                try {

                OutputStream output = socket.getOutputStream();

                    String singleDXClusterMessage = "DX de ";

//                    singleDXClusterMessage += chatController.getChatPreferences().getLoginCallSign() + ":   ";




                    singleDXClusterMessage += this.chatController.getChatPreferences().getNotify_DXCSrv_SpottersCallSign().getValue() + ":   ";
                    singleDXClusterMessage += Utils4KST.normalizeFrequencyString(aChatMember.getFrequency().getValue(), chatController.getChatPreferences().getNotify_optionalFrequencyPrefix()) + "  ";
                    singleDXClusterMessage += aChatMember.getCallSign().toUpperCase() + "             "; //we need such an amount of spaces for n1mm to work, otherwise bullshit happens
                    singleDXClusterMessage += aChatMember.getQra().toUpperCase() + "  ";
                    singleDXClusterMessage += new Utils4KST().time_generateCurrenthhmmZTimeStringForClusterMessage() + ((char)7) + ((char)7) + "\r\n";

//                    singleDXClusterMessage += chatController.getChatPreferences().getLoginCallSign() + ":   ";
//                    singleDXClusterMessage += Utils4KST.normalizeFrequencyString(aChatMember.getFrequency().getValue(), chatController.getChatPreferences().getNotify_optionalFrequencyPrefix()) + "  ";
//                    singleDXClusterMessage += aChatMember.getCallSign().toUpperCase() + " ";
//                    singleDXClusterMessage += aChatMember.getQra().toUpperCase() + "  ";
//                    singleDXClusterMessage += new Utils4KST().time_generateCurrenthhmmZTimeStringForClusterMessage() + ((char)7) + ((char)7) + "\r\n";

                    output.write((singleDXClusterMessage).getBytes());

                    ThreadStateMessage threadStateMessage = new ThreadStateMessage(this.ThreadNickName, true, "Last msg to " + clientSockets.size() + " Cluster Clients:\n" + singleDXClusterMessage, false);
                    callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);

                } catch (IOException e) {
                    LOGGER.log(Level.SEVERE, "[DXClusterSrvr] broadcasting DXC-message to clients went wrong", e);
                    return false;
                }
            }
        }
        return true; //if message had been sent, return true for "ok"
    }

}

class DXClusterServerWorkerRunnable implements Runnable{

    private static final Logger LOGGER = Logger.getLogger(DXClusterServerWorkerRunnable.class.getName());
    protected Socket clientSocket = null;
    protected String serverText = null;
    private ChatController client = null;
    private List<Socket> dxClusterClientSocketsConnectedList;
    private ThreadStatusCallback callBackToController;
    private String ThreadNickName = "DXCluster-Server";

    public DXClusterServerWorkerRunnable(Socket clientSocket, String serverText, ChatController chatController, List<Socket> clientSockets, ThreadStatusCallback callback) {
        this.clientSocket = clientSocket;
        this.serverText = serverText;
        this.client = chatController;
        this.dxClusterClientSocketsConnectedList = clientSockets;
        this.callBackToController = callback;

    }

    public void run() {
        try {
            OutputStream output = clientSocket.getOutputStream();
            dxClusterClientSocketsConnectedList.add(clientSocket);

            Timer dXCkeepAliveTimer = new Timer();
            dXCkeepAliveTimer.schedule(new TimerTask() {

                @Override
                public void run() {

                    StringBuilder connectedClients = new StringBuilder(); //only for statistics

                    for (Socket socket : dxClusterClientSocketsConnectedList) {

                        connectedClients.append(socket.getInetAddress()).append("\n");

                        try {
                            OutputStream output = socket.getOutputStream();
                            output.write(("\r\n").getBytes());

                        } catch (IOException e) {
                            LOGGER.log(Level.SEVERE, "[DXClusterSrvr] keep-alive broadcast to client failed", e);
                            dXCkeepAliveTimer.purge();

                            try {
                                socket.close();
                            } catch (IOException ex) {
                                LOGGER.log(Level.SEVERE, "[DXClusterSrvr] error closing client socket", ex);
                            }
                            finally {
                                this.cancel();
                            }
                            dxClusterClientSocketsConnectedList.remove(socket); //if socket is closed by client, remove it from the broadcast list and close it
                        }
                    }

//                    ThreadStateMessage threadStateMessage = new ThreadStateMessage(ThreadNickName, true, "Connected clients: " + connectedClients.toString(), false);
//                    callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);

                }
            }, 30000, 30000);


            output.write(("login: ").getBytes()); //say hello to the client, it will answer with a callsign
            System.out.println("[DXClusterThreadPooledServer, Info:] New cluster client connected! "); //TODO: maybe integrate non blocking reader for client identification

        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, "[DXClusterSrvr] error in worker runnable", e);
        } finally {
            synchronized(dxClusterClientSocketsConnectedList) {
                dxClusterClientSocketsConnectedList.remove(clientSocket); // Entferne den Client nach Verarbeitung
            }
        }
    }



}
