package org.limewire.http.auth;

import java.io.IOException;

import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.Credentials;
import org.apache.http.nio.entity.ConsumingNHttpEntity;
import org.apache.http.nio.protocol.NHttpRequestHandler;
import org.apache.http.nio.protocol.NHttpResponseTrigger;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.UriPatternMatcher;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;

import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * Default implementation of {@link AuthenticationInterceptor}. 
 * <p>
 * Intercepts {@link HttpRequest}, parses out authentication data and puts
 * {@link ServerAuthState} into the {@link HttpContext} so it can be queried
 * by other handlers for authentications state.
 */
@Singleton
public class AuthenticationInterceptorImpl implements AuthenticationInterceptor {
    
    private static final Log LOG = LogFactory.getLog(AuthenticationInterceptorImpl.class);
    
    private final Authenticator authenticator;
    private final UriPatternMatcher<String> protectedURIs;
    
    @Inject
    public AuthenticationInterceptorImpl(Authenticator authenticator) {
        this.authenticator = authenticator;
        protectedURIs = new UriPatternMatcher<String>();
    }

    public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
        if (request == null) {
            throw new IllegalArgumentException("HTTP request may not be null");
        }
        if (context == null) {
            throw new IllegalArgumentException("HTTP context may not be null");
        }
        
        ServerAuthState authState = new ServerAuthState();
        ServerAuthScheme authScheme = new BasicServerAuthScheme(authenticator);
        authState.setScheme(authScheme);  // TODO other schemes, scheme registry, etc
        context.setAttribute(ServerAuthState.AUTH_STATE, authState);
        if(protectedURIs.lookup(request.getRequestLine().getUri()) != null) {
            LOG.debugf("entering protected uri: {0}", request.getRequestLine().getUri());
            Credentials credentials = authScheme.authenticate(request);
            if(credentials != null) {
                authState.setCredentials(credentials);
                authScheme.setComplete();
            }                
        } else {
            authScheme.setComplete();
        }
    }
    
    public NHttpRequestHandler getGuardedHandler(String url, NHttpRequestHandler handler) {
        if(isProtected(handler)) {
            protectedURIs.register(url, url);
            return new GuardingHandler(handler);
        } else {
            return handler;
        }
    }
    
    @Override
    public void unregisterHandler(String urlPattern) {
        protectedURIs.unregister(urlPattern);
    }
    
    private boolean isProtected(NHttpRequestHandler handler) {
        return handler.getClass().getAnnotation(RequiresAuthentication.class) != null;        
    }
    
    private static class GuardingHandler implements NHttpRequestHandler {
        private final NHttpRequestHandler handler;

        public GuardingHandler(NHttpRequestHandler handler) {
            this.handler = handler;
        }

        public ConsumingNHttpEntity entityRequest(HttpEntityEnclosingRequest request, HttpContext context) throws HttpException, IOException {
            ServerAuthState authState = (ServerAuthState) context.getAttribute(ServerAuthState.AUTH_STATE);
            ServerAuthScheme authScheme = authState.getScheme();
            if(authScheme.isComplete()) {
                return handler.entityRequest(request, context);
            } else {
                return null; // TODO ?    
            }
        }

        public void handle(HttpRequest request, HttpResponse response, NHttpResponseTrigger trigger, HttpContext context) throws HttpException, IOException {
            ServerAuthState authState = (ServerAuthState) context.getAttribute(ServerAuthState.AUTH_STATE);
            ServerAuthScheme authScheme = authState.getScheme();
            if(authScheme.isComplete()) {
                handler.handle(request, response, trigger, context);
            } else {
                response.setStatusCode(HttpStatus.SC_UNAUTHORIZED);
                response.addHeader(authScheme.createChallenge());
                trigger.submitResponse(response);
            }
        }
    }
    
}
