package hirondelle.web4j.ui.tag;

import java.util.regex.*;
import java.util.logging.*;
import hirondelle.web4j.Controller;
import hirondelle.web4j.util.EscapeChars;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.util.Regex;

/**
 Suppress self-linking anchor tags (links), where a 
 link has the current page as its destination.

 <P>Self-linking is usually considered to be poor style, since the user is simply 
 led back to the current page. 
 
 <P>Rendering links to the current page in a distinct manner has two benefits :
<ul>
 <li>the user is prevented from following a useless link
 <li>the user can see 'where they are' in a menu of items
</ul>
 
 <P>This tag provides two distinct techniques for identifying self-linking: 
<ul>
 <li>the current URI <em>ends with</em> the link's <tt>href</tt> target. 
 This is the default mechanism. The 'ends with' is used since the current URI 
 (stored by the <tt>Controller</tt> in request scope) is absolute, while most 
 <tt>HREF</tt> attributes contain relative links.    
 <li>the trimmed link text (the body of the anchor tag) matches the <tt>TTitle</tt> request parameter 
 used by the WEB4J templating mechanism.
</ul>
 
 <P>If these policies are inadequate, then {@link #isSelfLinkingByHref(String, String)} and 
 {@link #isSelfLinkingByTitle(String, String)} may be overridden as desired. 
 
 <P>Example use case.
<PRE>
&lt;w:highlightCurrentPage styleClass="highlight"&gt;
  &lt;a href='ShowHomePage.do'&gt;Home&lt;/a&gt;
  &lt;a href='ShowSearch.do'&gt;Search&lt;/a&gt;
  &lt;a href='ShowContact.do'&gt;Contact&lt;/a&gt;
&lt;/w:highlightCurrentPage&gt;
</PRE>
 If the current URI is
 <PRE>
 http://www.blah.com/fish/main/home/ShowHomePage.do
 </PRE>
 then the output of this tag will be
<PRE>
  &lt;span class="highlight"&gt;Home&lt;/span&gt;
  &lt;a href='ShowSearch.do'&gt;Search&lt;/a&gt;
  &lt;a href='ShowContact.do'&gt;Contact&lt;/a&gt;
</PRE>
 If the <tt>styleClass</tt> attribute is not used, then the output would simply be :
<PRE>
  Home
  &lt;a href='ShowSearch.do'&gt;Search&lt;/a&gt;
  &lt;a href='ShowContact.do'&gt;Contact&lt;/a&gt;
</PRE>

 <P>This final example uses the <tt>TTitle</tt> mechanism :
<PRE>
&lt;w:highlightCurrentPage useTitle="true"&gt;
  &lt;a href='ShowHomePage.do'&gt;Home&lt;/a&gt;
  &lt;a href='ShowSearch.do'&gt;Search&lt;/a&gt;
  &lt;a href='ShowContact.do'&gt;Contact&lt;/a&gt;
&lt;/w:highlightCurrentPage&gt;
</PRE>
 For a page with <tt>TTitle</tt> parameter as <tt>'Home'</tt>, the output of this tag 
 would be as above:
<PRE>
  Home
  &lt;a href='ShowSearch.do'&gt;Search&lt;/a&gt;
  &lt;a href='ShowContact.do'&gt;Contact&lt;/a&gt;
</PRE>

 <P><em>Nuisance restriction</em> : the <tt>href</tt> attribute must appear as the 
 first attribute in the &lt;A&gt; tags.
*/
public class HighlightCurrentPage extends TagHelper {

  /**
   Name of the Cascading Style Sheet class used for highlighting text related to the current 
   page (optional).
  
   <P>For example, the link text could be highlighted in yellow in order 
   to make it stand out more clearly from the other links. If this attribute is not 
   specified, then the link text is simply presented as is, with no special styling.
  
   @param  aCascadingStyleSheetClassName satisfies {@link Util#textHasContent(String)}.
  */
  public final void setStyleClass(String aCascadingStyleSheetClassName){
    checkForContent("StyleClass", aCascadingStyleSheetClassName);
    fHighlightClass = aCascadingStyleSheetClassName;
  }
  
  /**
   Use the <tt>TTitle</tt> request parameter passed to this page.
   
   <P>Optional. Default is <tt>false</tt>. If set to <tt>true</tt>, 
   then the default mechanism is overridden, and self-linking pages will be identified 
   by comparing the link text to the page <tt>TTitle</tt> parameter. (The <tt>TTitle</tt>
   parameter is used in the WEB4J page templating mechanism).
  */
  public final void setUseTitle(Boolean aUseTitle) {
    fUseTTitle = aUseTitle;
  }
  
