package hirondelle.web4j.request;

import hirondelle.web4j.action.Action;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.ModelFromRequest;
import hirondelle.web4j.model.ModelUtil;
import hirondelle.web4j.readconfig.Config;
import hirondelle.web4j.security.ApplicationFirewall;
import hirondelle.web4j.util.Util;

import java.util.regex.Pattern;

/**
 <span class="highlight">Request parameter as a name and 
 (usually) an associated <em>regular expression</em>.</span>

 <P>This class does not <em>directly</em> provide access to the parameter <em>value</em>.
 For such services, please see {@link RequestParser} and {@link ModelFromRequest}. 
  
 <P>This class separates request parameters into two kinds : file upload request 
 parameters, and all others (here called "regular" request parameters).
  
 <P><b><a name="Regular">Regular Request Parameters</a></b><br>
 Regular request parameters are associated with :
 <ul>
 <li>a name, corresponding to both an underlying HTTP request parameter name, and 
 an underlying control name - see <a href="#NamingConvention">naming convention</a>. 
 <li>a regular expression, used by {@link ApplicationFirewall} to perform 
 <a href="ApplicationFirewall.html#HardValidation">hard validation</a>  
 </ul> 
 
 <P><b><a name="FileUpload">File Upload Request Parameters</a></b><br>
 Files are uploaded using forms having : 
<ul>
 <li> <tt>method="POST"</tt>
 <li> <tt>enctype="multipart/form-data"</tt>
 <li> an <tt>&lt;INPUT type="file"&gt;</tt> control
</ul>
 
 <P>In addition, note that the Servlet API does <em>not</em> have extensive services for 
 processing file upload parameters. It is likely best to use a third party tool for 
 that task. 
 
 <P>File upload request parameters, <em>as represented by this class</em>, have only a
 name associated with them, and no regular expression. This is because WEB4J 
 cannot perform <a href="ApplicationFirewall.html#HardValidation">hard validation</a>  
 on the value of a file upload parameter - since the user may select any file whatsoever, 
 validation of file contents can only be treated 
 as a <a href="ApplicationFirewall.html#SoftValidation">soft validation</a>. If there is a 
 problem, the response to the user must be polished, as part of the normal operation of 
 the application.  
 
 <P>As an example, an {@link Action} might perform 
 <a href="ApplicationFirewall.html#SoftValidation">soft validation</a> on a file upload parameter 
 for these items : 
 <ul>   
 <li>file size does not exceed a maximum value
 <li>MIME type matches a regular expression  
 <li>file name matches a regular expression
 <li>text file content may be matched to a regular expression
 </ul> 
 
 <P><b><a name="NamingConvention">Naming Convention</a></b><br>
 <span class="highlight">Parameter names are usually not arbitrary in WEB4J.</span>
 Instead, a simple convention is used which allows for automated mapping between 
 request parameter names and corresponding <tt>getXXX</tt> methods of Model Objects  
 (see {@link hirondelle.web4j.ui.tag.Populate}). For example, a parameter 
 named <tt>'Birth Date'</tt> (or <tt>'birthDate'</tt>) is mapped to a method named 
 <tt>getBirthDate()</tt> when prepopulating a form with the contents of 
 a Model Object. (The <tt>'Birth Date'</tt> naming style is recommended, since it 
 has this advantage : when messages regarding form input are presented to the user, 
 the control name may be used directly, without trivial mapping  
 of a 'coder-friendly' parameter name into more user-friendly text.)
  
 <P> Some parameters - notably those passed to <tt>Template.jsp</tt> - are not
 processed at all by the <tt>Controller</tt>, but are used directly in JSPs 
 instead. Such parameters do not undergo 
 <a href="ApplicationFirewall.html#HardValidation">hard validation</a> by the 
 {@link hirondelle.web4j.security.ApplicationFirewall}, and are not represented by this class.
 
 <P> See {@link java.util.regex.Pattern} for more information on regular expressions.
*/
public final class RequestParameter { 
  
  /**
   Return a <a href="#Regular">regular parameter</a> hard-validated only for 
   name and size. 
   
   <P>The size is taken from the <tt>MaxRequestParamValueSize</tt> setting in <tt>web.xml</tt>.
    
   @param aName name of the underlying HTTP request parameter. See 
   <a href="#NamingConvention">naming convention</a>. 
  */
  public static RequestParameter withLengthCheck(String aName){
    Config config = new Config();
    String regex = "(.){0," + config.getMaxRequestParamValueSize() + "}";
    Pattern lengthPattern = Pattern.compile(regex, Pattern.DOTALL);
    return withRegexCheck(aName, lengthPattern);
  }
  
  /**
   Return a <a href="#Regular">regular parameter</a> hard-validated for name and 
   for value matching a regular expression.
   
   @param aName name of the underlying HTTP request parameter. See 
   <a href="#NamingConvention">naming convention</a>. 
   @param aValueRegex regular expression for doing hard validation of the request 
   parameter value(s).
  */
  public static RequestParameter withRegexCheck(String aName, Pattern aValueRegex){
    return new RequestParameter(aName, aValueRegex);    
  }
  
