// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.bootstrap;

import io.netty.resolver.DefaultAddressResolverGroup;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.channel.EventLoopGroup;
import java.util.Iterator;
import java.util.Collection;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelHandler;
import io.netty.resolver.AddressResolver;
import io.netty.channel.EventLoop;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.Future;
import io.netty.channel.ChannelPromise;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.internal.ObjectUtil;
import io.netty.channel.ChannelFuture;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import io.netty.resolver.AddressResolverGroup;
import java.net.SocketAddress;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.channel.Channel;

public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel>
{
    private static final InternalLogger logger;
    private final BootstrapConfig config;
    private ExternalAddressResolver externalResolver;
    private volatile boolean disableResolver;
    private volatile SocketAddress remoteAddress;
    
    public Bootstrap() {
        this.config = new BootstrapConfig(this);
    }
    
    private Bootstrap(final Bootstrap bootstrap) {
        super(bootstrap);
        this.config = new BootstrapConfig(this);
        this.externalResolver = bootstrap.externalResolver;
        this.disableResolver = bootstrap.disableResolver;
        this.remoteAddress = bootstrap.remoteAddress;
    }
    
    public Bootstrap resolver(final AddressResolverGroup<?> resolver) {
        this.externalResolver = ((resolver == null) ? null : new ExternalAddressResolver(resolver));
        this.disableResolver = false;
        return this;
    }
    
    public Bootstrap disableResolver() {
        this.externalResolver = null;
        this.disableResolver = true;
        return this;
    }
    
    public Bootstrap remoteAddress(final SocketAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
        return this;
    }
    
    public Bootstrap remoteAddress(final String inetHost, final int inetPort) {
        this.remoteAddress = InetSocketAddress.createUnresolved(inetHost, inetPort);
        return this;
    }
    
    public Bootstrap remoteAddress(final InetAddress inetHost, final int inetPort) {
        this.remoteAddress = new InetSocketAddress(inetHost, inetPort);
        return this;
    }
    
    public ChannelFuture connect() {
        this.validate();
        final SocketAddress remoteAddress = this.remoteAddress;
        if (remoteAddress == null) {
            throw new IllegalStateException("remoteAddress not set");
        }
        return this.doResolveAndConnect(remoteAddress, this.config.localAddress());
    }
    
    public ChannelFuture connect(final String inetHost, final int inetPort) {
        return this.connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
    }
    
    public ChannelFuture connect(final InetAddress inetHost, final int inetPort) {
        return this.connect(new InetSocketAddress(inetHost, inetPort));
    }
    
    public ChannelFuture connect(final SocketAddress remoteAddress) {
        ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
        this.validate();
        return this.doResolveAndConnect(remoteAddress, this.config.localAddress());
    }
    
    public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
        this.validate();
        return this.doResolveAndConnect(remoteAddress, localAddress);
    }
    
    private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        final ChannelFuture regFuture = this.initAndRegister();
        final Channel channel = regFuture.channel();
        if (!regFuture.isDone()) {
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener((GenericFutureListener<? extends Future<? super Void>>)new ChannelFutureListener() {
                @Override
                public void operationComplete(final ChannelFuture future) throws Exception {
                    final Throwable cause = future.cause();
                    if (cause != null) {
                        promise.setFailure(cause);
                    }
                    else {
                        promise.registered();
                        Bootstrap.this.doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                    }
                }
            });
            return promise;
        }
        if (!regFuture.isSuccess()) {
            return regFuture;
        }
        return this.doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
    }
    
    private ChannelFuture doResolveAndConnect0(final Channel channel, final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
        try {
            if (this.disableResolver) {
                doConnect(remoteAddress, localAddress, promise);
                return promise;
            }
            final EventLoop eventLoop = channel.eventLoop();
            AddressResolver<SocketAddress> resolver;
            try {
                resolver = ExternalAddressResolver.getOrDefault(this.externalResolver).getResolver(eventLoop);
            }
            catch (final Throwable cause) {
                channel.close();
                return promise.setFailure(cause);
            }
            if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
                doConnect(remoteAddress, localAddress, promise);
                return promise;
            }
            final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
            if (resolveFuture.isDone()) {
                final Throwable resolveFailureCause = resolveFuture.cause();
                if (resolveFailureCause != null) {
                    channel.close();
                    promise.setFailure(resolveFailureCause);
                }
                else {
                    doConnect(resolveFuture.getNow(), localAddress, promise);
                }
                return promise;
            }
            resolveFuture.addListener(new FutureListener<SocketAddress>() {
                @Override
                public void operationComplete(final Future<SocketAddress> future) throws Exception {
                    if (future.cause() != null) {
                        channel.close();
                        promise.setFailure(future.cause());
                    }
                    else {
                        doConnect(future.getNow(), localAddress, promise);
                    }
                }
            });
        }
        catch (final Throwable cause2) {
            promise.tryFailure(cause2);
        }
        return promise;
    }
    
    private static void doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
        final Channel channel = connectPromise.channel();
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (localAddress == null) {
                    channel.connect(remoteAddress, connectPromise);
                }
                else {
                    channel.connect(remoteAddress, localAddress, connectPromise);
                }
                connectPromise.addListener((GenericFutureListener<? extends Future<? super Void>>)ChannelFutureListener.CLOSE_ON_FAILURE);
            }
        });
    }
    
    @Override
    void init(final Channel channel) throws Throwable {
        final ChannelPipeline p = channel.pipeline();
        p.addLast(this.config.handler());
        AbstractBootstrap.setChannelOptions(channel, this.newOptionsArray(), Bootstrap.logger);
        AbstractBootstrap.setAttributes(channel, this.newAttributesArray());
        final Collection<ChannelInitializerExtension> extensions = this.getInitializerExtensions();
        if (!extensions.isEmpty()) {
            for (final ChannelInitializerExtension extension : extensions) {
                try {
                    extension.postInitializeClientChannel(channel);
                }
                catch (final Exception e) {
                    Bootstrap.logger.warn("Exception thrown from postInitializeClientChannel", e);
                }
            }
        }
    }
    
    @Override
    public Bootstrap validate() {
        super.validate();
        if (this.config.handler() == null) {
            throw new IllegalStateException("handler not set");
        }
        return this;
    }
    
    @Override
    public Bootstrap clone() {
        return new Bootstrap(this);
    }
    
    public Bootstrap clone(final EventLoopGroup group) {
        final Bootstrap bs = new Bootstrap(this);
        bs.group = group;
        return bs;
    }
    
    @Override
    public final BootstrapConfig config() {
        return this.config;
    }
    
    final SocketAddress remoteAddress() {
        return this.remoteAddress;
    }
    
    final AddressResolverGroup<?> resolver() {
        if (this.disableResolver) {
            return null;
        }
        return ExternalAddressResolver.getOrDefault(this.externalResolver);
    }
    
    static {
        logger = InternalLoggerFactory.getInstance(Bootstrap.class);
    }
    
    static final class ExternalAddressResolver
    {
        final AddressResolverGroup<SocketAddress> resolverGroup;
        
        ExternalAddressResolver(final AddressResolverGroup<?> resolverGroup) {
            this.resolverGroup = (AddressResolverGroup<SocketAddress>)resolverGroup;
        }
        
        static AddressResolverGroup<SocketAddress> getOrDefault(final ExternalAddressResolver externalResolver) {
            if (externalResolver == null) {
                final AddressResolverGroup<?> defaultResolverGroup = DefaultAddressResolverGroup.INSTANCE;
                return (AddressResolverGroup<SocketAddress>)defaultResolverGroup;
            }
            return externalResolver.resolverGroup;
        }
    }
}
