package org.sdnplatform.sync.internal.config.bootstrap;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.sdnplatform.sync.error.SyncException;
import org.sdnplatform.sync.internal.SyncManager;
import org.sdnplatform.sync.internal.config.AuthScheme;
import org.sdnplatform.sync.internal.config.Node;
import org.sdnplatform.sync.internal.rpc.RPCService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.net.HostAndPort;

/**
 * Makes an attempt to bootstrap the cluster based on seeds stored in the
 * local system store
 * @author readams
 */
public class Bootstrap {
    protected static final Logger logger =
            LoggerFactory.getLogger(Bootstrap.class);
    
    /**
     * Channel group that will hold all our channels
     */
    protected ChannelGroup cg;

    /**
     * Transaction ID used in message headers in the RPC protocol
     */
    protected AtomicInteger transactionId = new AtomicInteger();
    
    /**
     * The {@link SyncManager} that we'll be bootstrapping
     */
    protected SyncManager syncManager;
    protected final AuthScheme authScheme;
    protected final String keyStorePath;
    protected final String keyStorePassword;
    
    ExecutorService bossExecutor = null;
    ExecutorService workerExecutor = null;
    ClientBootstrap bootstrap = null;
    BootstrapPipelineFactory pipelineFactory;
    
    protected Node localNode;
    protected volatile boolean succeeded = false;

    public Bootstrap(SyncManager syncManager, AuthScheme authScheme,
                     String keyStorePath, String keyStorePassword) {
        super();
        this.syncManager = syncManager;
        this.authScheme = authScheme;
        this.keyStorePath = keyStorePath;
        this.keyStorePassword = keyStorePassword;
    }

    public void init() throws SyncException {
        cg = new DefaultChannelGroup("Cluster Bootstrap");

        bossExecutor = Executors.newCachedThreadPool();
        workerExecutor = Executors.newCachedThreadPool();

        bootstrap =
                new ClientBootstrap(new NioClientSocketChannelFactory(bossExecutor,
                                                                      workerExecutor));
        bootstrap.setOption("child.reuseAddr", true);
        bootstrap.setOption("child.keepAlive", true);
        bootstrap.setOption("child.tcpNoDelay", true);
        bootstrap.setOption("child.sendBufferSize", 
                            RPCService.SEND_BUFFER_SIZE);
        bootstrap.setOption("child.receiveBufferSize", 
                            RPCService.SEND_BUFFER_SIZE);
        bootstrap.setOption("child.connectTimeoutMillis", 
                            RPCService.CONNECT_TIMEOUT);
        pipelineFactory = new BootstrapPipelineFactory(this);
        bootstrap.setPipelineFactory(pipelineFactory);
    }
    
    public void shutdown() {
        if (cg != null) {
            cg.close().awaitUninterruptibly();
            cg = null;
        }
        if (bootstrap != null)
            bootstrap.releaseExternalResources();
        bootstrap = null;
        if (pipelineFactory != null)
            pipelineFactory.releaseExternalResources();
        pipelineFactory = null;
        if (workerExecutor != null)
            workerExecutor.shutdown();
        workerExecutor = null;
        if (bossExecutor != null)
            bossExecutor.shutdown();
        bossExecutor = null;
    }
    
    public boolean bootstrap(HostAndPort seed, 
                             Node localNode) throws SyncException {
        this.localNode = localNode;
        succeeded = false;
        SocketAddress sa =
                new InetSocketAddress(seed.getHostText(), seed.getPort());
        ChannelFuture future = bootstrap.connect(sa);
        future.awaitUninterruptibly();
        if (!future.isSuccess()) {
            logger.debug("Could not connect to " + seed, future.getCause());
            return false;
        }
        Channel channel = future.getChannel();
        logger.debug("[{}] Connected to {}", 
                     localNode != null ? localNode.getNodeId() : null,
                     seed);
        
        try {
            channel.getCloseFuture().await();
        } catch (InterruptedException e) {
            logger.debug("Interrupted while waiting for bootstrap");
            return succeeded;
        }
        return succeeded;
    }
}