/********************************************************************
 * Copyright © 2004 EDS. All rights reserved
 ********************************************************************/
package gov.vha.edb.rules.web.builder;

import ilog.webui.dhtml.IlxWUtil;

import java.io.*;
import java.util.HashSet;
import java.util.Stack;

public class PrettyPrinter {

  public static void prettyPrint(Reader in, Writer out, String type) {
    Parser p = new Parser(in, out);
    try {
      p.parse(type);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * <p>This class allows you to parse a CSS stream and return a list of rules.</p>
   */
  static class Parser {

    private StreamTokenizer _st;
    private Stack _state;
    private PrintWriter out = null;

    // Java code.
    private static final int TOKEN_JAVA = 1;
    // XML code.
    private static final int TOKEN_XML = 2;
    // [/**....*/]
    private static final int TOKEN_SLASHSLASH_COMMENT = 3;
    // [//]
    private static final int TOKEN_SLASHSTAR_COMMENT = 4;
    // [<foo>]
    private static final int TOKEN_START_ELEMENT = 5;
    // [</foo>]
    private static final int TOKEN_END_ELEMENT = 6;
    // ["..."]
    private static final int TOKEN_JAVA_STRING = 7;
    // ["..."]
    private static final int TOKEN_XML_STRING = 8;
    // [<... name=...>]
    private static final int TOKEN_XML_ATTRIBUTE_NAME = 9;
    // [<... ...=...>]
    private static final int TOKEN_XML_ATTRIBUTE_OP = 10;
    // [<... ...=...>]
    private static final int TOKEN_XML_ATTRIBUTE_VALUE = 11;

    /**
     * <p>Creates a JSP parser.</p>
     */
    public Parser(Reader reader, Writer out) {
      init(reader);
      this.out = new PrintWriter(out);
    }

    private void pushState(int state) {
      _state.push(new Integer(state));
    }

    private int popState() {
      return ((Integer)_state.pop()).intValue();
    }

    private int popState(int value) {
      int peek = ((Integer)_state.pop()).intValue();
      if (value!=peek) {
        throw new RuntimeException("Wrong state");
      }
      return peek;
    }

    private int peekState() {
      return ((Integer)_state.peek()).intValue();
    }

    private void init(Reader reader) {
      // Initialize the streamtokenizer.
      _st = new StreamTokenizer(reader);
      _st.resetSyntax();
      _st.wordChars('a', 'z');
      _st.wordChars('A', 'Z');
      _st.wordChars(128+32, 255);
      _st.whitespaceChars(0, ' '-1);
      _st.ordinaryChar('\t');
      _st.eolIsSignificant(true);
      _st.wordChars('0','9');
      _st.wordChars('-', '-');
    }

    /**
     * <p>Parses the reader given at creation time.</p>
     */
    public void parse(String type) throws IOException {
      int tt;
      _state = new Stack();
      if (type==null) {
        type = "jsp";
      }
      if ("jsp".equals(type)) {
        pushState(TOKEN_XML);
        handler = new DefaultJSPHandler(this.out);
      } else if ("java".equals(type)) {
        pushState(TOKEN_JAVA);
        handler = new DefaultJavaHandler(this.out);
      } else if ("bom".equals(type)) {
        pushState(TOKEN_JAVA);
        handler = new DefaultBOMHandler(this.out);
      }
      while(StreamTokenizer.TT_EOF != (tt = _st.nextToken())) {
        switch (tt) {
          case StreamTokenizer.TT_EOL:
          gotEol();
          break;
        case StreamTokenizer.TT_NUMBER:
          gotWord(String.valueOf(_st.nval));
          break;
        case StreamTokenizer.TT_WORD:
          gotWord(_st.sval);
          break;
        default:
          gotToken(tt);
        }
      }
    }

    private void gotToken(int token) throws IOException {
      int tt;
      switch (token) {
      case '<':
        tt = _st.nextToken();
        if (tt=='%') {
          tt = _st.nextToken();
          if (tt=='@') {
            gotLTPercentAt();
          } else {
            _st.pushBack();
            gotLTPercent();
          }
        } else if (tt=='/') {
          gotLTSlash();
        } else {
          _st.pushBack();
          gotLT();
        }
        break;
      case '%':
        tt = _st.nextToken();
        if (tt=='>') {
          gotPercentGT();
        } else {
          _st.pushBack();
          character(String.valueOf((char)token));
        }
        break;
      case '>':	    gotGT();	    break;
      case '*':
        if (peekState()==TOKEN_SLASHSTAR_COMMENT) {
          if (_st.nextToken()=='/') {
            gotStarSlash();
            break;
          } else {
            _st.pushBack();
          }
        }
        character(String.valueOf((char)token));
        break;
      case '/':
        tt = _st.nextToken();
        if (tt=='/') {
          gotSlashSlash();
          break;
        } else if (tt=='*') {
          gotSlashStar();
          break;
        } else {
          _st.pushBack();
        }
        character(String.valueOf((char)token));
        break;
      case '\"':
        gotDoubleQuote();
        break;
      case '\\':
        if (_st.nextToken()=='\"') {
          gotEscapeDoubleQuote();
        } else {
          _st.pushBack();
        }
        character(String.valueOf((char)token));
        break;
      case '=':
        gotEqual();
        break;
      default:
        character(String.valueOf((char)token));
        break;
      }
    }

    private void gotEol() {
      int state = peekState();
      switch (state) {
      case TOKEN_SLASHSLASH_COMMENT:
        endJavaComment();
        popState();
        character("\n");
        break;
      default:
        character("\n");
      }
    }

    private void gotWord(String word) {
      int state = peekState();
      switch (state) {
      case TOKEN_JAVA:
        javaWord(word);
        break;
      case TOKEN_START_ELEMENT:
        startElement(word);
        popState();
        pushState(TOKEN_XML_ATTRIBUTE_NAME);
        break;
      case TOKEN_END_ELEMENT:
        endElement(word);
        break;
      case TOKEN_XML_ATTRIBUTE_NAME:
        attributeName(word);
        pushState(TOKEN_XML_ATTRIBUTE_OP);
        break;
      case TOKEN_XML_ATTRIBUTE_OP:
        attributeName(word);
        break;
      default:
        character(word);
      }
    }

    private void gotEqual() {
      int state = peekState();
      switch (state) {
      case TOKEN_XML_ATTRIBUTE_OP:
        attributeOperator("=");
        pushState(TOKEN_XML_ATTRIBUTE_VALUE);
        break;
      default:
        character("=");
      }
    }

    private void gotDoubleQuote() {
      int state = peekState();
      switch (state) {
      case TOKEN_JAVA:
        pushState(TOKEN_JAVA_STRING);
        startJavaString("\"");
        break;
      case TOKEN_XML_ATTRIBUTE_VALUE:
        pushState(TOKEN_XML_STRING);
        startXmlString("\"");
        break;
      case TOKEN_JAVA_STRING:
        popState();
        endJavaString("\"");
        break;
      case TOKEN_XML_STRING:
        popState(TOKEN_XML_STRING);
        popState(TOKEN_XML_ATTRIBUTE_VALUE);
        popState(TOKEN_XML_ATTRIBUTE_OP);
        endXmlString("\"");
        break;
      default:
        character("\"");
      }
    }

    private void gotEscapeDoubleQuote() {
      character("\\\"");
    }

    private void gotLTPercent() {
      int state = peekState();
      switch (state) {
      case TOKEN_XML:
        jspMarker("<%");
        startJava();
        pushState(TOKEN_JAVA);
        break;
      default:
        character("<%");
      }
    }

    private void gotLTPercentAt() {
      int state = peekState();
      switch (state) {
      case TOKEN_XML:
        jspMarker("<%@");
        pushState(TOKEN_START_ELEMENT);
        break;
      default:
        character("<%@");
      }
    }

    private void gotPercentGT() {
      int state = peekState();
      switch (state) {
      case TOKEN_JAVA:
        endJava();
        jspMarker("%>");
        popState();
        break;
      case TOKEN_XML_ATTRIBUTE_NAME:
        tagMarker("%>");
        popState();
        break;
      default:
        character("%>");
      }
    }

    private void gotSlashSlash() {
      int state = peekState();
      switch (state) {
      case TOKEN_JAVA:
        pushState(TOKEN_SLASHSLASH_COMMENT);
        startJavaComment();
        character("//");
        break;
      default:
        character("//");
      }
    }

    private void gotSlashStar() {
      int state = peekState();
      switch (state) {
      case TOKEN_JAVA:
        startJavaComment();
        character("/*");
        pushState(TOKEN_SLASHSTAR_COMMENT);
        break;
      default:
        character("/*");
      }
    }

    private void gotStarSlash() {
      int state = peekState();
      switch (state) {
      case TOKEN_SLASHSTAR_COMMENT:
        character("*/");
        endJavaComment();
        popState();
        break;
      default:
        character("*/");
      }
    }

    private void gotGT() {
      int state = peekState();
      switch (state) {
      case TOKEN_XML_ATTRIBUTE_NAME:
      case TOKEN_END_ELEMENT:
        tagMarker(">");
        popState();
        break;
      default:
        character(">");
      }
    }

    private void gotLT() {
      int state = peekState();
      switch (state) {
      case TOKEN_XML:
        tagMarker("<");
        pushState(TOKEN_START_ELEMENT);
        break;
      default:
        character("<");
      }
    }

    private void gotLTSlash() {
      int state = peekState();
      switch (state) {
      case TOKEN_XML:
        tagMarker("</");
        pushState(TOKEN_END_ELEMENT);
        break;
      default:
        character("</");
      }
    }

    // Callbacks

    private Handler handler;

    private Handler getHandler() {
      return handler;
    }

    private void character(String s) {
      getHandler().character(s);
    }

    private void tagMarker(String marker) {
      ((JSPHandler)getHandler()).tagMarker(marker);
    }

    private void startElement(String name) {
      ((JSPHandler)getHandler()).startElement(name);
    }

    private void endElement(String name) {
      ((JSPHandler)getHandler()).endElement(name);
    }

    private void attributeName(String name) {
      ((JSPHandler)getHandler()).attributeName(name);
    }

    private void attributeOperator(String op) {
      ((JSPHandler)getHandler()).attributeOperator(op);
    }

    private void jspMarker(String marker) {
      ((JSPHandler)getHandler()).jspMarker(marker);
    }

    private void startJava() {
      ((JavaHandler)getHandler()).startJava();
    }

    private void endJava() {
      ((JavaHandler)getHandler()).endJava();
    }

    private void javaWord(String word) {
      ((JavaHandler)getHandler()).javaWord(word);
    }

    private void startJavaString(String quote) {
      ((JavaHandler)getHandler()).startJavaString(quote);
    }

    private void endJavaString(String quote) {
      ((JavaHandler)getHandler()).endJavaString(quote);
    }

    private void startXmlString(String quote) {
      ((JSPHandler)getHandler()).startXmlString(quote);
    }

    private void endXmlString(String quote) {
      ((JSPHandler)getHandler()).endXmlString(quote);
    }

    private void startJavaComment() {
      ((JavaHandler)getHandler()).startJavaComment();
    }

    private void endJavaComment() {
      ((JavaHandler)getHandler()).endJavaComment();
    }

  }


  public interface Handler {

    public void character(String s);

  }

  public interface JavaHandler extends Handler {

    public void startJava();
    public void endJava();
    public void javaWord(String word);
    public void startJavaString(String quote);
    public void endJavaString(String quote);
    public void startJavaComment();
    public void endJavaComment();

  }

  public interface XMLHandler extends Handler {

    public void tagMarker(String name);
    public void startElement(String name);
    public void endElement(String name);
    public void startXmlString(String quote);
    public void endXmlString(String quote);
    public void attributeName(String name);
    public void attributeOperator(String equal);

  }

  public interface JSPHandler extends XMLHandler, JavaHandler {

    public void jspMarker(String marker);

  }

  static String [] javaKeywords = {
    "package",
    "import",
    "synchronized",
    "class",
    "interface",
    "extends",
    "implements",
    "final",
    "static",
    "public",
    "protected",
    "try",
    "catch",
    "throw",
    "throws",
    "private",
    "byte",
    "int",
    "long",
    "float",
    "double",
    "boolean",
    "char",
    "void",
    "super",
    "this",
    "null",
    "true",
    "false",
    "new",
    "return",
    "switch",
    "case",
    "default",
    "for",
    "while",
    "if",
    "else"
  };

  static HashSet javaKeywordsSet;
  static HashSet bomKeywordsSet;

  static {
    javaKeywordsSet = new HashSet();
    int length = javaKeywords.length;
    for (int i=0; i<length; i++) {
      javaKeywordsSet.add(javaKeywords[i]);
    }
    bomKeywordsSet = (HashSet)javaKeywordsSet.clone();
    bomKeywordsSet.add("property");
  }

  public static class DefaultHandler implements Handler  {

    protected PrintWriter out;

    public DefaultHandler(PrintWriter out) {
      this.out = out;
    }

    public void character(String s) {
      out.print(IlxWUtil.toHtml(s));
    }

  }

  public static class DefaultJavaHandler
    extends DefaultHandler implements JavaHandler {

    public DefaultJavaHandler(PrintWriter out) {
      super(out);
    }

    protected boolean isKeyword(String s) {
      return javaKeywordsSet.contains(s);
    }

    public void startJava() {
    }

    public void endJava() {
    }

    private int state = 0;

    public void javaWord(String word) {
      if (state==1) {
        out.print("<font color=\"#228B22\">");
      } else if (isKeyword(word)) {
        out.print("<font color=\"#A020F0\">");
      }
      character(word);
      if (state==1) {
        state = 0;
        out.print("</font>");
      } else if (isKeyword(word)) {
        out.print("</font>");
      }
      if ("class".equals(word)) {
        state = 1;
      }
    }

    public void startJavaString(String quote) {
      out.print("<font color=\"#BC8F8F\">");
      character(quote);
    }

    public void endJavaString(String quote) {
      character(quote);
      out.print("</font>");
    }

    public void startJavaComment() {
      out.print("<font color=\"green\">");
    }

    public void endJavaComment() {
      out.print("</font>");
    }

  }

  public static class DefaultJSPHandler extends DefaultJavaHandler
    implements JSPHandler {

    public DefaultJSPHandler(PrintWriter out) {
      super(out);
    }

    public void tagMarker(String marker) {
      out.print("<font color=\"blue\">");
      character(marker);
      out.print("</font>");
    }

    public void startElement(String name) {
      out.print("<font color=\"maroon\">");
      character(name);
      out.print("</font>");
    }

    public void endElement(String name) {
      out.print("<font color=\"maroon\">");
      character(name);
      out.print("</font>");
    }

    public void jspMarker(String marker) {
      out.print("<b><font color=\"blue\">");
      character(marker);
      out.print("</font></b>");
    }

    public void startXmlString(String quote) {
      out.print("<font color=\"blue\">");
      character(quote);
      out.print("</font>");
      out.print("<b><font color=\"black\">");
    }

    public void endXmlString(String quote) {
      out.print("</font></b>");
      out.print("<font color=\"blue\">");
      character(quote);
      out.print("</font>");
    }

    public void attributeName(String name) {
      //      out.print("<font color=\"orange\">");
      out.print("<font color=\"maroon\">");
      character(name);
      out.print("</font>");
    }

    public void attributeOperator(String equal) {
      out.print("<font color=\"blue\">");
      character(equal);
      out.print("</font>");
    }

  }

  public static class DefaultBOMHandler
    extends DefaultJavaHandler {

    public DefaultBOMHandler(PrintWriter out) {
      super(out);
    }

    protected boolean isKeyword(String s) {
      return bomKeywordsSet.contains(s);
    }

  }

}
