package hirondelle.web4j.ui.translate;

import java.util.Locale;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import javax.servlet.jsp.JspException;

import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.ui.tag.TagHelper;
import hirondelle.web4j.util.EscapeChars;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.util.Consts;

/**
 Custom tag for translating base language text (or a "coder key") into a localized form, and applying 
 "wiki-style" formatting.
 
 <P>This tag uses {@link hirondelle.web4j.ui.translate.Translator} and 
 {@link hirondelle.web4j.request.LocaleSource} to localize text.
 
 <P>There are several use cases for this tag. In general, the attributes control: 
 <ul>
 <li><a href="#BaseText">specifying base text</a> to be translated
 <li><a href="#WikiStyleFormatting">formatting</a> of the translated result 
 <li><a href="#TurningOffTranslation">turning off translation</a> altogether
 </ul> 
 
 <P><b><a name="BaseText">Specifying Base Text</a></b>
 <P>The base text may be specified simply as the tag body, or as the <tt>value</tt> attribute.
 <P>Example 1 : <br>
 <PRE>
 &lt;w:txt&gt;
  Of prayers, I am the prayer of silence.
  Of things that move not, I am the Himalayas.
 &lt;/w:txt&gt;
 </PRE>
 
 Example 2 uses a "coder key" : <br>
 <PRE>
 &lt;w:txt value="quotation.from.bhagavad.gita" /&gt;
 </PRE>
 
 <P>This second form is intended especially for translating items appearing 
 inside a tag.
  
 <P>These two use cases are mutually exclusive : either a body must be specified, or 
 the <tt>value</tt> attribute must be specified, <em>but not both</em>.
 
 <P>Here is another example, combining the two styles : 
 <PRE>
 &lt;span title='&lt;w:txt value="quotation.from.bhagavad.gita" /&gt;'&gt;
  &lt;w:txt&gt;
   Of prayers, I am the prayer of silence.
   Of things that move not, I am the Himalayas.
  &lt;/w:txt&gt;
 &lt;/span&gt;
 </PRE>
 
 <P>In either case, the item to be translated may be either some text in the 
 application's base language, or it may be a 'coder key' (see {@link Translator}).
 
 <P><a name="WikiStyleFormatting"><b>Formatting of the Result</b>
 <P>By default, this tag will escape all special characters using {@link EscapeChars#forHTML(String)}.
 Occasionally, it is desirable to allow some limited formatting of text input by the user. Even simple  
 effects such as bold and italic can measurably increase legibility and clarity, and allowing links 
 is also very useful. Most wikis allow such simple formatting. This tag uses the following special 
 characters to denote various effects :
<ul>
 <li> *bold* for <b>bold</b> (needs intial space before first '*')
 <li> _italic_ for <em>italic</em> (needs initial space before first '_')
 <li>^preserve formatting^ for preserving whitespace (&lt;PRE&gt;)
 <li>~~~~ on an otherwise blank line for a horizontal rule
 <li>[link:http:\\www.javapractices.com\Topic1.cjp enumerations] produces this link: <a href="http:\\www.javapractices.com\Topic1.cjp">enumerations</a> 
 <li>one or more empty lines for a paragraph (&lt;P&gt;)
 <li>a bar | at the end of a line for a line break (&lt;BR&gt;)
 <li>a line starting with ' ~ ' for a bullet entry in a list
</ul>

 <P>Example 3 has wiki style formatting:
 <PRE>
 &lt;w:txt wikiMarkup="true"&gt;
 Default Locale |   
 _Not all Locales are treated equally_ : there *must* be ... 
 &lt;/w:txt&gt;
 </PRE>
 
 Is rendered as :
<P>Default Locale<br>
<em>Not all Locales are treated equally</em> : there <b>must</b> be ...
  
 <P>To allow the above rules to be interpreted as HTML by this tag, {@link #setWikiMarkup(boolean)} to <tt>true</tt>.
 
 <P><b><a name="TurningOffTranslation">Turning Off Translation</a></b>
 <P>There are two cases in which it is useful to turn off translation altogether:
<ul>
 <li>using only the formatting services of this tag. For example, a message board application may want to 
 provide basic wiki-style formatting, without any translation. 
 <li>as a workaround for database issues regarding large text. Sometimes databases treat large 
 text differently from small text. For example in MySQL, it is not possible for a <tt>TEXT</tt> field to be  
 assigned a <tt>UNIQUE</tt> constraint. This split between large text and small text can be a problem, since 
 it may mean that blocks of text are treated differently simply according to their length, and workarounds for 
 large blocks of text are needed.   
</ul>  
 
 <P>Example 4 has wiki style formatting for untranslated user input:
 <PRE>
 &lt;w:txt wikiMarkup="true" translate="false"&gt;
   ...render some user input with wiki style formatting...
 &lt;/w:txt&gt;
 </PRE>

 <P>Example 5 has wiki style formatting for <em>large</em> untranslated text, hard-coded in the JSP.
 An example of such text may be an extended section of "help" information :
 <PRE>
 &lt;w:txt locale="en"&gt;
   ..<em>large</em> amount of hard-coded text in English...
 &lt;/w:txt&gt;
 
 &lt;w:txt locale="fr"&gt;
   ..<em>large</em> amount of hard-coded text in French...
 &lt;/w:txt&gt;
 </PRE>
 
 <em>The above style is outside the usual translation mechanism.</em> It does not use the configured 
 {@link Translator}. It does not translate its content at all. Rather, it will echo the tag content only 
 when the specified <tt>locale</tt> matches that returned by the {@link hirondelle.web4j.request.LocaleSource}.
 <span class="highlight">It is recommended that this style be used only when the above-mentioned problem regarding 
 database text size exists.</span> This style implicitly has <tt>translate="false"</tt>.
*/
public final class Text extends TagHelper {
  
