package gov.va.med.fw.ui.filter;

//Java imports
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;

//Commons imports
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

//Framework imports
import gov.va.med.fw.security.EncryptionServiceException;
import gov.va.med.fw.security.KeyCache;
import gov.va.med.fw.ui.security.UIEncryptionService;

/**
 * Wrapper class for the request. This class decrypt the 
 * query string if encrypted and override the related methods. 
 *  
 * @author Muddaiah Ranga
 * @version 3.0
 */
public class DecryptedRequest extends HttpServletRequestWrapper implements KeyCache
{
	//Key used to store the secret key in the session
	public static final String SESSION_SECRET_KEY = "SESSION_SECRET_KEY";
	
	protected Map parameterMap;
    protected String queryString; 
    
    /**
     * An instance of a log component used for logging information
     */ 
    protected Log logger = LogFactory.getLog(getClass());
    
    /**
     * Custom constructor to create the request wrapper. This constructor 
     * creates a wrapper and decrypts the query string.
     * 
     * @param request the request object
     */
    public DecryptedRequest(HttpServletRequest request)
    {
        super(request);
        queryString = super.getQueryString();
        parameterMap = super.getParameterMap();
    }
    
    /**
     * Custom constructor to create the request wrapper. This constructor 
     * creates a wrapper and decrypts the query string.
     * 
     * @param request the request object
     * @param encryptionService encryption service
     * @throws DecryptionException thrown when there is a decryption exception
     */
    public DecryptedRequest(HttpServletRequest request, UIEncryptionService encryptionService)
    throws EncryptionServiceException
    {
        super(request);
        queryString = request.getQueryString();
        parameterMap = super.getParameterMap();
    	if(encryptionService != null && encryptionService.isEncryptionEnabled() && this.getKey() != null)
    	{
    		DecryptedRequest tmpRequest = new DecryptedRequest(request);
    		//Decrypt the hidden fields
    		parameterMap = decryptHiddenFields(tmpRequest,encryptionService);
    		
    		//Decrypt the encrypted query string
    		String encryptedQueryString = request.getParameter(UIEncryptionService.CRYPTO_PREFIX);
    		if(StringUtils.isNotEmpty(encryptedQueryString))
    		{
    			decryptQueryString(tmpRequest,encryptedQueryString,parameterMap,encryptionService);
    		}
    	}
    	if(parameterMap == null) parameterMap = new Hashtable();
    	parameterMap = Collections.unmodifiableMap(parameterMap);
    }

	/**
	 * Gets the key from the cache.
	 */
	public SecretKey getKey()
	{
		HttpSession session = getSession(true);
        return (SecretKey)session.getAttribute(SESSION_SECRET_KEY);
	}
	
	/**
	 * Sets the key in the cache.
	 */
	public void setKey(SecretKey key)
	{
		HttpSession session = getSession(true);
        session.setAttribute(SESSION_SECRET_KEY,key);
	}
	
    /**
     * Returns the query string that is contained in the request URL after the path. 
     * This method returns null if the URL does not have a query string. Same as the 
     * value of the CGI variable QUERY_STRING.
     * 
     * @return a String containing the query string or null if the URL contains no query string. 
     * The value is not decoded by the container.
     */
    public String getQueryString()
    {
        return this.queryString;
    }

    /**
     * Returns the value of a request parameter as a String, or null 
     * if the parameter does not exist. Request parameters are extra 
     * information sent with the request. For HTTP servlets, parameters 
     * are contained in the query string or posted form data. Please also
     * refer to servlet API documentation. 
     * 
     * @param name a String specifying the name of the parameter
     * @return a String representing the single value of the parameter
     */
    public String getParameter(String name) 
    {
        String value = null;
        try
        {
        	String[] values = (String[])getParameterMap().get(name);
        	if(values != null)
        	{
        		value = values[0];
        	}
        }
        catch(Exception ignore)
        {
            value = null;
        } 
        return value;
    }

    /**
     * Returns an Enumeration of String objects containing the names of 
     * the parameters contained in this request. If the request has no 
     * parameters, the method returns an empty Enumeration.
     * 
     * @return an Enumeration of String objects, each String containing 
     * the name of a request parameter; or an empty Enumeration if the 
     * request has no parameters
     */
    public Enumeration getParameterNames() 
    {
    	Map map = getParameterMap();
    	if(map == null) 
    	{
    		return null;
    	}
    	if(map instanceof Hashtable)
    	{
    		return ((Hashtable)map).keys();
    	}
    	return Collections.enumeration(map.keySet());
    }
        
    /**
     * Returns an array of String objects containing all of the values 
     * the given request parameter has, or null if the parameter does 
     * not exist. 
     * 
     * @param name a String containing the name of the parameter 
     * whose value is requested
     * @return an array of String objects containing the parameter's values
     */
    public String[] getParameterValues(String name) 
    {
        try
        {
            return (String[])(getParameterMap().get(name));
        }
        catch(Exception ignore)
        {
            return null;
        }
    }

