package org.chenyang.http.impl.conn;

import org.chenyang.commons.logging.Log;
import org.chenyang.commons.logging.LogFactory;
import org.chenyang.http.HttpClientConnection;
import org.chenyang.http.HttpHost;
import org.chenyang.http.annotation.Contract;
import org.chenyang.http.annotation.ThreadingBehavior;
import org.chenyang.http.config.ConnectionConfig;
import org.chenyang.http.config.Lookup;
import org.chenyang.http.config.Registry;
import org.chenyang.http.config.RegistryBuilder;
import org.chenyang.http.config.SocketConfig;
import org.chenyang.http.conn.ConnectionRequest;
import org.chenyang.http.conn.DnsResolver;
import org.chenyang.http.conn.HttpClientConnectionManager;
import org.chenyang.http.conn.HttpClientConnectionOperator;
import org.chenyang.http.conn.HttpConnectionFactory;
import org.chenyang.http.conn.ManagedHttpClientConnection;
import org.chenyang.http.conn.SchemePortResolver;
import org.chenyang.http.conn.routing.HttpRoute;
import org.chenyang.http.conn.socket.ConnectionSocketFactory;
import org.chenyang.http.conn.socket.PlainConnectionSocketFactory;
import org.chenyang.http.conn.ssl.SSLConnectionSocketFactory;
import org.chenyang.http.protocol.HttpContext;
import org.chenyang.http.util.Args;
import org.chenyang.http.util.Asserts;
import org.chenyang.http.util.LangUtils;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@Contract(
   threading = ThreadingBehavior.SAFE_CONDITIONAL
)
public class BasicHttpClientConnectionManager implements HttpClientConnectionManager, Closeable {
   private final Log log;
   private final HttpClientConnectionOperator connectionOperator;
   private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory;
   private ManagedHttpClientConnection conn;
   private HttpRoute route;
   private Object state;
   private long updated;
   private long expiry;
   private boolean leased;
   private SocketConfig socketConfig;
   private ConnectionConfig connConfig;
   private final AtomicBoolean isShutdown;

