package com.jenjinstudios.io.server;

import com.jenjinstudios.io.ExecutionContext;
import com.jenjinstudios.io.connection.Connection;
import com.jenjinstudios.io.connection.ReusableConnectionBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
 * Used to accept and manage incoming connections.
 *
 * @author Caleb Brinkman
 */
public class Server
{
    private static final Logger LOGGER = LoggerFactory.getLogger(Server.class);
    private static final int EXECUTOR_THREADS = 4;
    private final ScheduledExecutorService executor;
    private final ServerSocket serverSocket;
    private final ReusableConnectionBuilder connectionBuilder;
    private final Collection<BiConsumer<Server, ExecutionContext>> contextualTasks;
    private final Collection<Consumer<Connection>> connectionAddedCallbacks;
    private final Collection<Consumer<Connection>> connectionRemovedCallbacks;
    private final Collection<Consumer<Server>> startupCallbacks;
    private final Collection<Consumer<Server>> shutdownCallbacks;
    private final Collection<Connection> connections;

    Server(
          ServerSocket serverSocket,
          ReusableConnectionBuilder connectionBuilder,
          Collection<BiConsumer<Server, ExecutionContext>> contextualTasks,
          Collection<Consumer<Connection>> addedCallbacks,
          Collection<Consumer<Connection>> removedCallbacks,
          Collection<Consumer<Server>> startupCallbacks,
          Collection<Consumer<Server>> shutdownCallbacks)
    {
        this.serverSocket = serverSocket;
        this.connectionBuilder = connectionBuilder;
        this.contextualTasks = new LinkedList<>(contextualTasks);
        this.connectionAddedCallbacks = new LinkedList<>(addedCallbacks);
        this.connectionRemovedCallbacks = new LinkedList<>(removedCallbacks);
        this.startupCallbacks = new LinkedList<>(startupCallbacks);
        this.shutdownCallbacks = new LinkedList<>(shutdownCallbacks);
        executor = Executors.newScheduledThreadPool(EXECUTOR_THREADS);
        connections = Collections.synchronizedCollection(new LinkedList<>());

        this.connectionBuilder.withShutdownCallback(connection -> {
            connections.remove(connection);
            connectionRemovedCallbacks.forEach(consumer -> consumer.accept(connection));
        });
    }

    /**
     * Stop listening for inbound connections and attempt to gracefully close all existing connections.
     */
    public void stop() {
        Collection<Connection> temp = new LinkedList<>();
        synchronized (connections) {
            temp.addAll(connections);
            temp.forEach(Connection::stop);
        }

        try {
            serverSocket.close();
        } catch (IOException e) {
            LOGGER.warn("Error when closing ServerSocket", e);
        }

        shutdownCallbacks.forEach(consumer -> consumer.accept(this));
    }

    /**
     * Start listening for inbound connections.
     */
    public void start() {
        connectionBuilder.
              withContextualTask(context -> this.contextualTasks.forEach(consumer -> consumer.accept(this, context)));
        executor.scheduleWithFixedDelay(() -> {
            try {
                if (serverSocket.isClosed()) {
                    LOGGER.debug("Server Socket Closed");
                } else {
                    Socket socket = serverSocket.accept();
                    if (socket != null) {
                        Connection connection = connectionBuilder.build(socket);
                        connections.add(connection);
                        connectionAddedCallbacks.forEach(consumer -> consumer.accept(connection));
                        connection.start();
                    } else {
                        LOGGER.warn("ServerSocket returned null connection");
                    }
                }
            } catch (IOException e) {
                LOGGER.error("Error when attempting to accept incoming connection", e);
            }
        }, 0, 10, TimeUnit.MILLISECONDS);

        startupCallbacks.forEach(consumer -> consumer.accept(this));
    }

    public int getConnectionCount() { return connections.size(); }
}

@@ -1570,26 +1570,24 @@
         
-Collection
+Iterable
 %3CBiConsu
@@ -1640,34 +1640,32 @@
 ,%0A          
-Collection
+Iterable
 %3CConsumer%3CCo
@@ -1697,34 +1697,32 @@
 ,%0A          
-Collection
+Iterable
 %3CConsumer%3CCo
@@ -1756,34 +1756,32 @@
 ,%0A          
-Collection
+Iterable
 %3CConsumer%3CSe
@@ -1811,34 +1811,32 @@
 ,%0A          
-Collection
+Iterable
 %3CConsumer%3CSe
@@ -2009,31 +2009,16 @@
 dList%3C%3E(
-contextualTasks
 );%0A     
@@ -2069,30 +2069,16 @@
 dList%3C%3E(
-addedCallbacks
 );%0A     
@@ -2131,32 +2131,16 @@
 dList%3C%3E(
-removedCallbacks
 );%0A     
@@ -2183,32 +2183,16 @@
 dList%3C%3E(
-startupCallbacks
 );%0A     
@@ -2240,28 +2240,12 @@
 t%3C%3E(
-shutdownCallbacks
 );%0A
+%0A
     
@@ -2386,24 +2386,351 @@
 List%3C%3E());%0A%0A
+        contextualTasks.forEach(this.contextualTasks::add);%0A        addedCallbacks.forEach(this.connectionAddedCallbacks::add);%0A        removedCallbacks.forEach(this.connectionRemovedCallbacks::add);%0A        startupCallbacks.forEach(this.startupCallbacks::add);%0A        shutdownCallbacks.forEach(this.shutdownCallbacks::add);%0A%0A
         this

Refactor Server constructor slightly