package org.apache.cactus.client.authentication;

import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.StringTokenizer;

import org.apache.cactus.WebRequest;
import org.apache.cactus.client.HttpClientConnectionHelper;
import org.apache.cactus.util.Configuration;

public class FormAuthentication extends AbstractAuthentication
{
    private URL securityCheckURL = null;
    private String sessionIdCookieName = null;
    private String sessionId = null;
   
    public FormAuthentication(String theName, String thePassword)
    {
        super(theName, thePassword);
    }
    
    /**
     * @see AbstractAuthentication#validateName(String)
     */
    protected void validateName(String theName)
    {
        // Nothing to do here...
    }
    
    /**
     * @see AbstractAuthentication#validatePassword(String)
     */
    protected void validatePassword(String thePassword)
    {
        // Nothing to do here...
    }

    /**
     * @see AbstractAuthentication#configure(WebRequest)
     */
    public void configure(WebRequest theRequest)
    {
        if (sessionId == null)
        {
           authenticate();
        }
      
        if (sessionId != null)
        {
            theRequest.addCookie(sessionIdCookieName, sessionId);
        }
    }
    
    /**
     * This sets the URL to use when attempting to log in. This method is used
     * if for whatever reason the default URL is incorrect.
     *
     * @param theUrl A URL to use to attempt to login.
     */
    public void setSecurityCheckURL(URL theUrl)
    {
       securityCheckURL = theUrl;
    }
    
    /**
     * This returns the URL to use when attempting to log in. By default, it's
     * the context URL defined in the cactus.properties file with  
     * "/j_security_check" appended. 
     *
     * @return The URL that is being used to attempt to login.
     */
    public URL getSecurityCheckURL()
    {
        if (securityCheckURL == null)
        {
            // Configure default
            try
            {
                securityCheckURL = 
                    new URL(Configuration.getContextURL() + "/j_security_check");
            }
            catch(java.net.MalformedURLException e)
            {
                // Convert to a runtime exception since this won't happen very
                // often.
                throw new IllegalArgumentException(
                    "Unable to create default Security Check URL, the " +
                    "context URL in cactus.properties may be invalid.");
            }
        }
        
        return securityCheckURL;
    }
    
    public void authenticate()
    {
        try
        {
            // Create a helper that will connect to the security check URL.
            HttpClientConnectionHelper helper = 
                new HttpClientConnectionHelper(getSecurityCheckURL().toString());
                
            // Configure a web request with the username and password.
            WebRequest request = new WebRequest();
            request.addParameter("j_username", getName(), WebRequest.POST_METHOD);
            request.addParameter("j_password", getPassword(), WebRequest.POST_METHOD);
            
            // Make the connection using the configured web request.
            HttpURLConnection connection = helper.connect(request);
//System.out.println("Response code: " + connection.getResponseCode());
//printHeaders(connection);
//print(connection);
        
            // Clean any existing session ID.
            sessionId = null;
            
            // Check (possible multiple) cookies for a JSESSIONID.
            int i = 1;
            String key = connection.getHeaderFieldKey(i);
            while (key != null)
            {
                if (key.equalsIgnoreCase("set-cookie"))
                {
                    // Cookie is in the form:
                    // NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure
                    // The only thing we care about is finding a cookie with the
                    // name "JSESSIONID" and caching the value.
                    
                    String cookiestr = connection.getHeaderField(i);
                    String nameValue = cookiestr.substring(0, cookiestr.indexOf(";"));
                    int equalsChar = nameValue.indexOf("=");
                    String name = nameValue.substring(0, equalsChar);

                    if (name.equalsIgnoreCase("JSESSIONID"))
                    {
                        // We must set a cookie with the exact same name as the
                        // one given to us, so to preserve any capitalization
                        // issues, cache the exact cookie name.
                        sessionIdCookieName = name;
                        sessionId = nameValue.substring(equalsChar+1);
                        break;
                    }
                }
                key = connection.getHeaderFieldKey(++i);
            }

            // If we get back a response code of 302, it means we were redirected
            // to the context root after successfully logging in. If we receive
            // anything else, we didn't log in correctly.
            if (connection.getResponseCode() != 302)
            {
                throw new IllegalArgumentException(
                    "Unable to login, probably due to bad username/password. " +
                    "[Bad Response Code]");
            }
            else
            {
                // Verify we're redirected properly
                String location = connection.getHeaderField("Location");
                if (location != null)
                {
                    // WebLogic, at least, appends JSESSIONID after a semicolon 
                    // at the end of theURL. Remove it.
                    int semicolonIndex = location.indexOf(";");
                    if (semicolonIndex != -1)
                    {
                        location = location.substring(0, semicolonIndex);
                    }
                    
                    // We should get redirected to the context url
                    if (! location.equals(Configuration.getContextURL()))
                    {
                        throw new IllegalArgumentException(
                            "Unable to login, probably due to bad username/" +
                            "password. [Incorrect Location Redirect]");
                    }
                }
                else
                {
                    // ??? Assume everything's alright?
                    System.out.println("NO LOCATION HEADER RETRIEVED!");
                }
            }
        }
        catch (Throwable e)
        {
            // This is to deal with some weirdness with the HttpURLConnection 
            // impl, because the compiler complains the connect call above 
            // throws a "Throwable" that must be caught. If anything happends 
            // we'll just turn it into a runtime exception.
            throw new IllegalStateException(e.getClass() + ": " + e.getMessage());
        }
    }
    
    private void print(HttpURLConnection connection)
    {
        try
        {
            java.io.InputStream in = connection.getInputStream();
            
            byte[] buf = new byte[1000];
            int num = in.read(buf);
            while (num != -1)
            {
                System.out.println(new String(buf, 0, num));
                num = in.read(buf);
            }
        }
        catch (java.io.IOException e)
        {
            System.out.println(e);
        }
    }
    
    private void printHeaders(HttpURLConnection connection)
    {
        int i = 1;
        String key = connection.getHeaderFieldKey(i);
        while (key != null)
        {
            System.out.println("Header - " + key + ": " + connection.getHeaderField(i));
            key = connection.getHeaderFieldKey(++i);
        }
    }
}
