package org.chenyang.http.impl.client;

import org.chenyang.commons.logging.Log;
import org.chenyang.commons.logging.LogFactory;
import org.chenyang.http.Header;
import org.chenyang.http.HttpHost;
import org.chenyang.http.HttpRequest;
import org.chenyang.http.HttpResponse;
import org.chenyang.http.ProtocolException;
import org.chenyang.http.annotation.Contract;
import org.chenyang.http.annotation.ThreadingBehavior;
import org.chenyang.http.client.CircularRedirectException;
import org.chenyang.http.client.RedirectStrategy;
import org.chenyang.http.client.config.RequestConfig;
import org.chenyang.http.client.methods.HttpGet;
import org.chenyang.http.client.methods.HttpHead;
import org.chenyang.http.client.methods.HttpUriRequest;
import org.chenyang.http.client.methods.RequestBuilder;
import org.chenyang.http.client.protocol.HttpClientContext;
import org.chenyang.http.client.utils.URIUtils;
import org.chenyang.http.protocol.HttpContext;
import org.chenyang.http.util.Args;
import org.chenyang.http.util.Asserts;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;

@Contract(
   threading = ThreadingBehavior.IMMUTABLE
)
public class DefaultRedirectStrategy implements RedirectStrategy {
   private final Log log;
   public static final int SC_PERMANENT_REDIRECT = 308;
   /** @deprecated */
   @Deprecated
   public static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations";
   public static final DefaultRedirectStrategy INSTANCE = new DefaultRedirectStrategy();
   private final String[] redirectMethods;

   public DefaultRedirectStrategy() {
      this(new String[]{"GET", "HEAD"});
   }

   public DefaultRedirectStrategy(String[] redirectMethods) {
      this.log = LogFactory.getLog(this.getClass());
      String[] tmp = redirectMethods.clone();
      Arrays.sort(tmp);
      this.redirectMethods = tmp;
   }

   public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException {
      Args.notNull(request, "HTTP request");
      Args.notNull(response, "HTTP response");
      int statusCode = response.getStatusLine().getStatusCode();
      String method = request.getRequestLine().getMethod();
      Header locationHeader = response.getFirstHeader("location");
      switch (statusCode) {
         case 301:
         case 307:
         case 308:
            return this.isRedirectable(method);
         case 302:
            return this.isRedirectable(method) && locationHeader != null;
         case 303:
            return true;
         case 304:
         case 305:
         case 306:
         default:
            return false;
      }
   }

   public URI getLocationURI(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException {
      Args.notNull(request, "HTTP request");
      Args.notNull(response, "HTTP response");
      Args.notNull(context, "HTTP context");
      HttpClientContext clientContext = HttpClientContext.adapt(context);
      Header locationHeader = response.getFirstHeader("location");
      if (locationHeader == null) {
         throw new ProtocolException("Received redirect response " + response.getStatusLine() + " but no location header");
      } else {
         String location = locationHeader.getValue();
         if (this.log.isDebugEnabled()) {
            this.log.debug("Redirect requested to location '" + location + "'");
         }

         RequestConfig config = clientContext.getRequestConfig();
         URI uri = this.createLocationURI(location);

         try {
            if (config.isNormalizeUri()) {
               uri = URIUtils.normalizeSyntax(uri);
            }

            if (!uri.isAbsolute()) {
               if (!config.isRelativeRedirectsAllowed()) {
                  throw new ProtocolException("Relative redirect location '" + uri + "' not allowed");
               }

               HttpHost target = clientContext.getTargetHost();
               Asserts.notNull(target, "Target host");
               URI requestURI = new URI(request.getRequestLine().getUri());
               URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, config.isNormalizeUri() ? URIUtils.NORMALIZE : URIUtils.NO_FLAGS);
               uri = URIUtils.resolve(absoluteRequestURI, uri);
            }
         } catch (URISyntaxException ex) {
            throw new ProtocolException(ex.getMessage(), ex);
         }

         RedirectLocations redirectLocations = (RedirectLocations)clientContext.getAttribute("http.protocol.redirect-locations");
         if (redirectLocations == null) {
            redirectLocations = new RedirectLocations();
            context.setAttribute("http.protocol.redirect-locations", redirectLocations);
         }

         if (!config.isCircularRedirectsAllowed() && redirectLocations.contains(uri)) {
            throw new CircularRedirectException("Circular redirect to '" + uri + "'");
         } else {
            redirectLocations.add(uri);
            return uri;
         }
      }
   }

   protected URI createLocationURI(String location) throws ProtocolException {
      try {
         return new URI(location);
      } catch (URISyntaxException ex) {
         throw new ProtocolException("Invalid redirect URI: " + location, ex);
      }
   }

   protected boolean isRedirectable(String method) {
      return Arrays.binarySearch(this.redirectMethods, method) >= 0;
   }

   public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException {
      URI uri = this.getLocationURI(request, response, context);
      String method = request.getRequestLine().getMethod();
      if (method.equalsIgnoreCase("HEAD")) {
         return new HttpHead(uri);
      } else if (method.equalsIgnoreCase("GET")) {
         return new HttpGet(uri);
      } else {
         int status = response.getStatusLine().getStatusCode();
         return (HttpUriRequest)(status != 307 && status != 308 ? new HttpGet(uri) : RequestBuilder.copy(request).setUri(uri).build());
      }
   }
}
