/*
 * Copyright (C) 2003-2010 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.wiki.rendering.render.confluence;

import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.xwiki.rendering.renderer.printer.LookaheadWikiPrinter;
import org.xwiki.rendering.renderer.printer.WikiPrinter;

/**
 * A Wiki printer that knows how to escape characters that would otherwise mean something different in Confluence wiki
 * syntax. For example if we have "*" as special symbols (and not as a Bold Format block) we need to escape them to
 * "~*" as otherwise they'd be considered bold after being rendered.
 */
public class ConfluenceSyntaxEscapeWikiPrinter extends LookaheadWikiPrinter {
  
  private static final Pattern VERBATIM_PATTERN = Pattern.compile("(\\{\\{\\{)|(\\}\\}\\})");

  private ConfluenceSyntaxListenerChain listenerChain;

  private ConfluenceSyntaxEscapeHandler escapeHandler;

  private boolean escapeLastChar;

  private Pattern escapeFirstIfMatching;

  private String lastPrinted;

  public ConfluenceSyntaxEscapeWikiPrinter(WikiPrinter printer, ConfluenceSyntaxListenerChain listenerChain) {
    
    super(printer);

    this.escapeHandler = new ConfluenceSyntaxEscapeHandler();

    this.listenerChain = listenerChain;
  }

  public ConfluenceSyntaxEscapeHandler getEscapeHandler() {
    return escapeHandler;
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.xwiki.rendering.renderer.printer.LookaheadWikiPrinter#printInternal(java.lang.String)
   */
  @Override
  protected void printInternal(String text) {
    super.printInternal(text);

    int length = text.length();

    if (length > 0) {
      this.escapeHandler.setOnNewLine(text.charAt(length - 1) == '\n');
    }

    this.lastPrinted = text;
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.xwiki.rendering.renderer.printer.LookaheadWikiPrinter#println(java.lang.String)
   */
  @Override
  protected void printlnInternal(String text) {
    super.printlnInternal(text);

    this.escapeHandler.setOnNewLine(true);

    this.lastPrinted = "\n";
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.xwiki.rendering.renderer.printer.LookaheadWikiPrinter#flush()
   */
  @Override
  public void flush() {
    if (getBuffer().length() > 0) {
      this.escapeHandler.escape(getBuffer(), this.listenerChain, this.escapeLastChar, this.escapeFirstIfMatching);
      super.flush();
    }
    this.escapeLastChar = false;
    this.escapeFirstIfMatching = null;
  }

  public void printBeginBold() {
    flush();

    boolean isOnNewLine = this.escapeHandler.isOnNewLine();

    print("*");

    if (isOnNewLine) {
      this.escapeFirstIfMatching = ConfluenceSyntaxEscapeHandler.STARLISTEND_PATTERN;
    }
  }

  public void setEscapeLastChar(boolean escapeLastChar) {
    this.escapeLastChar = escapeLastChar;
  }

  public void setBeforeLink(boolean beforeLink) {
    this.escapeHandler.setBeforeLink(beforeLink);
  }

  public void setOnNewLine(boolean onNewLine) {
    this.escapeHandler.setOnNewLine(onNewLine);
  }

  public boolean isOnNewLine() {
    return this.escapeHandler.isOnNewLine();
  }

  public boolean isAfterWhiteSpace() {
    return isOnNewLine()
        || Character.isWhitespace(getLastPrinted().charAt(getLastPrinted().length() - 1));
  }

  public String getLastPrinted() {
    return this.lastPrinted;
  }

  public void printBeginItalic() {
    // If the lookahead buffer is not empty and the last character is ":" then
    // we need to escape it since otherwise we would get "://" which could be confused for a URL.
    if (getBuffer().length() > 0 && getBuffer().charAt(getBuffer().length() - 1) == ':') {
      this.escapeLastChar = true;
    }

    print("_");
  }

  public void printEndItalic() {
    // If the lookahead buffer is not empty and the last character is ":" then
    // we need to escape it since otherwise we would get "://" which could be confused for a URL.
    if (getBuffer().length() > 0 && getBuffer().charAt(getBuffer().length() - 1) == ':') {
      this.escapeLastChar = true;
    }

    print("_");
  }

  public void printInlineMacro(String confluenceSyntaxText) {
    // If the lookahead buffer is not empty and the last character is "{" then
    // we need to escape it since otherwise we would get "{{{" which could be confused for a verbatim block.
    if (getBuffer().length() > 0 && getBuffer().charAt(getBuffer().length() - 1) == '{') {
      this.escapeLastChar = true;
    }

    print(confluenceSyntaxText);
  }

  public void printVerbatimContent(String verbatimContent) {
    StringBuffer result = new StringBuffer();

    Stack<StringBuffer> subVerbatimStack = new Stack<StringBuffer>();
    boolean printEndVerbatim = false;

    Matcher matcher = VERBATIM_PATTERN.matcher(verbatimContent);
    int currentIndex = 0;
    for (; matcher.find(); currentIndex = matcher.end()) {
      String before = verbatimContent.substring(currentIndex, matcher.start());

      if (printEndVerbatim) {
        if (before.startsWith("}")) {
          result.append("~}~}~}");
        } else {
          result.append("~}}}");
        }
      }

      if (subVerbatimStack.size() == 0) {
        result.append(before);
      } else {
        subVerbatimStack.peek().append(before);
      }

      if (matcher.group(1) != null) {
        subVerbatimStack.push(new StringBuffer());
      } else {
        if (subVerbatimStack.size() == 0) {
          printEndVerbatim = true;
        } else {
          StringBuffer subVerbatim = subVerbatimStack.pop();

          if (subVerbatimStack.size() == 0) {
            result.append("{{{");
            result.append(subVerbatim);
            result.append("}}}");
          } else {
            subVerbatimStack.peek().append("{{{");
            subVerbatimStack.peek().append(subVerbatim);
            subVerbatimStack.peek().append("}}}");
          }
        }
      }
    }

    if (currentIndex == 0) {
      print(verbatimContent);
      return;
    }

    String end = verbatimContent.substring(currentIndex);

    if (printEndVerbatim) {
      if (end.length() == 0 || end.charAt(0) == '}') {
        result.append("~}~}~}");
      } else {
        result.append("~}}}");
      }
    }

    if (subVerbatimStack.size() > 0) {
      // Append remaining string
      subVerbatimStack.peek().append(end);

      // Escape not closed verbatim blocks
      while (subVerbatimStack.size() > 0) {
        StringBuffer subVerbatim = subVerbatimStack.pop();

        if (subVerbatimStack.size() == 0) {
          if (subVerbatim.length() > 0 && subVerbatim.charAt(0) == '{') {
            result.append("~{~{~{");
          } else {
            result.append("~{{{");
          }
          result.append(subVerbatim);
        } else {
          if (subVerbatim.length() > 0 && subVerbatim.charAt(0) == '{') {
            subVerbatimStack.peek().append("~{~{~{");
          } else {
            subVerbatimStack.peek().append("~{{{");
          }
          subVerbatimStack.peek().append(subVerbatim);
        }
      }
    } else {
      // Append remaining string
      result.append(end);
    }

    print(result.toString());
  }
}
