package com.digitalpetri.enip;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;

import com.digitalpetri.enip.commands.RegisterSession;
import com.digitalpetri.enip.commands.UnRegisterSession;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;

public class ChannelManager {

    private final AtomicReference<State> state = new AtomicReference<>(new Idle());

    private final EtherNetIpClient client;

    public ChannelManager(EtherNetIpClient client) {
        this.client = client;
    }

    public CompletableFuture<Channel> getChannel() {
        State currentState = state.get();

        if (currentState instanceof Idle) {
            Connecting nextState = new Connecting();

            if (state.compareAndSet(currentState, nextState)) {
                CompletableFuture<Channel> future = nextState.future;

                future.whenComplete((ch, ex) -> {
                    if (ch != null) state.set(new Connected(future));
                    else state.set(new Idle());
                });

                return connect(future);
            } else {
                return getChannel();
            }
        } else if (currentState instanceof Connecting) {
            return ((Connecting) currentState).future;
        } else if (currentState instanceof Connected) {
            return ((Connected) currentState).future;
        } else {
            throw new IllegalStateException(currentState.getClass().getSimpleName());
        }
    }

    private CompletableFuture<Channel> connect(CompletableFuture<Channel> future) {
        CompletableFuture<Channel> bootstrap = EtherNetIpClient.bootstrap(client);

        bootstrap.whenComplete((ch, ex) -> {
            if (ch != null) {
                ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                    @Override
                    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                        state.set(new Idle());

                        super.channelInactive(ctx);
                    }
                });

                CompletableFuture<RegisterSession> registerFuture = new CompletableFuture<>();

                registerFuture.whenComplete((r, ex2) -> {
                    if (r != null) future.complete(ch);
                    else future.completeExceptionally(ex2);
                });

                client.writeCommand(ch, new RegisterSession(), registerFuture);
            } else {
                future.completeExceptionally(ex);
            }
        });

        return future;
    }

    public void disconnect() {
        State currentState = state.get();

        if (currentState instanceof Connecting) {
            ((Connecting) currentState).future.whenComplete((ch, ex) -> {
                if (ch != null) ch.close();
            });
        } else if (currentState instanceof Connected) {
            ((Connected) currentState).future.whenComplete((ch, ex) -> {
                CompletableFuture<UnRegisterSession> f = new CompletableFuture<>();
                client.writeCommand(ch, new UnRegisterSession(), f);

                f.whenComplete((cmd, ex2) -> ch.close());
            });
        }
    }

    public String getState() {
        return state.get().getClass().getSimpleName();
    }

    private static abstract class State {
    }

    private static class Idle extends State {
    }

    private static class Connecting extends State {
        private final CompletableFuture<Channel> future = new CompletableFuture<>();
    }

    private static class Connected extends State {
        private final CompletableFuture<Channel> future;

        private Connected(CompletableFuture<Channel> future) {
            this.future = future;
        }
    }

}

@@ -382,84 +382,160 @@
 ort 
-io.netty.channel.SimpleChannelInboundHandler;%0A%0Apublic class ChannelManager %7B
+org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;%0A%0Apublic class ChannelManager %7B%0A%0A    private final Logger logger = LoggerFactory.getLogger(getClass());
 %0A%0A  
@@ -1962,24 +1962,184 @@
  != null) %7B%0A
+                logger.debug(%22Channel bootstrap succeeded: localAddress=%7B%7D, remoteAddress=%7B%7D%22,%0A                        ch.localAddress(), ch.remoteAddress());%0A%0A
             
@@ -2852,32 +2852,116 @@
        %7D else %7B%0A
+                logger.debug(%22Channel bootstrap failed: %7B%7D%22, ex.getMessage(), ex);%0A%0A
                 

Add logging on connection success and failure