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

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

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;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.TimeoutException;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;
import io.netty.util.concurrent.GlobalEventExecutor;

/**
 * Makes an attempt to bootstrap the cluster based on seeds stored in the
 * local system store
 * @author readams
 */
public class BootstrapClient {
    protected static final Logger logger =
            LoggerFactory.getLogger(BootstrapClient.class);
    
    /**
     * Channel group that will hold all our channels
     */
    private 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;
    
    EventLoopGroup workerExecutor = null;
    Bootstrap bootstrap = null;
    BootstrapChannelInitializer pipelineFactory;
    
    protected Node localNode;
    protected volatile boolean succeeded = false;
    
    private Timer timer;

    public BootstrapClient(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", GlobalEventExecutor.INSTANCE);

        workerExecutor = new NioEventLoopGroup();
        timer = new HashedWheelTimer();
        
        bootstrap = new Bootstrap()
        .group(workerExecutor)
        .channel(NioSocketChannel.class)
        .option(ChannelOption.SO_REUSEADDR, true)
        .option(ChannelOption.SO_KEEPALIVE, true)
        .option(ChannelOption.TCP_NODELAY, true)
        .option(ChannelOption.SO_SNDBUF, RPCService.SEND_BUFFER_SIZE)
        .option(ChannelOption.SO_RCVBUF, RPCService.SEND_BUFFER_SIZE)
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, RPCService.CONNECT_TIMEOUT);
        
        pipelineFactory = new BootstrapChannelInitializer(timer, this);
        bootstrap.handler(pipelineFactory);
    }
    
    public void shutdown() {
        if (cg != null) {
            cg.close().awaitUninterruptibly();
            cg = null;
        }
        bootstrap = null;
        pipelineFactory = null;
        if (workerExecutor != null) {
            try {
				workerExecutor.shutdownGracefully();
			} catch (TimeoutException e) {
				logger.warn("Error waiting for gracefull shutdown of BootstrapClient {}", e);
			}
            workerExecutor = null;
        }
        if (timer != null) {
        	timer.stop();
        	timer = null;
        }
    }
    
    public boolean bootstrap(HostAndPort seed, 
                             Node localNode) throws SyncException {
        this.localNode = localNode;
        succeeded = false;
        SocketAddress sa =
                new InetSocketAddress(seed.getHost(), seed.getPort());
        ChannelFuture future = bootstrap.connect(sa);
        future.awaitUninterruptibly();
        if (!future.isSuccess()) {
            logger.debug("Could not connect to " + seed, future.cause());
            return false;
        }
        Channel channel = future.channel();
        logger.debug("[{}] Connected to {}", 
                     localNode != null ? localNode.getNodeId() : null,
                     seed);
        
        try {
            channel.closeFuture().await();
        } catch (InterruptedException e) {
            logger.debug("Interrupted while waiting for bootstrap");
            return succeeded;
        }
        return succeeded;
    }
    
    public ChannelGroup getChannelGroup() {
    	return cg;
    }
}
