/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.webapp.taglib;

import java.io.IOException;
import java.util.StringTokenizer;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyTagSupport;

/**
 * @author Michel-Ange ANTON
 */
public class XmlFileTag extends BodyTagSupport {

// ----------------------------------------------------- Constants
    private static final int NONE = 0;
    private static final int HEADER = 1;
    private static final int ELEMENT = 2;
    private static final int ELEMENT_AUTO_CLOSE = 3;
    private static final int ELEMENT_CLOSE = 4;
    private static final int COMMENT = 5;
    private static final int TEXT = 6;
// ----------------------------------------------------- Instance Variables

    protected String m_Body = null;
    protected int m_LastPrint = NONE;
    protected int m_Indent = 0;

    //are we printing XML comment content
    protected boolean comment = false;

// ------------------------------------------------------------- Properties

// --------------------------------------------------------- Public Methods

    /**
     * Render this instant actions control.
     *
     * @exception JspException if a processing error occurs
     */
    public int doEndTag()
        throws JspException {
        JspWriter out = pageContext.getOut();
        try {
            String sXml = null;
            // Get body
            if (m_Body != null) {
                sXml = m_Body;
                m_Body = null;
            }
            // Display
            if (sXml != null) {
                m_LastPrint = NONE;
                m_Indent = 0;
                render(out, sXml, 0, sXml.length());
            }
        }
        catch (IOException e) {
            throw new JspException(e);
        }
        return (EVAL_PAGE);
    }

    public int doAfterBody()
        throws JspException {
        String sBody = bodyContent.getString();
        if (sBody != null) {
            sBody = sBody.trim();
            if (sBody.length() > 0) {
                this.m_Body = sBody;
            }
        }
        return (SKIP_BODY);
    }

    /**
     * Release all state information set by this tag.
     */
    public void release() {
        m_Body = null;
    }

// -------------------------------------------------------- Protected Methods

    protected void render(JspWriter p_Out, String p_Xml, int p_Begin, int p_End)
        throws IOException, JspException {

        int iCurrent = p_Begin;
        int iBegin;
        int iEnd;
        String sElement;
        String sElementWithAttr;
        String sName;
        char cType;

        for (; ; ) {
            // Find begin element
            iBegin = p_Xml.indexOf("&lt;", iCurrent);
            if ((iBegin == -1) || (iBegin >= p_End)) {
                break;
            }
            // Find end element
            iEnd = p_Xml.indexOf("&gt;", iBegin);
            if (iEnd == -1) {
                break;
            }
            // render normal text
            if (iBegin > iCurrent) {
                String sText = p_Xml.substring(iCurrent, iBegin);
                sText = sText.trim();
                if (sText.length() > 0) {
                    printText(p_Out, sText);
                }
            }

            // Get complete element
            sElement = p_Xml.substring(iBegin, iEnd + 4);
            // Detect special element (doctype, header, comment)
            cType = sElement.charAt(4);
            if ((cType == '!') || (cType == '?')) {
                // Special element (doctype, header, comment)
                if (cType == '!') {
                    // Detect comment
                    cType = sElement.charAt(5);
                    if (cType == '-') {
                        // Find actual end element - if we are dealing with comment
                        iEnd = p_Xml.indexOf("--&gt;", iBegin);
                        if (iEnd == -1) {
                            break;
                        }
                        // set counter on proper value
                        iEnd = iEnd +2;

                        // Get complete element
                        sElement = p_Xml.substring(iBegin, iEnd + 4);

                        printComment(p_Out, sElement);
                    }
                    else {
                        printHeader(p_Out, sElement);
                    }
                }
                else {
                    printHeader(p_Out, sElement);
                }
                // Next
                iCurrent = iEnd + 4;
            }
            else {
                // get name of element
                sElementWithAttr = sElement.substring(4, sElement.length() - 4);
                sName = getNameElement(sElementWithAttr);
                // Detect end element
                if (sElementWithAttr.charAt(0) == '/') {
                    // Next
                    iCurrent = iEnd + 4;
                    break;
                }
                // Detect auto-end element
                else if (sElementWithAttr.charAt(sElementWithAttr.length() - 1) == '/') {
                    printElementAutoClose(p_Out, sElement);
                    // Next
                    iCurrent = iEnd + 4;
                }
                else {
                    // Render element
                    printElement(p_Out, sElement);
                    // Search close element
                    String sCloseElement = "&lt;/" + sName + "&gt;";
                    int iPosCloseElement = p_Xml.indexOf(sCloseElement, iEnd);
                    if (iPosCloseElement == -1) {
                        // Error
                        break;
                    }
                    // render children element (or text)
                    render(p_Out, p_Xml, iEnd + 4, iPosCloseElement + sCloseElement.length());
                    // render end element
                    printElementClose(p_Out, sCloseElement);
                    // Next
                    iCurrent = iPosCloseElement + sCloseElement.length();
                }
            }
        }
    }

