package hirondelle.web4j.ui.translate;

import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Locale;
import java.util.regex.*;

import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.request.LocaleSource;
import hirondelle.web4j.ui.tag.TagHelper;
import hirondelle.web4j.util.EscapeChars;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.util.Regex;

/**
 Custom tag for translating regular text flow in large sections of a web page. 
 
 <P><span class="highlight">This tag treats every piece of free flow text delimited by a  
 tag as a unit of translatable base text</span>, and passes it to {@link Translator}. 
 That is, all tags are treated as <em>delimiters</em> of units of translatable text.
  
 <P>This tag is suitable for translating most, but not all, of the 
 regular text flow in a web page. <span class="highlight">It is suitable for translating 
 markup that contains short, isolated snippets of text, that have no "structure", and 
 no dynamic data</span>, such as the labels in a form, the column headers in a listing, 
 and so on. (For many intranet applications, this 
 makes up most of the free flow text appearing in the application.) Instead of using many 
 separate <tt>&lt;w:txt&gt;</tt> {@link Text} tags to translate each item one by one,
 a single <tt>&lt;w:txtFlow&gt;</tt> tag can often be used to do the same thing in a single step.
 
 <P><span class="highlight">Using this class has two strong advantages</span> : 
<ul>
 <li>the effort needed to internationalize a page is greatly reduced
 <li>the markup will be significantly easier to read and maintain, since most of the free flow text 
 remains unchanged from the single-language case
</ul>
 
 <P>
 This tag is <em>not suitable</em> when the base text to be translated : 
<ul>
 <li>contains markup
 <li>has dynamic data of any sort
 <li>contains a <tt>TEXTAREA</tt> with a <em>non-empty</em> body. Such text will be seen as a translatable 
 unit, which is usually undesired, since such text is usually not fixed, but dynamic (that is, from the database). 
 (To avoid this, simply nest this tag <em>inside</em> the <tt>&lt;w:populate&gt;</tt> tag surrounding the 
 form that contains the <tt>TEXTAREA</tt>. This ensures that the population is not affected by the action of this 
 tag.)
</ul>
  
 <P>For example, given this text containing markup :
 <PRE>The &lt;EM&gt;raison-d'etre&lt;/EM&gt; for this...</PRE>
 then this tag will split the text into three separate pieces, delimited by the <tt>EM</tt> tags.
 Then, each piece will be translated. For such items, this is almost always undesirable. Instead, 
 one must use a <tt>&lt;w:txt&gt;</tt> {@link Text} tag, which can treat such items as 
 a single unit of translatable text, without chopping it up into three pieces. 
 
 <P><b>Example</b><br>
 Here, all of the <tt>LABEL</tt> tags in this form will have their content translated by the 
 <tt>&lt;w:txtFlow&gt;</tt> tag :
 <PRE>
&lt;w:populate style='edit' using="myUser"&gt;
&lt;w:txtFlow&gt;
&lt;form action='blah.do' method='post' class="user-input"&gt;
&lt;table align="center"&gt;
&lt;tr&gt;
 &lt;td&gt;
  &lt;label class="mandatory"&gt;Email&lt;/label&gt;
 &lt;/td&gt;
 &lt;td&gt;
  &lt;input type="text" name="Email Address" size='30'&gt;
 &lt;/td&gt; 
&lt;/tr&gt;

&lt;tr&gt;
 &lt;td&gt;
  &lt;label&gt;Age&lt;/label&gt;
 &lt;/td&gt;
 &lt;td&gt;
  &lt;input type="text" name="Age" size="30"&gt;
 &lt;/td&gt; 
&lt;/tr&gt;

&lt;tr&gt;
 &lt;td&gt;
  &lt;label&gt;Desired Salary&lt;/label&gt;
 &lt;/td&gt;
 &lt;td&gt;
  &lt;input type="text" name="Desired Salary" size="30"&gt;
 &lt;/td&gt; 
&lt;/tr&gt;

&lt;tr&gt;
 &lt;td&gt;
  &lt;label&gt; Birth Date &lt;/label&gt;
 &lt;/td&gt;
 &lt;td&gt;
  &lt;input type="text" name="Birth Date" size="30"&gt;
 &lt;/td&gt; 
&lt;/tr&gt;

&lt;tr&gt;
 &lt;td&gt;
  &lt;input type='submit' value='UPDATE'&gt; 
 &lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/form&gt;
&lt;/w:txtFlow&gt;
&lt;/w:populate&gt;
</PRE>
*/
public final class TextFlow extends TagHelper {

