package hirondelle.web4j.model;

import hirondelle.web4j.model.Id;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.ModelUtil;
import hirondelle.web4j.model.Check;
import hirondelle.web4j.security.SafeText;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 An item in a code table.
 
 <P>Here, a value in a code table is modeled with one required item (text), and four optional items 
 (id, short form, long form, and order index). The id is optional, since for 'add' operations it is not yet 
 specified. This class is offered as a convenience for implementing Code Tables. 
 Applications are not required to use it. 
 
 <P>Please see the example application for an example of one way of implementing code tables.
 
 <h3>Code Tables</h3>
 <P>A code table is an informal term describing a set of related values. It resembles  
 an enumeration. Simple examples :
<ul>
 <li>geographical divisions - countries, states or provinces
 <li>list of accepted credit cards - Mastercard, Visa, and so on
 <li>the account types offered by a bank - chequing, savings, and so on
</ul>

 Other important aspects of code tables :
 <ul>
 <li>most applications use them.
 <li>they often appear in the user interface as drop-down <tt>SELECT</tt> controls.
 <li>they usually don't change very often.
 <li>they may have a specific sort order, unrelated to alphabetical ordering. 
 <li>it is almost always desirable that the user-visible text attached to a code be unique (in a given code table), 
 since the user will usually have no means to distinguish the identical items. 
 <li>it is often desirable to present the same code in different ways, according to context.
 For example, report listings may present codes in an abbreviated style, while a tool-tips may present a 
 codes in a more verbose style, to explain their meaning.
 <li>they are usually closely related to various foreign keys in the database.
 </ul>
 
 <h3>Underlying Data</h3> 
 Code tables may be implemented in various ways, including 
 <ul>
 <li>database tables constructed explicitly for that purpose 
 <li>database tables that have already been constructed to hold problem domain items. That is, 
 data from an existing table may be extracted in a shortened form, to be used as a code table.
 <li>simple in-memory data. For example, a <tt>1..10</tt> rating system might use simple <tt>Integer</tt>
 objects created upon startup. 
 </ul>
 
 <h3>Avoiding Double-Escaping</h3>
 This class uses {@link SafeText}, which escapes special characters. 
 When rendering a <tt>Code</tt> object in a JSP, some care must be taken to ensure that
 special characters are not mistakenly escaped <em>twice</em>. 
 
 <P>In a single language app, it's usually safe to render a <tt>Code</tt> by simply using <tt>${code}</tt>. This 
 calls {@link #toString()}, which returns escaped text, safe for direct rendering in a JSP.
 
 <P>In a multilingual app, however, the various translation tags 
 (<tt>&lt;w:txt&gt;, &lt;w:txtFlow&gt;, &lt;w:tooltip&gt;</tt>) <em>already escape special characters</em>.
 So, if a translation tag encounters a <tt>Code</tt> somewhere its body, the <tt>Code</tt> must be in 
 an <em>unescaped</em> form, otherwise it wil be escaped <em>twice</em>, which undesirable.  
 <span class='highlight'>In a multilingual app, you should usually render a <tt>Code</tt> using <tt>${code.text.rawString}</tt>.</span> 
 
 <P>This asymmetry between single-language and many-language apps is somewhat displeasing, and 
 constitutes a pitfall of using this class. If desired, you could define an alternate <tt>Code</tt> class 
 whose <tt>toString</tt> returns a <tt>String</tt> instead of {@link SafeText}. 
*/
public final class Code implements Serializable {

  /**
   Full constructor.
   
   @param aId underlying database identifier for the code value. Optional, 1..50 characters.
   @param aText default text for presentation to the end user. Required, 1..50 characters.
   @param aShortText short form for text presented to the end user. This item is useful for terse 
   presentation in reports, often as an abbreviation of one or two letters. Optional, 1..10 characters.
   @param aLongText long form for text presented to the user. For example, this may be a description 
   or definition of the meaning of the code. Optional, 1..200 characters.
   @param aOrderIdx defines the order of appearance of this item in a listing. Optional, range 1..1000.
   This item is used to provide explicit control over the order of appearance of items as presented to 
   the user, and will often be related to an <tt>ORDER BY</tt> clause in an SQL statement.
  */
  public Code(Id aId, SafeText aText, SafeText aShortText, SafeText aLongText, Integer aOrderIdx) throws ModelCtorException {
    fId = aId;
    fText = aText;
    fShortText = aShortText;
    fLongText = aLongText;
    fOrderIdx = aOrderIdx;
    validateState();
  }
   
  /** As in the full constructor, but without a short description, long description, or an order index.  */
  public Code(Id aId, SafeText aText) throws ModelCtorException {
    fId = aId;
    fText = aText;
    fShortText = null;
    fLongText = null;
    fOrderIdx = null;
    validateState();
  }
  
  /** As in the full constructor, but without a long description or order index.  */
  public Code(Id aId, SafeText aText, SafeText aShortText) throws ModelCtorException {
    fId = aId;
    fText = aText;
    fShortText = aShortText;
    fLongText = null;
    fOrderIdx = null;
    validateState();
  }
  
  /** As in the full constructor, but without an order index.  */
  public Code(Id aId, SafeText aText, SafeText aShortText, SafeText aLongText) throws ModelCtorException {
    fId = aId;
    fText = aText;
    fShortText = aShortText;
    fLongText = aLongText;
    fOrderIdx = null;
    validateState();
  }
  
  /** Return the Id passed to the constructor. */
  public Id getId() { return fId;  }
  /** Return the Text passed to the constructor. */
  public SafeText getText() {  return fText;  }
  /** Return the Short Text passed to the constructor. */
  public SafeText getShortText() {  return fShortText;  }
  /** Return the Long Text passed to the constructor. */
  public SafeText getLongText() {  return fLongText;  }
  /** Return the Order Index passed to the constructor. */
  public Integer getOrderIdx() {  return fOrderIdx; }
  
  /** 
   Returns {@link #getText()}.toString().
   
   <P>This is the most user-friendly form of a code, and is useful for rendering in JSPs.
  */
  @Override public String toString(){
    return fText.toString();
  }
  
  @Override public boolean equals(Object aThat){
    Boolean result = ModelUtil.quickEquals(this, aThat);
    if ( result == null ){
      Code that = (Code) aThat;
      result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
    }
    return result;    
  }
  
  @Override public int hashCode() {
    if ( fHashCode == 0 ) {
      fHashCode = ModelUtil.hashCodeFor(getSignificantFields());
    }
    return fHashCode;
  }
  
  // PRIVATE 
  
  /** @serial */
  private final Id fId;
  /** @serial */
  private final SafeText fText;
  /** @serial */
  private final SafeText fShortText;
  /** @serial */
  private final SafeText fLongText;
  /** @serial */
  private final Integer fOrderIdx;
  /** @serial */
  private int fHashCode;
  
  /**
   For evolution of this class, see Sun guidelines : 
   http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/version.html#6678 
  */
  private static final long serialVersionUID = 8856876119383545215L;

  private Object[] getSignificantFields(){
    return new Object[] {fId, fText, fShortText, fLongText, fOrderIdx};
  }
  
  private void validateState() throws ModelCtorException {
    ModelCtorException ex = new ModelCtorException();
    if ( ! Check.optional(fId, Check.min(1), Check.max(50))) {
      ex.add("Code Id is optional, 1..50 chars.");
    }
    if ( ! Check.required(fText, Check.range(1,50))) {
      ex.add("Text is required, 1..50 chars.");
    }
    if ( ! Check.optional(fShortText, Check.range(1,10))) {
      ex.add("Short Text is optional, 1..10 chars.");
    }
    if ( ! Check.optional(fLongText, Check.range(1,200))) {
      ex.add("Long Text is optional, 1..200 chars.");
    }
    if ( ! Check.optional(fOrderIdx, Check.range(1,1000))) {
      ex.add("Order Idx is optional, 1..1000.");
    }
    if ( ! ex.isEmpty() ) throw ex;
  }

  /**
   Always treat de-serialization as a full-blown constructor, by
   validating the final state of the de-serialized object.
  */
  private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException, ModelCtorException {
    //always perform the default de-serialization first
    aInputStream.defaultReadObject();
    //make defensive copy of mutable fields (none here)
    //ensure that object state has not been corrupted or tampered with maliciously
    validateState();
 }

  /**
   This is the default implementation of writeObject.
   Customise if necessary.
  */
  private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
    //perform the default serialization for all non-transient, non-static fields
    aOutputStream.defaultWriteObject();
  }
}
