package com.xjeffrose.xio.client;

import com.google.common.base.Preconditions;
import com.xjeffrose.xio.client.loadbalancer.Distributor;
import com.xjeffrose.xio.client.loadbalancer.Protocol;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.handler.codec.http.HttpClientCodec;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import lombok.Setter;

import java.net.InetSocketAddress;
import java.util.function.Supplier;

@Slf4j
@Accessors(fluent = true)
public class XioClientBootstrap {

  private final Bootstrap bootstrap;
  @Setter
  private InetSocketAddress address;
  @Setter
  private Distributor distributor;
  @Setter
  private boolean ssl;
  @Setter
  private Protocol proto;
  @Setter
  private Supplier<ChannelHandler> applicationProtocol;
  @Setter
  private Supplier<ChannelHandler> tracingHandler;
  @Setter
  private ChannelHandler handler;
  @Setter boolean usePool;
  private ChannelConfiguration channelConfig;

  public XioClientBootstrap(ChannelConfiguration channelConfig) {
    this.channelConfig = channelConfig;
    bootstrap = buildBootstrap();
    usePool = false;
    tracingHandler = () -> null;
  }

  public XioClientBootstrap(EventLoopGroup group) {
    this(ChannelConfiguration.clientConfig(group));
  }

  private ChannelInitializer<Channel> buildInitializer() {
    if (proto != null && (proto == Protocol.HTTP || proto == Protocol.HTTPS)) {
      applicationProtocol = () -> new HttpClientCodec();
    } else if (applicationProtocol == null) {
      throw new RuntimeException("Cannot build initializer, specify either protocol or applicationProtocol");
    }
    return new DefaultChannelInitializer(handler, ssl, applicationProtocol, tracingHandler);
  }

  public Bootstrap buildBootstrap() {
    return new Bootstrap()
      .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 500)
      .option(ChannelOption.SO_REUSEADDR, true)
      .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
      .option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 32 * 1024)
      .option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 8 * 1024)
      .option(ChannelOption.TCP_NODELAY, true)
      .group(channelConfig.workerGroup())
      .channel(channelConfig.channel());
  }

  public XioClient build() {
    Preconditions.checkNotNull(handler);
    bootstrap.handler(buildInitializer());
    if (address != null) {
      bootstrap.remoteAddress(address);
      if (usePool) {
        return new SingleNodeClient(address, bootstrap);
      } else {
        return new SingleUnpooledNodeClient(address, bootstrap);
      }
    } else if (distributor != null) {
      return new MultiNodeClient(distributor, bootstrap);
    } else {
      throw new RuntimeException("Cannot build XioClient, specify either address or distributor");
    }
  }

  public Bootstrap getBootstrap() {
    return bootstrap;
  }
}

@@ -767,14 +767,8 @@
 ate 
-final 
 Boot
@@ -784,16 +784,62 @@
 tstrap;%0A
+  private ChannelConfiguration channelConfig;%0A
   @Sette
@@ -1171,16 +1171,18 @@
  @Setter
+%0A 
  boolean
@@ -1191,16 +1191,17 @@
 sePool;%0A
+%0A
   privat
@@ -1206,43 +1206,364 @@
 ate 
-ChannelConfiguration channelConfig;
+XioClientBootstrap(XioClientBootstrap other) %7B%0A    this.address = other.address;%0A    this.distributor = other.distributor;%0A    this.ssl = other.ssl;%0A    this.proto = other.proto;%0A    this.applicationProtocol = other.applicationProtocol;%0A    this.tracingHandler = other.tracingHandler;%0A    this.handler = other.handler;%0A    this.usePool = other.usePool;%0A  %7D
 %0A%0A  
@@ -1622,24 +1622,36 @@
 elConfig) %7B%0A
+    this();%0A
     this.cha
@@ -1660,19 +1660,17 @@
 elConfig
- = 
+(
 channelC
@@ -1678,31 +1678,143 @@
 nfig
+)
 ;%0A  
-  bootstrap = build
+%7D%0A%0A  public XioClientBootstrap(EventLoopGroup group) %7B%0A    this(ChannelConfiguration.clientConfig(group));%0A  %7D%0A%0A  public XioClient
 Boot
@@ -1820,17 +1820,18 @@
 tstrap()
-;
+ %7B
 %0A    use
@@ -1913,83 +1913,163 @@
 trap
-(EventLoopGroup group) %7B%0A    this(ChannelConfiguration.clientConfig(group))
+ channelConfig(ChannelConfiguration channelConfig) %7B%0A    this.channelConfig = channelConfig;%0A    bootstrap = buildBootstrap(channelConfig);%0A    return this
 ;%0A  
@@ -2553,32 +2553,66 @@
  buildBootstrap(
+ChannelConfiguration channelConfig
 ) %7B%0A    return n
@@ -3098,24 +3098,71 @@
 t build() %7B%0A
+    Preconditions.checkNotNull(channelConfig);%0A
     Precondi
@@ -3747,10 +3747,313 @@
 ap;%0A  %7D%0A
+%0A  public XioClientBootstrap clone(ChannelConfiguration channelConfig) %7B%0A    XioClientBootstrap bs = new XioClientBootstrap(this);%0A    return bs.channelConfig(channelConfig);%0A  %7D%0A%0A  public XioClientBootstrap clone(EventLoopGroup group) %7B%0A    return clone(ChannelConfiguration.clientConfig(group));%0A  %7D%0A%0A
 %7D%0A

Make XioClientBootstrap cloneable