  /**
   Alter any links in the tag body which refer to the current page.
   
   <P>See examples in the class comment.
  */
  @Override protected final String getEmittedText(String aOriginalBody){
    StringBuffer result = new StringBuffer();
    Matcher matcher = LINK_PATTERN.matcher(aOriginalBody);
    while ( matcher.find() ) {
      matcher.appendReplacement(result, getReplacement(matcher));
    }
    matcher.appendTail(result);
    return result.toString();
  }

  /**
   Determine if self-linking is present, using the current URI and the link's <tt>HREF</tt> target.
    
   <P>Overridable default implementation that simply checks if <tt>aCurrentURI</tt> <em>ends with</em> 
   <tt>aHrefTarget</tt>. In addition, <tt>aCurrentURI</tt> is passed through {@link EscapeChars#forHrefAmpersand(String)}
   before the comparison is made. This ensures the text will match a valid <tt>HREF</tt> attribute. 
  */
  protected boolean isSelfLinkingByHref(String aCurrentURI, String aHrefTarget){
    String currentURI = EscapeChars.forHrefAmpersand(aCurrentURI);
    return currentURI.endsWith(aHrefTarget);
  }
  
  /**
   Determine if self-linking is present, using the link text and the <tt>TTitle</tt> request parameter.
   
   <P>Overridable default implementation that checks if the trimmed <tt>aLinkText</tt> 
   (the body of the &lt;A&gt; tag) and <tt>aTTitle</tt> are the same.
  */
  protected boolean isSelfLinkingByTitle(String aLinkText, String aTTitle){
    return aLinkText.trim().equals(aTTitle);
  }
  
  /** Used for offline unit testing only.  */
  void testSetCurrentURI(String aCurrentURI){
    fTestingCurrentURI = aCurrentURI;
  }
  
  /** Used for offline unit testing only. */
  void testSetTitleParam(String aTTitleParam){
    fTestingTitleParam = aTTitleParam;  
  }
  
  // PRIVATE //
  private String fHighlightClass;
  private Boolean fUseTTitle = Boolean.FALSE;
  
  private String fTestingCurrentURI;
  private String fTestingTitleParam;

  /**
   Group 0 - entire link
   Group 1 - href attribute
   Group 2 - link text (untrimmed)
  */
  private static final Pattern LINK_PATTERN = Pattern.compile( 
    "<a" +  "(?:\\s)* href=" + Regex.QUOTED_ATTR + Regex.ALL_BUT_END_OF_TAG + Regex.END_TAG + "(" + Regex.ALL_BUT_END_OF_TAG + ")" + "</a>",
    Pattern.CASE_INSENSITIVE
  );
  
  private static final int WHOLE_LINK = 0;
  private static final int HREF_TARGET = 1;
  private static final int LINK_TEXT = 2;
  private static final String TITLE_PARAM = "TTitle";
  private static final Logger fLogger = Util.getLogger(HighlightCurrentPage.class);
  
  private String getReplacement(Matcher aMatcher){
    String result = aMatcher.group(WHOLE_LINK);
    if ( isSelfLink(aMatcher) ){
      String linkText = aMatcher.group(LINK_TEXT);
      result = getHighlightedText(linkText);
    }
    return EscapeChars.forReplacementString(result);
  }
  
  private String getCurrentURI(){
    String result = null;
    if( ! isTesting(fTestingCurrentURI) ){
      result = (String)getRequest().getAttribute(Controller.CURRENT_URI);
    }
    else {
      result = fTestingCurrentURI;
    }
    return result;
  }
  
  private String getTitleParam(){
    String result = null;
    if( ! isTesting(fTestingTitleParam) ){
      result = getRequest().getParameter(TITLE_PARAM);
    }
    else {
      result = fTestingTitleParam;
    }
    return result;
  }
  
  private boolean isSelfLink(Matcher aMatcher){
    boolean result = false;
    if( ! fUseTTitle ){
      String hrefTarget = aMatcher.group(HREF_TARGET);
      String currentURI = getCurrentURI();
      result = isSelfLinkingByHref(currentURI, hrefTarget);
      fLogger.finest("Is self-linking (href)? " + result);
      fLogger.finest("Current URI " + Util.quote(currentURI) + " HREF Target : " + Util.quote(hrefTarget));
    }
    else {
      String linkText = aMatcher.group(LINK_TEXT);
      result = isSelfLinkingByTitle(linkText, getTitleParam());
      fLogger.finest("Is self-linking (TTitle)? " + result);
    }
    return result;
  }
  
  private String getHighlightedText(String aLinkText){
    String result = aLinkText;
    if( Util.textHasContent(fHighlightClass)){
      result = "<span class=\"" + fHighlightClass + "\">" + result + "</span>";
    }
    return result;
  }
  
  private boolean isTesting(String aTestItem){
    return Util.textHasContent(aTestItem);
  }
}