  /**
   By default, this tag will escape any special characters appearing in the 
   text flow, using {@link EscapeChars#forHTML(String)}. To change that default  
   behaviour, set this value to <tt>false</tt>.
   
   <P><span class="highlight">Exercise care that text is not doubly escaped.</span> 
   For instance, if the text already contains 
   character entities, and <tt>setEscapeChars</tt> is true, then the text <tt>&amp;amp;</tt>
   will be emitted by this tag as <tt>&amp;amp;amp;</tt>, for example.
  */
  public void setEscapeChars(boolean aValue){
    fEscapeChars = aValue;
  }
  
  /**
   Translate each piece of free flow text appearing in <tt>aOriginalBody</tt>.
   
   <P>Each piece of text is delimited by one or more tags, and is translated using the configured 
   {@link Translator}. Leading or trailing white space is preserved.
  */
  @Override protected String getEmittedText(String aOriginalBody) {
    final StringBuffer result = new StringBuffer();
    final StringBuffer snippet = new StringBuffer();
    boolean isInsideTag = false;
    
    final StringCharacterIterator iterator = new StringCharacterIterator(aOriginalBody);
    char character =  iterator.current();
    while (character != CharacterIterator.DONE ){
      if (character == '<') {
        doStartTag(result, snippet, character);
        isInsideTag = true;
      }
      else if (character == '>') {
        doEndTag(result, character);
        isInsideTag = false;
      }
      else {
        doRegularCharacter(result, snippet, isInsideTag, character);
      }
      character = iterator.next();
    }
    if( Util.textHasContent(snippet.toString()) ) {
      appendTranslation(snippet, result);
    }
    return result.toString();
  }
  
  // PRIVATE //
  static Pattern TRIMMED_TEXT = Pattern.compile("((?:\\S(?:.)*\\S)|(?:\\S))");
  
  private boolean fEscapeChars = true;
  private LocaleSource fLocaleSource = BuildImpl.forLocaleSource();
  private Translator fTranslator = BuildImpl.forTranslator();
  
  private void doStartTag(StringBuffer aResult, StringBuffer aSnippet, char aCharacter) {
    if (Util.textHasContent(aSnippet.toString()) ){ 
      appendTranslation(aSnippet, aResult);
    }
    else {
      //often contains just spaces and/or new lines, which are just appended
      aResult.append(aSnippet.toString());
    }
    aSnippet.setLength(0);
    aResult.append(aCharacter);
  }
  
  private void doEndTag(StringBuffer aResult, char aCharacter) {
    aResult.append(aCharacter);
  }
  
  private void doRegularCharacter(StringBuffer aResult, StringBuffer aSnippet, boolean aIsInsideTag, char aCharacter) {
    if( aIsInsideTag ){
      aResult.append(aCharacter);
    }
    else {
      aSnippet.append(aCharacter);
    }
    //fLogger.fine("Snippet : " + aSnippet);
  }

  /**
   The snippet may contain leading or trailing white space, or control chars (new lines), 
   which must be preserved. 
  */
  private void appendTranslation(StringBuffer aSnippet, StringBuffer aResult){
    if( Util.textHasContent(aSnippet.toString()) ) {
      StringBuffer translatedSnippet = new StringBuffer();
      
      Matcher matcher = TRIMMED_TEXT.matcher(aSnippet.toString());
      while ( matcher.find() ) {
        matcher.appendReplacement(translatedSnippet, getReplacement(matcher));
      }
      matcher.appendTail(translatedSnippet);
      
      if( fEscapeChars ) {
        aResult.append(EscapeChars.forHTML(translatedSnippet.toString()));
      }
      else {
        aResult.append(translatedSnippet);
      }
    }
    else {
      aResult.append(aSnippet.toString());
    }
  }
  
  private String getReplacement(Matcher aMatcher){
    String result = null;
    String baseText = aMatcher.group(Regex.FIRST_GROUP);
    if (Util.textHasContent(baseText)){
      Locale locale = fLocaleSource.get(getRequest());
      result = fTranslator.get(baseText, locale);
    }
    else {
      result = baseText;
    }
    return EscapeChars.forReplacementString(result);
  }
}
