package net.sf.saxon.trace;

import net.sf.saxon.Controller;
import net.sf.saxon.Version;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.parser.CodeInjector;
import net.sf.saxon.lib.StandardErrorListener;
import net.sf.saxon.lib.TraceListener;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.tree.util.FastStringBuffer;
import net.sf.saxon.tree.util.Navigator;
import net.sf.saxon.value.Whitespace;

import java.io.PrintStream;
import java.util.Iterator;

/**
 * This is the standard trace listener used when the -T option is specified on the command line.
 * There are two variants, represented by subclasses: one for XSLT, and one for XQuery. The two variants
 * differ in that they present the trace output in terms of constructs used in the relevant host language.
 */

public abstract class AbstractTraceListener implements TraceListener {
    private int indent = 0;
    private PrintStream out = System.err;
    /*@NotNull*/ private static StringBuffer spaceBuffer = new StringBuffer("                ");

    /**
     * Get the associated CodeInjector to be used at compile time to generate the tracing calls
     */

    public CodeInjector getCodeInjector() {
        return new TraceCodeInjector();
    }

    /**
     * Called at start
     */

    public void open(Controller controller) {
        out.println("<trace " +
                "saxon-version=\"" + Version.getProductVersion()+ "\" " +
                getOpeningAttributes() + '>');
        indent++;
    }

    protected abstract String getOpeningAttributes();

    /**
     * Called at end
     */

    public void close() {
        indent--;
        out.println("</trace>");
    }

    /**
     * Called when an instruction in the stylesheet gets processed
     */

    public void enter(/*@NotNull*/ InstructionInfo info, XPathContext context) {
        int infotype = info.getConstructType();
        StructuredQName qName = info.getObjectName();
        String tag = tag(infotype);
        if (tag==null) {
            // this TraceListener ignores some events to reduce the volume of output
            return;
        }
        String file = StandardErrorListener.abbreviatePath(info.getSystemId());
        String msg = AbstractTraceListener.spaces(indent) + '<' + tag;
        String name = (String)info.getProperty("name");
        if (name!=null) {
            msg += " name=\"" + escape(name) + '"';
        } else if (qName != null) {
            msg += " name=\"" + escape(qName.getDisplayName()) + '"';
        }
        Iterator props = info.getProperties();
        while (props.hasNext()) {
            String prop = (String)props.next();
            Object val = info.getProperty(prop);
            if (prop.startsWith("{")) {
                // It's a QName in Clark notation: we'll strip off the namespace
                int rcurly = prop.indexOf('}');
                if (rcurly > 0) {
                    prop = prop.substring(rcurly+1);
                }
            }
            if (val != null && !prop.equals("name") && !prop.equals("expression")) {
                msg += ' ' + prop + "=\"" + escape(val.toString()) + '"';
            }
        }

        msg += " line=\"" + info.getLineNumber() + '"';

        int col = info.getColumnNumber();
        if (col >= 0) {
            msg += " column=\"" + info.getColumnNumber() + '"';
        }

        msg += " module=\"" + escape(file) + "\">";
        out.println(msg);
        indent++;
    }

    /**
     * Escape a string for XML output (in an attribute delimited by double quotes).
     * This method also collapses whitespace (since the value may be an XPath expression that
     * was originally written over several lines).
     */

    public String escape(/*@Nullable*/ String in) {
        if (in==null) {
            return "";
        }
        CharSequence collapsed = Whitespace.collapseWhitespace(in);
        FastStringBuffer sb = new FastStringBuffer(collapsed.length() + 10);
        for (int i=0; i<collapsed.length(); i++) {
            char c = collapsed.charAt(i);
            if (c=='<') {
                sb.append("&lt;");
            } else if (c=='>') {
                sb.append("&gt;");
            } else if (c=='&') {
                sb.append("&amp;");
            } else if (c=='\"') {
                sb.append("&#34;");
            } else if (c=='\n') {
                sb.append("&#xA;");
            } else if (c=='\r') {
                sb.append("&#xD;");
            } else if (c=='\t') {
                sb.append("&#x9;");
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * Called after an instruction of the stylesheet got processed
     */

    public void leave(/*@NotNull*/ InstructionInfo info) {
        int infotype = info.getConstructType();
        String tag = tag(infotype);
        if (tag==null) {
            // this TraceListener ignores some events to reduce the volume of output
            return;
        }
        indent--;
        out.println(AbstractTraceListener.spaces(indent) + "</" + tag + '>');
    }

    protected abstract String tag(int construct);

    /**
    * Called when an item becomes the context item
    */

   public void startCurrentItem(Item item) {
       if (item instanceof NodeInfo) {
           NodeInfo curr = (NodeInfo) item;
           out.println(AbstractTraceListener.spaces(indent) + "<source node=\"" + Navigator.getPath(curr)
                   + "\" line=\"" + curr.getLineNumber()
                   + "\" file=\"" + StandardErrorListener.abbreviatePath(curr.getSystemId())
                   + "\">");
       }
       indent++;
   }

    /**
     * Called after a node of the source tree got processed
     */

    public void endCurrentItem(Item item) {
        indent--;
        if (item instanceof NodeInfo) {
            NodeInfo curr = (NodeInfo) item;
            out.println(AbstractTraceListener.spaces(indent) + "</source><!-- " +
                    Navigator.getPath(curr) + " -->");
        }
    }

    /**
     * Get n spaces
     */

    private static String spaces(int n) {
        while (spaceBuffer.length() < n) {
            spaceBuffer.append(AbstractTraceListener.spaceBuffer);
        }
        return spaceBuffer.substring(0, n);
    }

    /**
     * Set the output destination (default is System.err)
     * @param stream the output destination for tracing output
     */

    public void setOutputDestination(PrintStream stream) {
        out = stream;
    }

    /**
     * Get the output destination
     */

    public PrintStream getOutputDestination() {
        return out;
    }
}

//
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
// you may not use this file except in compliance with the License. You may obtain a copy of the
// License at http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is: all this file
//
// The Initial Developer of the Original Code is Saxonica Limited.
// Portions created by ___ are Copyright (C) ___. All rights reserved.
//
// Contributor(s):
//