    protected void printElement(JspWriter p_Out, String p_Print)
        throws IOException, JspException {
        switch (m_LastPrint) {
            case NONE:
            case TEXT:
                break;
            case HEADER:
            case ELEMENT:
            case ELEMENT_AUTO_CLOSE:
            case ELEMENT_CLOSE:
            case COMMENT:
                p_Out.print("<br>");
                break;
        }

        printIndent(p_Out, m_Indent);

        // are we printing comment
        if (!comment)
            p_Out.print("<span class=\"xmlElement\">");

        p_Out.print(p_Print);

        // are we printing comment
        if (!comment)
            p_Out.print("</span>");

        m_LastPrint = ELEMENT;
        m_Indent++;
    }

    protected void printElementAutoClose(JspWriter p_Out, String p_Print)
        throws IOException, JspException {

        switch (m_LastPrint) {
            case NONE:
            case TEXT:
                break;
            case HEADER:
            case ELEMENT:
            case ELEMENT_AUTO_CLOSE:
            case ELEMENT_CLOSE:
            case COMMENT:
                p_Out.print("<br>");
                break;
        }
        printIndent(p_Out, m_Indent);
        //are we printing comment
        if (!comment)
            p_Out.print("<span class=\"xmlElement\">");

        p_Out.print(p_Print);

        // are we printing comment
        if (!comment)
            p_Out.print("</span>");

        m_LastPrint = ELEMENT_AUTO_CLOSE;
    }

    protected void printElementClose(JspWriter p_Out, String p_Print)
        throws IOException, JspException {

        m_Indent--;

        switch (m_LastPrint) {
            case NONE:
            case ELEMENT:
            case TEXT:
                break;
            case HEADER:
            case ELEMENT_AUTO_CLOSE:
            case ELEMENT_CLOSE:
            case COMMENT:
                p_Out.print("<br>");
                printIndent(p_Out, m_Indent);
                break;
        }
        // are we printing comment
        if (!comment)
            p_Out.print("<span class=\"xmlElement\">");

        p_Out.print(p_Print);

        // are we printing comment
        if (!comment)
            p_Out.print("</span>");

        m_LastPrint = ELEMENT_CLOSE;
    }

    protected void printComment(JspWriter p_Out, String p_Print)
        throws IOException, JspException {
        switch (m_LastPrint) {
            case NONE:
                break;
            case HEADER:
            case ELEMENT:
            case ELEMENT_AUTO_CLOSE:
            case ELEMENT_CLOSE:
            case COMMENT:
            case TEXT:
                p_Out.print("<br>");
                break;
        }
        printIndent(p_Out, m_Indent);
        p_Out.print("<span class=\"xmlComment\">");

        // take only comment content
        p_Print = p_Print.substring(7,p_Print.length()-6).trim();
        // start printing commented element
        p_Out.print("&lt;!--");

        // are we dealing with commented element
        if (p_Print.indexOf("/&gt;")==-1){
            // print simple comment text
            p_Out.print(p_Print);
        } else {
            // set comment property value - disables <span/> elements
            setComment(true);
            // print commented element
            render(p_Out, p_Print, 0, p_Print.length()-1);
            p_Out.print("<br>");
            // turn back comment property value - enable <span/> elements again
            setComment(false);
        }

        // print comment ending
        printIndent(p_Out, m_Indent);
        p_Out.print("--&gt;");

        p_Out.print("</span>");

        m_LastPrint = COMMENT;
    }

    protected void printHeader(JspWriter p_Out, String p_Print)
        throws IOException, JspException {
        switch (m_LastPrint) {
            case NONE:
                break;
            case HEADER:
            case ELEMENT:
            case ELEMENT_AUTO_CLOSE:
            case ELEMENT_CLOSE:
            case COMMENT:
            case TEXT:
                p_Out.print("<br>");
                break;
        }
        // are we printing comment
        if (!comment)
            p_Out.print("<span class=\"xmlHeader\">");

        p_Out.print(p_Print);

        // are we printing comment
        if (!comment)
            p_Out.print("</span>");

        m_LastPrint = HEADER;
    }

    protected void printText(JspWriter p_Out, String p_Print)
        throws IOException, JspException {

        // are we printing comment
        if (!comment)
            p_Out.print("<span class=\"xmlText\">");

        p_Out.print(p_Print);

        // are we printing comment
        if (!comment)
            p_Out.print("</span>");

        m_LastPrint = TEXT;
    }

    protected void printIndent(JspWriter p_Out, int p_Indent)
        throws IOException, JspException {
        for (int i = 0; i < p_Indent; i++) {
            p_Out.print("&nbsp;&nbsp;");
        }
    }

    protected String getNameElement(String p_ElementWithAttr) {
        StringTokenizer st = new StringTokenizer(p_ElementWithAttr, " ");
        return st.nextToken();
    }

    protected void setComment(boolean value){
        // are we printing comment (enable/disable <span/> elements)
        comment = value;
    }

}
