package hirondelle.web4j.action;

import hirondelle.web4j.model.ModelUtil;
import hirondelle.web4j.util.WebUtil;

/**
 <span class="highlight">Response page served to the user at the end of an {@link Action}.</span>
 
 <P>Identifies the page as a resource. Does not include its content, but is rather  
 a <em>reference</em> to the page. 
 
 <P>The {@link hirondelle.web4j.Controller} will either redirect or forward to the
 resource identified by {@link #toString}, according to the value of {@link #getIsRedirect}.
 The default value of {@link #getIsRedirect} varies according to constructor:
<ul>
 <li>{@link #ResponsePage(String)} defaults to a <em>redirect</em>
 <li>all other constructors default to a <em>forward</em>
</ul> 
 
 <P>These defaults are almost always the desired values. They can be changed, if 
 necessary, by calling {@link #setIsRedirect}. It's important to note that that method 
 returns a brand new object - it doesn't change the state of an existing object. 
 
 <P>See the <a href="http://www.javapractices.com/Topic181.cjp">forward-versus-redirect</a> 
 topic on javapractices.com for more information.
*/
public final class ResponsePage {

  /**
   Constructor which uses the WEB4J template mechanism.
   
   @param aTitle text of the <tt>&lt;TITLE&gt;</tt> tag to be presented in the 
   template page ; appended to <tt>aTemplateURL</tt> as a query parameter
   @param aBodyJsp body of the templated page, where most of the content of interest 
   lies ; appended to <tt>aTemplateURL</tt> as a query parameter
   @param aTemplateURL identifies the templated page which dynamically includes 
   <tt>aBodyJsp</tt> in its content. Some applications will have only a single 
   template for the entire application. 
  */
  public ResponsePage(String aTitle, String aBodyJsp, String aTemplateURL){
    this(aTitle, aBodyJsp, aTemplateURL, null);
  }  

  /** 
   Constructor which uses the WEB4J template mechanism, with a conventional template.
   <P>As in {@link #ResponsePage(String, String, String, Class)}, 
   but with the template URL taking the conventional value of <tt>'../Template.jsp'</tt>. 
  */
  public ResponsePage(String aTitle, String aBodyJsp, Class aRepresentativeClass) {
    this(aTitle, aBodyJsp, "../Template.jsp",  aRepresentativeClass);
  }
  
  /**
   Constructor which uses the WEB4J template mechanism.
   
   <P><span class="highlight">
   This constructor allows for an unusual but useful policy : placing JSPs in the 
   same directory as related code, under <tt>WEB-INF/classes</tt>, instead 
   of under the document root of the application.</span> 
   Such a style is beneficial since it allows all (or nearly all) items related 
   to a given feature to be placed in the same directory - JSPs, Action,
   Model Objects, Data Access Objects, and <tt>.sql</tt> files.
   This is the recommended style. It allows 
   <a href="http://www.javapractices.com/Topic205.cjp">package-by-feature</a>.
   
   @param aTitle text of the <tt>&lt;TITLE&gt;</tt> tag to be presented in the 
   template page ; appended to <tt>aTemplateURL</tt> as a query parameter
   @param aBodyJsp body of the templated page, where most of the content of interest 
   lies ; appended to <tt>aTemplateURL</tt> as a query parameter
   @param aTemplateURL identifies the templated page which dynamically includes 
   <tt>aBodyJsp</tt> in its content
   @param aClass class literal of any java class related to the given feature; the 
   <em>package</em> of this class will be used to construct the 'real' path to <tt>aBodyJsp</tt>, 
   as in '<tt>WEB-INF/classes/&lt;package-as-directory-path&gt;/&lt;aBodyJsp&gt;</tt>'. These  
   paths are completely internal, are known only to the {@link hirondelle.web4j.Controller}, and are 
   never visible to the user in the URL. 
  */
  public ResponsePage(String aTitle, String aBodyJsp, String aTemplateURL, Class<?> aClass){
    fIsRedirect = FOR_FORWARD;
    String url = WebUtil.setQueryParam(aTemplateURL, "TTitle", aTitle);
    url = WebUtil.setQueryParam(url, "TBody", getPathPrefix(aClass) + aBodyJsp);
    fURL = url;
    fIsBinary = false;
  }  
  
  /**
   Constructor for a non-templated response. 
  
   <P>This constructor does not use the WEB4J template mechanism.  
   This constructor is used both for response pages that require a redirect, and for 
   serving pages that do not use the templating mechanism.
   
   <P><tt>aURL</tt> identifies the resource which will be used by an {@link Action}
   as its destination page. 
   Example values for <tt>aURL</tt> :
   <ul>
   <li><tt>ViewAccount.do</tt> (suitable for a redirect)
   <li><tt>/ProblemHasBeenLogged.html</tt> (suitable for a forward to an item 
   which is <em>not</em> templated)
   </ul> 
   
   <P>This constructor returns a redirect. If a forward is desired, call {@link #setIsRedirect(Boolean)}.
  */
  public ResponsePage(String aURL) {
    this(aURL, FOR_REDIRECT);
  }
  
