package org.chenyang.http.impl.client;

import org.chenyang.commons.logging.Log;
import org.chenyang.commons.logging.LogFactory;
import org.chenyang.http.FormattedHeader;
import org.chenyang.http.Header;
import org.chenyang.http.HttpHost;
import org.chenyang.http.HttpResponse;
import org.chenyang.http.annotation.Contract;
import org.chenyang.http.annotation.ThreadingBehavior;
import org.chenyang.http.auth.AuthOption;
import org.chenyang.http.auth.AuthScheme;
import org.chenyang.http.auth.AuthSchemeProvider;
import org.chenyang.http.auth.AuthScope;
import org.chenyang.http.auth.Credentials;
import org.chenyang.http.auth.MalformedChallengeException;
import org.chenyang.http.client.AuthCache;
import org.chenyang.http.client.AuthenticationStrategy;
import org.chenyang.http.client.CredentialsProvider;
import org.chenyang.http.client.config.RequestConfig;
import org.chenyang.http.client.protocol.HttpClientContext;
import org.chenyang.http.config.Lookup;
import org.chenyang.http.protocol.HTTP;
import org.chenyang.http.protocol.HttpContext;
import org.chenyang.http.util.Args;
import org.chenyang.http.util.CharArrayBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;

@Contract(
   threading = ThreadingBehavior.IMMUTABLE
)
abstract class AuthenticationStrategyImpl implements AuthenticationStrategy {
   private final Log log = LogFactory.getLog(this.getClass());
   private static final List<String> DEFAULT_SCHEME_PRIORITY = Collections.unmodifiableList(Arrays.asList("Negotiate", "Kerberos", "NTLM", "CredSSP", "Digest", "Basic"));
   private final int challengeCode;
   private final String headerName;

   AuthenticationStrategyImpl(int challengeCode, String headerName) {
      this.challengeCode = challengeCode;
      this.headerName = headerName;
   }

   public boolean isAuthenticationRequested(HttpHost authhost, HttpResponse response, HttpContext context) {
      Args.notNull(response, "HTTP response");
      int status = response.getStatusLine().getStatusCode();
      return status == this.challengeCode;
   }

   public Map<String, Header> getChallenges(HttpHost authhost, HttpResponse response, HttpContext context) throws MalformedChallengeException {
      Args.notNull(response, "HTTP response");
      Header[] headers = response.getHeaders(this.headerName);
      Map<String, Header> map = new HashMap(headers.length);

      for(Header header : headers) {
         CharArrayBuffer buffer;
         int pos;
         if (header instanceof FormattedHeader) {
            buffer = ((FormattedHeader)header).getBuffer();
            pos = ((FormattedHeader)header).getValuePos();
         } else {
            String s = header.getValue();
            if (s == null) {
               throw new MalformedChallengeException("Header value is null");
            }

            buffer = new CharArrayBuffer(s.length());
            buffer.append(s);
            pos = 0;
         }

         while(pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) {
            ++pos;
         }

         int beginIndex;
         for(beginIndex = pos; pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos)); ++pos) {
         }

         String s = buffer.substring(beginIndex, pos);
         map.put(s.toLowerCase(Locale.ROOT), header);
      }

      return map;
   }

   abstract Collection<String> getPreferredAuthSchemes(RequestConfig var1);

   public Queue<AuthOption> select(Map<String, Header> challenges, HttpHost authhost, HttpResponse response, HttpContext context) throws MalformedChallengeException {
      Args.notNull(challenges, "Map of auth challenges");
      Args.notNull(authhost, "Host");
      Args.notNull(response, "HTTP response");
      Args.notNull(context, "HTTP context");
      HttpClientContext clientContext = HttpClientContext.adapt(context);
      Queue<AuthOption> options = new LinkedList();
      Lookup<AuthSchemeProvider> registry = clientContext.getAuthSchemeRegistry();
      if (registry == null) {
         this.log.debug("Auth scheme registry not set in the context");
         return options;
      } else {
         CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
         if (credsProvider == null) {
            this.log.debug("Credentials provider not set in the context");
            return options;
         } else {
            RequestConfig config = clientContext.getRequestConfig();
            Collection<String> authPrefs = this.getPreferredAuthSchemes(config);
            if (authPrefs == null) {
               authPrefs = DEFAULT_SCHEME_PRIORITY;
            }

            if (this.log.isDebugEnabled()) {
               this.log.debug("Authentication schemes in the order of preference: " + authPrefs);
            }

            for(String id : authPrefs) {
               Header challenge = (Header)challenges.get(id.toLowerCase(Locale.ROOT));
               if (challenge != null) {
                  AuthSchemeProvider authSchemeProvider = registry.lookup(id);
                  if (authSchemeProvider == null) {
                     if (this.log.isWarnEnabled()) {
                        this.log.warn("Authentication scheme " + id + " not supported");
                     }
                  } else {
                     AuthScheme authScheme = authSchemeProvider.create(context);
                     authScheme.processChallenge(challenge);
                     AuthScope authScope = new AuthScope(authhost, authScheme.getRealm(), authScheme.getSchemeName());
                     Credentials credentials = credsProvider.getCredentials(authScope);
                     if (credentials != null) {
                        options.add(new AuthOption(authScheme, credentials));
                     }
                  }
               } else if (this.log.isDebugEnabled()) {
                  this.log.debug("Challenge for " + id + " authentication scheme not available");
               }
            }

            return options;
         }
      }
   }

   public void authSucceeded(HttpHost authhost, AuthScheme authScheme, HttpContext context) {
      Args.notNull(authhost, "Host");
      Args.notNull(authScheme, "Auth scheme");
      Args.notNull(context, "HTTP context");
      HttpClientContext clientContext = HttpClientContext.adapt(context);
      if (this.isCachable(authScheme)) {
         AuthCache authCache = clientContext.getAuthCache();
         if (authCache == null) {
            authCache = new BasicAuthCache();
            clientContext.setAuthCache(authCache);
         }

         if (this.log.isDebugEnabled()) {
            this.log.debug("Caching '" + authScheme.getSchemeName() + "' auth scheme for " + authhost);
         }

         authCache.put(authhost, authScheme);
      }

   }

   protected boolean isCachable(AuthScheme authScheme) {
      if (authScheme != null && authScheme.isComplete()) {
         String schemeName = authScheme.getSchemeName();
         return schemeName.equalsIgnoreCase("Basic");
      } else {
         return false;
      }
   }

   public void authFailed(HttpHost authhost, AuthScheme authScheme, HttpContext context) {
      Args.notNull(authhost, "Host");
      Args.notNull(context, "HTTP context");
      HttpClientContext clientContext = HttpClientContext.adapt(context);
      AuthCache authCache = clientContext.getAuthCache();
      if (authCache != null) {
         if (this.log.isDebugEnabled()) {
            this.log.debug("Clearing cached auth scheme for " + authhost);
         }

         authCache.remove(authhost);
      }

   }
}
