package styx.http.server;

import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.logging.Logger;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import styx.http.HttpException;
import styx.http.SessionVariable;

public class Server implements AutoCloseable {

    private static final Logger logger = Logger.getLogger(Server.class.getName());

    private final EventLoopGroup bossGroup;
    private final EventLoopGroup workerGroup;
    private Channel channel;

    private String domain = "localhost";
    private boolean secure = false;
    private int port = 80;
    private SessionHandler sessionHandler;
    private Function<Request, CompletableFuture<Response>> handler;

    public Server() {
        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup();
        logger.info("Started server NIO groups.");
    }

    public Server secure(boolean secure) {
        this.secure = secure;
        return this;
    }

    public Server secure(boolean secure, String domain) {
        this.secure = secure;
        this.domain = domain;
        return this;
    }

    public Server port(int port) {
        this.port = port;
        return this;
    }

    public Server enableSessions() {
        sessionHandler = new SessionHandler();
        return this;
    }

    public Server routes(Route... routes) {
        this.handler = Route.createHandler(routes, this);
        return this;
    }

    public void start() {
        Optional<SslContext> sslContext = createSslContext(domain);
        ServerBootstrap bootstrap = new ServerBootstrap().
                group(bossGroup, workerGroup).
                channel(NioServerSocketChannel.class).
                handler(new LoggingHandler(LogLevel.INFO)).
                childHandler(new ServerChannelInitializer(sslContext, handler));

        channel = bootstrap.bind(port).syncUninterruptibly().channel();
        logger.info("Started " + protocol() + " server on port " + port);
    }

    public void run() {
        start();
        channel.closeFuture().syncUninterruptibly();
    }

    @Override
    public void close() {
        if(channel != null) {
            logger.info("Stopping " + protocol() + " server on port " + port);
            channel.close().syncUninterruptibly();
        }
        logger.info("Stopping server NIO groups."); // TODO: shutdown semantics and performance?
        bossGroup.shutdownGracefully()/*.syncUninterruptibly()*/;
        workerGroup.shutdownGracefully()/*.syncUninterruptibly()*/;
    }

    public static Route.Builder route() {
        return new Route.Builder();
    }

    Optional<SessionHandler> getSessionHandler() {
        return Optional.ofNullable(sessionHandler);
    }

    public void login(Response response, Duration expiry, SessionVariable... sessionVariables) {
        response.cookie(SessionHandler.COOKIE_NAME, sessionHandler.encodeAndSignCookie(
                Arrays.asList(sessionVariables), Instant.now().plus(expiry)));
    }

    private String protocol() {
        return secure ? "HTTPS" : "HTTP";
    }

    private Optional<SslContext> createSslContext(String domain) {
        try {
            if(secure) {
                logger.warning("Using self-signed SSL certificate for " + domain);
                SelfSignedCertificate certificate = new SelfSignedCertificate(domain);
                return Optional.of(SslContextBuilder.forServer(
                        certificate.certificate(), certificate.privateKey()).build());
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new HttpException("Failed to create SSL context.", e);
        }
    }
}

@@ -172,16 +172,60 @@
 Future;%0A
+import java.util.concurrent.CountDownLatch;%0A
 import j
@@ -2569,32 +2569,48 @@
 + port);%0A    %7D%0A%0A
+    @Deprecated%0A
     public void 
@@ -2690,24 +2690,141 @@
 y();%0A    %7D%0A%0A
+    public void run(CountDownLatch stop) throws InterruptedException %7B%0A        start();%0A        stop.await();%0A    %7D%0A%0A
     @Overrid

Support for CountDownLatch for signalling shutdown