  /**
   Set the item to be translated (optional).
   
   <P><tt>aTextAsAttr</tt> takes two forms : user-visible text in the base language, 
   or a coder key (known to the programmer, but not seen by the end user). See {@link Translator} 
   for more information.
   
   <P>If this attribute is set, then the tag must <em>not</em> have a body.
   
   @param aTextAsAttr must have content; this value is always trimmed by this method.
  */
  public void setValue(String aTextAsAttr){
    checkForContent("Value", aTextAsAttr);
    fTextAsAttr = aTextAsAttr.trim();
  }
  
  /** 
   Toggle translation on and off (optional, default <tt>true</tt>).
    
   <P>By default, text will be translated. An example of setting this item to <tt>false</tt> is a 
   discussion board, where users input in a single language, and 
   <a href="#WikiStyleFormatting">wiki style formatting</a> is desired. 
  
   <P>An example of rendering such text is :
   <PRE>
   &lt;w:txt translate="false" wikiMarkup="true"&gt;
     This is *bold*, and so on...
   &lt;/w:txt&gt;
   </PRE>
  */
  public void setTranslate(boolean aValue) {
    fIsTranslating = aValue;
  }
  
  /**
   Specify an explicit {@link Locale} (optional).
   
   <P><span class='highlight'>When this attribute is specified, this tag will never translate its content.</span> 
   Instead, this tag will simply <em>emit or suppress</em> its content, according to whether <tt>aLocale</tt> matches 
   that returned by {@link hirondelle.web4j.request.LocaleSource}. 
  */
  public void setLocale(String aLocale){
    fSpecificLocale = Util.buildLocale(aLocale);
    fIsTranslating = false;
  }

  /** 
   Allow <a href="#WikiStyleFormatting">wiki style formatting</a> to be used (optional, default <tt>false</tt>).
  */
  public void setWikiMarkup(boolean aValue){
    fHasWikiMarkup = aValue;
  }
  
  /** Validate attributes against each other.  */
  protected void crossCheckAttributes() {
    if( fSpecificLocale != null && fIsTranslating ){
      String message = "Cannot translate and specify a Locale at the same time. Page : " + getPageName();
      fLogger.severe(message);
      throw new IllegalArgumentException(message);
    }
  }

  /** See class comment. */
  @Override protected String getEmittedText(String aOriginalBody) throws JspException {
    if ( Util.textHasContent(fTextAsAttr) && Util.textHasContent(aOriginalBody) ){
      throw new JspException(
        "Please specify text (or key) to be translated as either value attribute or tag body, but not both." +
        " Value attribute: " +Util.quote(fTextAsAttr) + ". Tag body: " + Util.quote(aOriginalBody) + 
        " Page Name :" + getPageName() 
      );
    }
    
    String baseText = Util.textHasContent(fTextAsAttr) ? fTextAsAttr : aOriginalBody;
    String result = Consts.EMPTY_STRING;
    if(Util.textHasContent(baseText)){
      Locale locale = BuildImpl.forLocaleSource().get(getRequest());
      if(fIsTranslating){
        Translator translator = BuildImpl.forTranslator();
        result = translator.get(baseText, locale);
        fLogger.finest("Translating base text : " + Util.quote(baseText) + " using Locale " + locale  + " into " + Util.quote(result));
      }
      else {
        fLogger.finest("LocaleSource: " + locale + ", locale attribute: " + fSpecificLocale);
        if (fSpecificLocale == null || fSpecificLocale.equals(locale)){
          fLogger.finest("Echoing tag content (possibly adding formatting).");
          result = baseText;
        }
        else {
          fLogger.finest("Suppressing tag content.");
          result = Consts.EMPTY_STRING;
        }
      }
      result = processMarkup(result);
    }
    return result;
  }

  /**
   Translate the text into a result. Escapes characters, then optionally changes wiki markup into hypertext.  
   Made package-private for testing purposes. 
  */
  String processMarkup(String aText) {
    String result = aText;
    if(Util.textHasContent(result)){
      result = EscapeChars.forHTML(result);
      if( fHasWikiMarkup ){
        result = changePseudoMarkupToHTML(result);
      }
    }
    return result;
  }
  