    /**
     * Returns a java.util.Map of the parameters of this request. 
     * Request parameters are extra information sent with the request. 
     * For HTTP servlets, parameters are contained in the query string 
     * or posted form data.
     * 
     * @return an immutable java.util.Map containing parameter names as 
     * keys and parameter values as map values. The keys in the parameter 
     * map are of type String. The values in the parameter map are of type 
     * String array.
     */
    public Map getParameterMap() 
    {
    	return parameterMap;
    }

    /********************************************************************************/
    /**************************** Private Methods ***********************************/
    /********************************************************************************/
    
    /**
     * Decrypt hidden field.
     * 
     * @param request the request object
     * @param encryptionService encryption service
     * @throws EncryptionServiceException thrown when there is a decryption error
     */
    private Map decryptHiddenFields(HttpServletRequest request,UIEncryptionService encryptionService)
    throws EncryptionServiceException
    {
    	Map paramMap = request.getParameterMap();
		Map tmpMap = new  Hashtable();
    	//Decrypt the hidden fields
    	if(paramMap != null || !paramMap.isEmpty())
    	{
    		for(Iterator iter = paramMap.keySet().iterator(); iter.hasNext();)
    		{
    			String name = (String)iter.next();
    			String[] values = (String[])paramMap.get(name);
    			if(values != null)
    			{
    				for(int i=0; i<values.length; i++)
    				{
    					String value = values[i];
    					if(StringUtils.isNotEmpty(value) && value.startsWith(UIEncryptionService.HIDDEN_FIELD_COLAN))
    					{
    						value = encryptionService.decrypt(request,StringUtils.substringAfter(value,UIEncryptionService.HIDDEN_FIELD_COLAN));
    						values[i] = value;
    					}
    				}
    			}
    			tmpMap.put(name,values);
    		}
    	}
    	return tmpMap;
    }
    
    /**
     * Decrypt and parses the query string.
     * 
     * @param request the request object
     * @param encryptedQS the query string
     * @param paramMap parameters map
     * @param encryptionService encryption service
     * @throws EncryptionServiceException thrown when there is a decryption error
     */
    private Map decryptQueryString(HttpServletRequest request,String encryptedQS, Map paramMap,UIEncryptionService encryptionService)
    throws EncryptionServiceException
    {
    	if(paramMap == null) paramMap = new Hashtable();
        String decryptedQueryString = encryptionService.decrypt(request,encryptedQS);
        if(StringUtils.isEmpty(decryptedQueryString))
        {
            throw new EncryptionServiceException("decrypted query string is null.");
        }
        try
		{
        	String characterEncoding = request.getCharacterEncoding();
        	if(StringUtils.isEmpty(characterEncoding))
        	{
        		characterEncoding = encryptionService.getDefaultCharacterEncoding();
        	}
        	queryString = URLDecoder.decode(decryptedQueryString,characterEncoding);
		}
        catch(UnsupportedEncodingException ex)
		{
        	throw new EncryptionServiceException("unsupported encoding error.",ex);
		}
        
        //If the decrypted string is empty, return.
        if(StringUtils.isEmpty(queryString))
        {
            return paramMap;
        }
        
        String valArray[], key, value = null;
        for(Iterator iter = getTokens(queryString).iterator(); iter.hasNext();)
        {
            String pair = (String)iter.next();
            int pos = pair.indexOf('=');
            if (pos == -1) 
            {
            	throw new IllegalArgumentException("Invalid query string");
            }
            key = pair.substring(0, pos);
            value = pair.substring(pos + 1, pair.length());
            if(paramMap.containsKey(key))
            {
                String oldVals[] = (String[])paramMap.get(key);
                valArray = new String[oldVals.length + 1];
                for(int i = 0; i < oldVals.length; i++)
                {
                    valArray[i] = oldVals[i];
                }
                valArray[oldVals.length] = value;
            } 
            else
            {
                valArray = new String[1];
                valArray[0] = value;
            }
            paramMap.put(key, valArray);
        }
        return paramMap;
    }
    
    /**
     * Gets the '&' delimted tokens.
     * 
     * @param queryString the query string
     * 
     * @return list of tokens.
     */
	private static List getTokens(String queryString)
	{
		List list = new ArrayList();
		String tokens[] = queryString.split("&");
		for(int i=0; i<tokens.length; i++)
		{
			String pair = tokens[i];
            if(!list.isEmpty() && !StringUtils.contains(pair,'=')) 
            {
            	int lastElementIndex = list.size()-1;
            	String previousPair = (String)list.get(lastElementIndex);
            	list.set(lastElementIndex,previousPair + '&' + pair);
            	continue;
            }
			list.add(pair);
		}
		return list;
	}
}