   private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
      return RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build();
   }

   public BasicHttpClientConnectionManager(Lookup<ConnectionSocketFactory> socketFactoryRegistry, HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, SchemePortResolver schemePortResolver, DnsResolver dnsResolver) {
      this(new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver), connFactory);
   }

   public BasicHttpClientConnectionManager(HttpClientConnectionOperator httpClientConnectionOperator, HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
      this.log = LogFactory.getLog(this.getClass());
      this.connectionOperator = (HttpClientConnectionOperator)Args.notNull(httpClientConnectionOperator, "Connection operator");
      this.connFactory = (HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection>)(connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE);
      this.expiry = Long.MAX_VALUE;
      this.socketConfig = SocketConfig.DEFAULT;
      this.connConfig = ConnectionConfig.DEFAULT;
      this.isShutdown = new AtomicBoolean(false);
   }

   public BasicHttpClientConnectionManager(Lookup<ConnectionSocketFactory> socketFactoryRegistry, HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
      this(socketFactoryRegistry, connFactory, (SchemePortResolver)null, (DnsResolver)null);
   }

   public BasicHttpClientConnectionManager(Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
      this(socketFactoryRegistry, (HttpConnectionFactory)null, (SchemePortResolver)null, (DnsResolver)null);
   }

   public BasicHttpClientConnectionManager() {
      this(getDefaultRegistry(), (HttpConnectionFactory)null, (SchemePortResolver)null, (DnsResolver)null);
   }

   protected void finalize() throws Throwable {
      try {
         this.shutdown();
      } finally {
         super.finalize();
      }

   }

   public void close() {
      if (this.isShutdown.compareAndSet(false, true)) {
         this.closeConnection();
      }

   }

   HttpRoute getRoute() {
      return this.route;
   }

   Object getState() {
      return this.state;
   }

   public synchronized SocketConfig getSocketConfig() {
      return this.socketConfig;
   }

   public synchronized void setSocketConfig(SocketConfig socketConfig) {
      this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
   }

   public synchronized ConnectionConfig getConnectionConfig() {
      return this.connConfig;
   }

   public synchronized void setConnectionConfig(ConnectionConfig connConfig) {
      this.connConfig = connConfig != null ? connConfig : ConnectionConfig.DEFAULT;
   }

   public final ConnectionRequest requestConnection(final HttpRoute route, final Object state) {
      Args.notNull(route, "Route");
      return new ConnectionRequest() {
         public boolean cancel() {
            return false;
         }

         public HttpClientConnection get(long timeout, TimeUnit timeUnit) {
            return BasicHttpClientConnectionManager.this.getConnection(route, state);
         }
      };
   }

   private synchronized void closeConnection() {
      if (this.conn != null) {
         this.log.debug("Closing connection");

         try {
            this.conn.close();
         } catch (IOException iox) {
            if (this.log.isDebugEnabled()) {
               this.log.debug("I/O exception closing connection", iox);
            }
         }

         this.conn = null;
      }

   }

   private void checkExpiry() {
      if (this.conn != null && System.currentTimeMillis() >= this.expiry) {
         if (this.log.isDebugEnabled()) {
            this.log.debug("Connection expired @ " + new Date(this.expiry));
         }

         this.closeConnection();
      }

   }

   synchronized HttpClientConnection getConnection(HttpRoute route, Object state) {
      Asserts.check(!this.isShutdown.get(), "Connection manager has been shut down");
      if (this.log.isDebugEnabled()) {
         this.log.debug("Get connection for route " + route);
      }

      Asserts.check(!this.leased, "Connection is still allocated");
      if (!LangUtils.equals((Object)this.route, (Object)route) || !LangUtils.equals(this.state, state)) {
         this.closeConnection();
      }

      this.route = route;
      this.state = state;
      this.checkExpiry();
      if (this.conn == null) {
         this.conn = this.connFactory.create(route, this.connConfig);
      }

      this.conn.setSocketTimeout(this.socketConfig.getSoTimeout());
      this.leased = true;
      return this.conn;
   }

   public synchronized void releaseConnection(HttpClientConnection conn, Object state, long keepalive, TimeUnit timeUnit) {
      Args.notNull(conn, "Connection");
      Asserts.check(conn == this.conn, "Connection not obtained from this manager");
      if (this.log.isDebugEnabled()) {
         this.log.debug("Releasing connection " + conn);
      }

      if (!this.isShutdown.get()) {
         try {
            this.updated = System.currentTimeMillis();
            if (!this.conn.isOpen()) {
               this.conn = null;
               this.route = null;
               this.conn = null;
               this.expiry = Long.MAX_VALUE;
            } else {
               this.state = state;
               this.conn.setSocketTimeout(0);
               if (this.log.isDebugEnabled()) {
                  String s;
                  if (keepalive > 0L) {
                     s = "for " + keepalive + " " + timeUnit;
                  } else {
                     s = "indefinitely";
                  }

                  this.log.debug("Connection can be kept alive " + s);
               }

               if (keepalive > 0L) {
                  this.expiry = this.updated + timeUnit.toMillis(keepalive);
               } else {
                  this.expiry = Long.MAX_VALUE;
               }
            }
         } finally {
            this.leased = false;
         }

      }
   }

   public void connect(HttpClientConnection conn, HttpRoute route, int connectTimeout, HttpContext context) throws IOException {
      Args.notNull(conn, "Connection");
      Args.notNull(route, "HTTP route");
      Asserts.check(conn == this.conn, "Connection not obtained from this manager");
      HttpHost host;
      if (route.getProxyHost() != null) {
         host = route.getProxyHost();
      } else {
         host = route.getTargetHost();
      }

      InetSocketAddress localAddress = route.getLocalSocketAddress();
      this.connectionOperator.connect(this.conn, host, localAddress, connectTimeout, this.socketConfig, context);
   }

   public void upgrade(HttpClientConnection conn, HttpRoute route, HttpContext context) throws IOException {
      Args.notNull(conn, "Connection");
      Args.notNull(route, "HTTP route");
      Asserts.check(conn == this.conn, "Connection not obtained from this manager");
      this.connectionOperator.upgrade(this.conn, route.getTargetHost(), context);
   }

   public void routeComplete(HttpClientConnection conn, HttpRoute route, HttpContext context) throws IOException {
   }

   public synchronized void closeExpiredConnections() {
      if (!this.isShutdown.get()) {
         if (!this.leased) {
            this.checkExpiry();
         }

      }
   }

   public synchronized void closeIdleConnections(long idletime, TimeUnit timeUnit) {
      Args.notNull(timeUnit, "Time unit");
      if (!this.isShutdown.get()) {
         if (!this.leased) {
            long time = timeUnit.toMillis(idletime);
            if (time < 0L) {
               time = 0L;
            }

            long deadline = System.currentTimeMillis() - time;
            if (this.updated <= deadline) {
               this.closeConnection();
            }
         }

      }
   }

   public void shutdown() {
      if (this.isShutdown.compareAndSet(false, true) && this.conn != null) {
         this.log.debug("Shutting down connection");

         try {
            this.conn.shutdown();
         } catch (IOException iox) {
            if (this.log.isDebugEnabled()) {
               this.log.debug("I/O exception shutting down connection", iox);
            }
         }

         this.conn = null;
      }

   }
}
