package electrosphere.net.server;

import electrosphere.engine.Globals;
import electrosphere.engine.Main;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.NetUtils;
import electrosphere.net.parser.net.message.NetworkMessage;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;

/**
 * Lowest level networking class for the server
 */
public class Server implements Runnable{
    
    //the port the server is running on
    int port;
    
    //the socket for the server
    ServerSocket serverSocket;
    
    //the map of ip->connection handler
    Map<String,ServerConnectionHandler> clientMap = new HashMap<String,ServerConnectionHandler>();
    
    
    
    
    /**
     * Inits the server
     */
    void initServer(){
        // clientMap = new HashMap<String,ServerConnectionHandler>();
    }
    
    /**
     * Constructor
     * @param port The port to run the server on
     */
    public Server(int port){
        this.port = port;
    }

    @Override
    public void run() {
        initServer();
        try {
            serverSocket = new ServerSocket(port);
            //if we set port to 0, java searches for any available port to open
            //This then explicitly alerts NetUtils of the real port
            if(port == 0){
                NetUtils.setPort(serverSocket.getLocalPort());
            }
        } catch(BindException ex){
            LoggerInterface.loggerNetworking.ERROR("Failed to bind server socket!",ex);
            ex.printStackTrace();
        } catch (IOException ex) {
            LoggerInterface.loggerNetworking.ERROR("Failed to start server socket!",ex);
            ex.printStackTrace();
            LoggerInterface.loggerNetworking.ERROR("", ex);
        }
        while(Main.isRunning()){
            Socket newSocket;
            try {
                newSocket = serverSocket.accept();
                ServerConnectionHandler newClient = new ServerConnectionHandler(newSocket);
                clientMap.put(newSocket.getInetAddress().getHostAddress(), newClient);
                new Thread(newClient).start();
            } catch (SocketException ex){
                LoggerInterface.loggerNetworking.ERROR("Socket closed!",ex);
            } catch (IOException ex) {
                LoggerInterface.loggerNetworking.ERROR("Socket error on client socket!",ex);
            }
        }
    }

    /**
     * Synchronously handles queued packets for each client connection
     */
    public void synchronousPacketHandling(){
        for(ServerConnectionHandler connectionHandler : this.clientMap.values()){
            connectionHandler.handleSynchronousPacketQueue();
        }
    }
    
    /**
     * Closes the server socket
     */
    public void close(){
        try {
            if(serverSocket != null){
                serverSocket.close();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    
    /**
     * Broadcasts a message to all clients
     * @param message The message to broadcast
     */
    public void broadcastMessage(NetworkMessage message){
        for(ServerConnectionHandler client : clientMap.values()){
            if(Globals.clientPlayer == null || client.playerID != Globals.clientPlayer.getId()){
                client.addMessagetoOutgoingQueue(message);
            }
        }
    }

    /**
     * Adds a connection created manually by two streams instead of receiving the streams from the server socket
     * @param serverInputStream The input stream
     * @param serverOutputStream The output stream
     * @return The connection object for the provided streams
     */
    public ServerConnectionHandler addLocalPlayer(InputStream serverInputStream, OutputStream serverOutputStream){
        ServerConnectionHandler newClient = new ServerConnectionHandler(serverInputStream,serverOutputStream);
        clientMap.put("127.0.0.1", newClient);
        new Thread(newClient).start();
        return newClient;
    }
}