  /**
   Return a <a href="#Regular">regular parameter</a> hard-validated for name and 
   for value matching a regular expression.
   
   @param aName name of the underlying HTTP request parameter. See 
   <a href="#NamingConvention">naming convention</a>. 
   @param aValueRegex regular expression for doing hard validation of the request 
   parameter value(s).
  */
  public static RequestParameter withRegexCheck(String aName, String aValueRegex){
    return new RequestParameter(aName, aValueRegex);    
  }
  
  /**
   Constructor for a <a href="#FileUpload">file upload</a> request parameter. 
   
   @param aName name of the underlying HTTP request parameter. See 
   <a href="#NamingConvention">naming convention</a>. 
  */
  public static RequestParameter forFileUpload(String aName){
    return new RequestParameter(aName); 
  }
  
  /** Return the request parameter name.  */
  public String getName(){
    return fName;
  }
   
  /**
   Return the regular expression associated with this <tt>RequestParameter</tt>. 
   
   <P>This regular expression is used to perform 
   <a href="ApplicationFirewall.html#HardValidation">hard validation</a> of this parameter's value(s).
   
   <P>This method will return <tt>null</tt> only for <a href="#FileUpload">file upload</a> parameters.
  */
  public Pattern getRegex(){
    return fRegex;
  }
  
  /**
   Return <tt>true</tt> only if {@link #forFileUpload} was used to build this object. 
  */
  public boolean isFileUploadParameter() {
    return ! fIsRegularParameter;
  }
  
  /**
   Return <tt>true</tt> only if <tt>aRawParamValue</tt> satisfies the regular expression 
   {@link #getRegex()}, <em>or</em> if this is a <a href="#FileUpload">file upload</a> 
   request parameter.
   
   <P>Always represents a <a href="ApplicationFirewall.html#HardValidation">hard validation</a>, not a 
   soft validation.
  */
  public boolean isValidParamValue(String aRawParamValue){
    boolean result = false;
    if ( isFileUploadParameter() ){
      result = true;   
    }
    else {
      result = Util.matches(fRegex, aRawParamValue);
    }
    return result;
  }
  
  @Override public boolean equals(Object aThat){
    if (this == aThat) return true;
    if ( !(aThat instanceof RequestParameter) ) return false;
    RequestParameter that = (RequestParameter)aThat;
    return ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
  }
  
  @Override public int hashCode(){
    return ModelUtil.hashCodeFor(getSignificantFields());
  }

  /** Intended for debugging only.  */
  @Override public String toString() {
    String result = null;
    if ( isFileUploadParameter() ) {
      result = "Name[File Upload]: " + fName;  
    }
    else {
      result = "Name:" + fName + " Regex:" + fRegex;  
    }
    return result;
  } 
  
  // PRIVATE  
  private final String fName;
  private Pattern fRegex; //Patterns are immutable
  private final boolean fIsRegularParameter;
  
  /**
   Constructor for a <a href="#Regular">"regular"</a> request parameter. 
   
   @param aName name of the underlying HTTP request parameter. See 
   <a href="#NamingConvention">naming convention</a>. 
   @param aRegex regular expression for doing hard validation of the request 
   parameter value(s).
  */
  private RequestParameter(String aName, String aRegex) { 
    this(aName, Pattern.compile(aRegex));
    validateState();
  }
   
  /**
   Constructor for a <a href="#Regular">"regular"</a> request parameter.
    
   @param aName name of the underlying HTTP request parameter. See 
   <a href="#NamingConvention">naming convention</a>. 
   @param aRegex regular expression for doing hard validation of the request 
   parameter value(s).
  */
  private RequestParameter(String aName, Pattern aRegex) {
    fName = aName;
    fRegex = aRegex;
    fIsRegularParameter = true;
    validateState();
  }
  
  /**
   Constructor for a <a href="#FileUpload">file upload</a> request parameter.
    
   @param aName name of the underlying HTTP request parameter. See 
   <a href="#NamingConvention">naming convention</a>. 
  */
  private RequestParameter(String aName) {
    fName = aName;
    fIsRegularParameter = false;
    validateState();
  }
 
  private void validateState(){
    //use the model ctor exception only to gather errors together
    //it is never actually thrown.
    ModelCtorException ex = new ModelCtorException();
    if ( ! Util.textHasContent(fName) ){
      ex.add("Name must have content.");
    }
    if ( fIsRegularParameter && (fRegex == null || ! Util.textHasContent(fRegex.pattern()) ) ){
      ex.add("For regular request parameters, regex pattern must be present.");
    }
    if ( ! fIsRegularParameter && fRegex != null  ){
      ex.add("For file upload parameters, regex pattern must be null.");
    }
    if ( ex.isNotEmpty() ) {
      throw new IllegalArgumentException(ex.getMessages().toString());
    }
  }
  
  private Object[] getSignificantFields(){
    return new Object[] {fName, fRegex};
  }
}
