package net.openhft.chronicle.network.ssl;

import net.openhft.chronicle.core.util.ThrowingFunction;
import net.openhft.chronicle.network.*;
import net.openhft.chronicle.network.api.TcpHandler;
import net.openhft.chronicle.network.cluster.ClusterContext;
import net.openhft.chronicle.network.cluster.ConnectionManager;
import net.openhft.chronicle.network.cluster.handlers.HeartbeatHandler;
import net.openhft.chronicle.network.cluster.handlers.UberHandler;
import net.openhft.chronicle.network.connection.VanillaWireOutPublisher;
import net.openhft.chronicle.threads.EventGroup;
import net.openhft.chronicle.threads.Pauser;
import net.openhft.chronicle.wire.WireType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;

import static net.openhft.chronicle.network.NetworkStatsListener.notifyHostPort;

public final class SslTestClusterContext extends ClusterContext<SslTestClusteredNetworkContext> {

    private transient SslTestCluster cluster;

    private static <T extends SslNetworkContext<T>> TcpHandler<T> wrapForSsl(final TcpHandler<T> delegate) {
        new RuntimeException(String.format("%s/0x%s created",
                delegate.getClass().getSimpleName(), Integer.toHexString(System.identityHashCode(delegate)))).
                printStackTrace(System.out);

        return new SslDelegatingTcpHandler<>(delegate);
    }

    @Override
    public void defaults() {
        this.handlerFactory(new UberHandler.Factory<>())
                .heartbeatFactory(new HeartbeatHandler.Factory<>())
                .wireOutPublisherFactory(VanillaWireOutPublisher::new)
                .wireType(WireType.TEXT)
                .connectionEventHandler(StubConnectionManager::new)
                .serverThreadingStrategy(ServerThreadingStrategy.CONCURRENT)
                .networkContextFactory(c -> ncFactory())
                .networkStatsListenerFactory(ctx -> new LoggingNetworkStatsListener<>())
                .eventLoop(new EventGroup(false, Pauser.balanced(), false, "ssl-cluster-"));
    }

    @NotNull
    private SslTestClusteredNetworkContext ncFactory() {
        return new SslTestClusteredNetworkContext(localIdentifier(), this.cluster, eventLoop());
    }

    @NotNull
    @Override
    public ThrowingFunction<SslTestClusteredNetworkContext, TcpEventHandler<SslTestClusteredNetworkContext>, IOException> tcpEventHandlerFactory() {
        return new BootstrapHandlerFactory<SslTestClusteredNetworkContext>()::createHandler;
    }

    void cluster(final SslTestCluster cluster) {

        this.cluster = cluster;
    }

    public static final class BootstrapHandlerFactory<T extends NetworkContext<T>> {
        @NotNull
        TcpEventHandler<T> createHandler(final T networkContext) {
            @NotNull final T nc = networkContext;
            if (nc.isAcceptor())
                nc.wireOutPublisher(new VanillaWireOutPublisher(WireType.TEXT));
            @NotNull final TcpEventHandler<T> handler = new TcpEventHandler<>(networkContext);

            @NotNull final Function<Object, TcpHandler<T>> consumer = o -> {

                if (o instanceof TcpHandler) {
                    return wrapForSsl((TcpHandler) o);
                }

                throw new UnsupportedOperationException("not supported class=" + o.getClass());
            };

            final NetworkStatsListener<T> nl = nc.networkStatsListener();
            notifyHostPort(nc.socketChannel(), nl);

            @Nullable final Function<T, TcpHandler<T>> f
                    = x -> new HeaderTcpHandler<>(handler, consumer);

            @NotNull final WireTypeSniffingTcpHandler<T> sniffer = new
                    WireTypeSniffingTcpHandler<>(handler, f);

            handler.tcpHandler(sniffer);
            return handler;
        }
    }

    private static class StubConnectionManager<T extends NetworkContext<T>> implements ConnectionManager<T> {
        private final List<ConnectionListener<T>> listeners = new CopyOnWriteArrayList<>();

        @Override
        public void onConnectionChanged(final boolean isConnected, final T nc) {
            for (ConnectionListener<T> listener : listeners) {
                listener.onConnectionChange(nc, isConnected);
            }
        }

        @Override
        public void addListener(final ConnectionListener<T> connectionListener) {
            listeners.add(connectionListener);
        }
    }

    private static class LoggingNetworkStatsListener<T extends NetworkContext<T>> implements NetworkStatsListener<T> {
        @Override
        public void networkContext(final T networkContext) {

        }

        @Override
        public void onNetworkStats(final long writeBps, final long readBps, final long socketPollCountPerSecond) {

        }

        @Override
        public void onHostPort(final String hostName, final int port) {
        }

        @Override
        public void onRoundTripLatency(final long nanosecondLatency) {

        }

        @Override
        public void procPrefix(final String procPrefix) {

        }

        @Override
        public void close() {

        }

        @Override
        public boolean isClosed() {
            return false;
        }
    }
}