  // PRIVATE 
  private String fTextAsAttr;
  private boolean fIsTranslating = true;
  private Locale fSpecificLocale;
  private boolean fHasWikiMarkup = false;
  private static final Logger fLogger = Util.getLogger(Text.class);
  
  /* 
   The regexes don't refer to the original '*' and so on. Rather, they refer to the 
   escaped versions thereof - their 'fingerprints', so to speak.
   
   Implementation Note:
   Seeing undesired wiki-formatting of _blah_ text in links. To fix, introduced second, small variation 
   for *blah* and _blah_, which matches to the beginning of the input.
   
   This is a rather hacky, and a more robust implmentation would use javacc.
  */
  
  private static final Pattern PSEUDO_LINK = Pattern.compile("&#091;link&#058;((\\S)*) ((.)+?)\\&#093;");
  private static final Pattern PSEUDO_BOLD = Pattern.compile("(?: &#042;)((?:.)+?)(?:&#042;)"); 
  private static final Pattern PSEUDO_BOLD_START_OF_INPUT = Pattern.compile("(?:^&#042;)((?:.)+?)(?:&#042;)"); 
  private static final Pattern PSEUDO_ITALIC = Pattern.compile("(?: &#095;)((?:.)+?)(?:&#095;)");
  private static final Pattern PSEUDO_ITALIC_START_OF_INPUT = Pattern.compile("(?:^&#095;)((?:.)+?)(?:&#095;)");
  private static final Pattern PSEUDO_CODE = Pattern.compile("(?:\\&#094;)((?:.)+?)(?:\\&#094;)", Pattern.MULTILINE | Pattern.DOTALL);
  private static final Pattern PSEUDO_HR = Pattern.compile("^(?:\\s)*&#126;&#126;&#126;&#126;(?:\\s)*$", Pattern.MULTILINE);
  private static final Pattern PSEUDO_PARAGRAPH = Pattern.compile("(^(?:\\s)*$)", Pattern.MULTILINE);
  private static final Pattern PSEUDO_LINE_BREAK = Pattern.compile("\\&#124;(?: )*$", Pattern.MULTILINE);
  private static final Pattern PSEUDO_LIST = Pattern.compile("^(?: )*(\\&#126;)(?: )", Pattern.MULTILINE);

  private String changePseudoMarkupToHTML(String aText){
    String result = null;
    result = addLink(aText);
    result = addBold(result);
    result = addBold2(result);
    result = addItalic(result);
    result = addItalic2(result);
    result = addLineBreak(result);
    result = addParagraph(result);
    result = addList(result);
    result = addCode(result);
    result = addHorizontalRule(result);
    return result;
  }
  
  private String addBold(String aText){
    Matcher matcher = PSEUDO_BOLD.matcher(aText);
    return matcher.replaceAll(" <b>$1</b>"); //extra space
  }

  private String addBold2(String aText){
    Matcher matcher = PSEUDO_BOLD_START_OF_INPUT.matcher(aText);
    return matcher.replaceAll("<b>$1</b>"); //extra space
  }
  
  private String addItalic(String aText){
    Matcher matcher = PSEUDO_ITALIC.matcher(aText);
    return matcher.replaceAll(" <em>$1</em>"); //extra space
  }

  private String addItalic2(String aText){
    Matcher matcher = PSEUDO_ITALIC_START_OF_INPUT.matcher(aText);
    return matcher.replaceAll("<em>$1</em>"); //extra space
  }
  
  private String addCode(String aText){
    Matcher matcher = PSEUDO_CODE.matcher(aText);
    return matcher.replaceAll("<PRE>$1</PRE>");
  }
  
  private String addHorizontalRule(String aText){
    Matcher matcher = PSEUDO_HR.matcher(aText);
    return matcher.replaceAll("<hr>");
  }

  private String addLink(String aText){
    Matcher matcher = PSEUDO_LINK.matcher(aText);
    return matcher.replaceAll("<a href='$1'>$3</a>");
  }

  private String addParagraph(String aText){
    Matcher matcher = PSEUDO_PARAGRAPH.matcher(aText);
    return removeInitialAndFinalParagraphs(matcher.replaceAll("<P>"));
  }

  /** Bit hacky - cannot find correct regex to take care of this cleanly. */
  private String removeInitialAndFinalParagraphs(String aText){
    String result = aText.trim();
    if (aText.startsWith("<P>")){
      result = result.substring(3);
    }
    if (aText.endsWith("<P>")){
      result = result.substring(0, result.length()-3);
    }
    return result;
  }
  
  private String addLineBreak(String aText){
    Matcher matcher = PSEUDO_LINE_BREAK.matcher(aText);
    return matcher.replaceAll("<BR>");
  }
  
  private String addList(String aText){
    Matcher matcher = PSEUDO_LIST.matcher(aText);
    return matcher.replaceAll("<br>&nbsp;&nbsp;&#8226; "); //another version of bull has a non-standard number
  }
}