  /**
    Constructor for a non-templated response located under <tt>/WEB-INF/</tt>.
    
   <P><span class="highlight">
   This constructor allows for an unusual but useful policy : placing JSPs in the 
   same directory as related code, under <tt>WEB-INF/classes</tt>, instead 
   of under the document root of the application.</span> 
   Such a style is beneficial since it allows all (or nearly all) items related 
   to a given feature to be placed in the same directory - JSPs, Action,
   Model Objects, Data Access Objects, and <tt>.sql</tt> files.
   This is the recommended style. It allows 
   <a href="http://www.javapractices.com/Topic205.cjp">package-by-feature</a>.
    
   <P>This constructor defaults the response to a forward operation. 
    
   @param aJsp simple name of a JSP, as in 'view.jsp' 
   @param aClass class literal of any java class in the same package as the given JSP.
   The <em>package</em> of this class will be used to construct the 'real' path to the JSP, 
   as in <PRE>WEB-INF/classes/&lt;package-as-directory-path&gt;/&lt;aJsp&gt;</PRE>These  
   paths are completely internal, are known only to the {@link hirondelle.web4j.Controller}, and are 
   never visible to the user in the URL. 
   */
  public ResponsePage(String aJsp, Class<?> aClass){
    fIsRedirect = FOR_FORWARD;
    fURL = getPathPrefix(aClass) + aJsp;
    fIsBinary = false;
  }

  /**
   Factory method for binary responses. 
   An example of a binary reponse is serving a report in <tt>.pdf</tt> format.
   
   <P>With such <tt>ResponsePage</tt>s, the normal templating mechanism is not 
   available (since it is based on text), and no forward/redirect is performed by the 
   <tt>Controller</tt>. In essence, the <tt>Action</tt> becomes entirely responsible 
   for generating the response. 
   
   <P>When serving a binary response, your <tt>Action</tt> will typically : 
   <ul>
   <li>set the <tt>content-type</tt> response header.
   <li>generate the output stream containing the desired binary content.
   <li>close any resources, if necessary. For example, if generating a chart dynamically, then you 
   may need to recover resources used in the generation of an image.
   </ul>
  */
  public static ResponsePage withBinaryData(){
    return new ResponsePage();
  }
  
  /** Return <tt>true</tt> only if {@link #withBinaryData()} was called.  */
  public Boolean hasBinaryData(){
    return fIsBinary;
  }

  /**
   Return the URL passed to the constructor, with any query parameters added by 
   {@link #appendQueryParam} appended on the end.
  */
  @Override public String toString() { 
    return fURL;  
  } 

  /** See class comment. */
  public Boolean getIsRedirect(){
    return fIsRedirect;
  }
  
  /**
   Return a new <tt>ResponsePage</tt>, having the specified forward/redirect behavior.
   See class comment.
    
   <P>This method returns a new <tt>ResponsePage</tt> having the updated URL. 
   (This will ensure that <tt>ResponsePage</tt> objects are immutable.) 
  */
  public ResponsePage setIsRedirect(Boolean aValue){
    return new ResponsePage(fURL, aValue);    
  }

  /**
   Append a query parameter to the URL of a <tt>ResponsePage</tt>.
   
   <P>This method returns a new <tt>ResponsePage</tt> having the updated URL. 
   (This will ensure that <tt>ResponsePage</tt> objects are immutable.) 
   <P>This method uses {@link WebUtil#setQueryParam}. 
  */
  public ResponsePage appendQueryParam(String aParamName, String aParamValue){
    String newURL = WebUtil.setQueryParam(fURL, aParamName, aParamValue);
    return new ResponsePage(newURL, fIsRedirect);
  }
  
  @Override public boolean equals(Object aThat){
    Boolean result = ModelUtil.quickEquals(this, aThat);
    if ( result == null ){
      ResponsePage that = (ResponsePage) aThat;
      result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
    }
    return result;    
  }
  
  @Override public int hashCode(){
    return ModelUtil.hashCodeFor(getSignificantFields());
  }
   
  // PRIVATE 
  private final String fURL;
  private final Boolean fIsRedirect;
  private final Boolean fIsBinary;
  private static final Boolean FOR_REDIRECT = Boolean.TRUE;
  private static final Boolean FOR_FORWARD = Boolean.FALSE;
  
  private String getPathPrefix(Class aClass){
    String result = "";
    if ( aClass != null ){
      result = "/WEB-INF/classes/" + aClass.getPackage().getName().replace('.', '/') + "/"; 
    }
    return result;
  }
  
  /** Private constructor. */
  private ResponsePage(String aURL, Boolean aIsRedirect){
    fIsRedirect = aIsRedirect;
    fURL = aURL;
    fIsBinary = false;
  }

  /** Private constructor for binary responses. */
  private ResponsePage(){
    fIsRedirect = FOR_FORWARD; //not really used
    fURL = null; //not usd
    fIsBinary = true;
  }
  
  private Object[] getSignificantFields(){
    return new Object[] {fURL, fIsRedirect, fIsBinary};
  }
}
