/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id$
 */
 package org.apache.xml.serializer;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Properties;

import javax.xml.transform.Result;

import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;

/**
 * This class receives notification of SAX-like events, and with gathered
 * information over these calls it will invoke the equivalent SAX methods
 * on a handler, the ultimate xsl:output method is known to be "xml".
 * 
 * This class is not a public API.
 * @xsl.usage internal
 */
public final class ToXMLSAXHandler extends ToSAXHandler
{

    /**
     * Keeps track of whether output escaping is currently enabled
     */
    protected boolean m_escapeSetting = true;

    public ToXMLSAXHandler()
    {
        // default constructor (need to set content handler ASAP !)
        m_prefixMap = new NamespaceMappings();
        initCDATA();
    }

    /**
     * @see Serializer#getOutputFormat()
     */
    public Properties getOutputFormat()
    {
        return null;
    }

    /**
     * @see Serializer#getOutputStream()
     */
    public OutputStream getOutputStream()
    {
        return null;
    }

    /**
     * @see Serializer#getWriter()
     */
    public Writer getWriter()
    {
        return null;
    }

    /**
     * Do nothing for SAX.
     */
    public void indent(int n) throws SAXException
    {
    }


    /**
     * @see DOMSerializer#serialize(Node)
     */
    public void serialize(Node node) throws IOException
    {
    }

    /**
     * @see SerializationHandler#setEscaping(boolean)
     */
    public boolean setEscaping(boolean escape) throws SAXException
    {
        boolean oldEscapeSetting = m_escapeSetting;
        m_escapeSetting = escape;

        if (escape) {
            processingInstruction(Result.PI_ENABLE_OUTPUT_ESCAPING, "");
        } else {
            processingInstruction(Result.PI_DISABLE_OUTPUT_ESCAPING, "");
        }

        return oldEscapeSetting;
    }

    /**
     * @see Serializer#setOutputFormat(Properties)
     */
    public void setOutputFormat(Properties format)
    {
    }

    /**
     * @see Serializer#setOutputStream(OutputStream)
     */
    public void setOutputStream(OutputStream output)
    {
    }

    /**
     * @see Serializer#setWriter(Writer)
     */
    public void setWriter(Writer writer)
    {
    }

    /**
     * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
     */
    public void attributeDecl(
        String arg0,
        String arg1,
        String arg2,
        String arg3,
        String arg4)
        throws SAXException
    {
    }

    /**
     * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String)
     */
    public void elementDecl(String arg0, String arg1) throws SAXException
    {
    }

    /**
     * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String)
     */
    public void externalEntityDecl(String arg0, String arg1, String arg2)
        throws SAXException
    {
    }

    /**
     * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(String, String)
     */
    public void internalEntityDecl(String arg0, String arg1)
        throws SAXException
    {
    }

    /**
     * Receives notification of the end of the document.
     * @see org.xml.sax.ContentHandler#endDocument()
     */
    public void endDocument() throws SAXException
    {

        flushPending();

        // Close output document
        m_saxHandler.endDocument();

        if (m_tracer != null)
            super.fireEndDoc();
    }

    /**
     * This method is called when all the data needed for a call to the
     * SAX handler's startElement() method has been gathered.
     */
    protected void closeStartTag() throws SAXException
    {

        m_elemContext.m_startTagOpen = false;

        final String localName = getLocalName(m_elemContext.m_elementName);
        final String uri = getNamespaceURI(m_elemContext.m_elementName, true);

        // Now is time to send the startElement event
        if (m_needToCallStartDocument)
        {
            startDocumentInternal();
        }
        m_saxHandler.startElement(uri, localName, m_elemContext.m_elementName, m_attributes);
        // we've sent the official SAX attributes on their way,
        // now we don't need them anymore.
        m_attributes.clear();

        if(m_state != null)
          m_state.setCurrentNode(null);
    }

    /**
     * Closes ane open cdata tag, and
     * unlike the this.endCDATA() method (from the LexicalHandler) interface,
     * this "internal" method will send the endCDATA() call to the wrapped
     * handler.
     * 
     */
    public void closeCDATA() throws SAXException
    {

        // Output closing bracket - "]]>"
        if (m_lexHandler != null && m_cdataTagOpen) {
            m_lexHandler.endCDATA();
        }
        

        // There are no longer any calls made to 
        // m_lexHandler.startCDATA() without a balancing call to
        // m_lexHandler.endCDATA()
        // so we set m_cdataTagOpen to false to remember this.
        m_cdataTagOpen = false;        
    }

    /**
     * @see org.xml.sax.ContentHandler#endElement(String, String, String)
     */
    public void endElement(String namespaceURI, String localName, String qName)
        throws SAXException
    {
        // Close any open elements etc.
        flushPending();
        
        if (namespaceURI == null)
        {
            if (m_elemContext.m_elementURI != null)
                namespaceURI = m_elemContext.m_elementURI;
            else
                namespaceURI = getNamespaceURI(qName, true);
        }
        
        if (localName == null)
        {
            if (m_elemContext.m_elementLocalName != null)
                localName = m_elemContext.m_elementLocalName;
            else
                localName = getLocalName(qName);
        }

        m_saxHandler.endElement(namespaceURI, localName, qName);

        if (m_tracer != null)
            super.fireEndElem(qName);       

        /* Pop all namespaces at the current element depth.
         * We are not waiting for official endPrefixMapping() calls.
         */
        m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth,
            m_saxHandler);
        m_elemContext = m_elemContext.m_prev;
    }

    /**
     * @see org.xml.sax.ContentHandler#endPrefixMapping(String)
     */
    public void endPrefixMapping(String prefix) throws SAXException
    {
        /* poping all prefix mappings should have been done
         * in endElement() already
         */
         return;
    }

    /**
     * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
     */
    public void ignorableWhitespace(char[] arg0, int arg1, int arg2)
        throws SAXException
    {
        m_saxHandler.ignorableWhitespace(arg0,arg1,arg2);
    }

    /**
     * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator)
     */
    public void setDocumentLocator(Locator arg0)
    {
        m_saxHandler.setDocumentLocator(arg0);
    }

    /**
     * @see org.xml.sax.ContentHandler#skippedEntity(String)
     */
    public void skippedEntity(String arg0) throws SAXException
    {
        m_saxHandler.skippedEntity(arg0);
    }

    /**
     * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
     * @param prefix The prefix that maps to the URI
     * @param uri The URI for the namespace
     */
    public void startPrefixMapping(String prefix, String uri)
        throws SAXException
    {
       startPrefixMapping(prefix, uri, true);
    }

    /**
     * Remember the prefix/uri mapping at the current nested element depth.
     *
     * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
     * @param prefix The prefix that maps to the URI
     * @param uri The URI for the namespace
     * @param shouldFlush a flag indicating if the mapping applies to the
     * current element or an up coming child (not used).
     */

    public boolean startPrefixMapping(
        String prefix,
        String uri,
        boolean shouldFlush)
        throws org.xml.sax.SAXException
    {

        /* Remember the mapping, and at what depth it was declared
         * This is one greater than the current depth because these
         * mappings will apply to the next depth. This is in
         * consideration that startElement() will soon be called
         */

        boolean pushed;
        int pushDepth;
        if (shouldFlush)
        {
            flushPending();
            // the prefix mapping applies to the child element (one deeper)
            pushDepth = m_elemContext.m_currentElemDepth + 1;
        }
        else
        {
            // the prefix mapping applies to the current element
            pushDepth = m_elemContext.m_currentElemDepth;
        }
        pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);

        if (pushed)
        {
            m_saxHandler.startPrefixMapping(prefix,uri);
            
            if (getShouldOutputNSAttr()) 
            {

	              /* I don't know if we really needto do this. The
	               * callers of this object should have injected both
	               * startPrefixMapping and the attributes.  We are
	               * just covering our butt here.
	               */
	              String name;
  	            if (EMPTYSTRING.equals(prefix))
  	            {
  	                name = "xmlns";
  	                addAttributeAlways(XMLNS_URI, name, name,"CDATA",uri, false);
  	            }
  	            else 
                {
  	                if (!EMPTYSTRING.equals(uri)) // hack for attribset16 test
  	                {                             // that maps ns1 prefix to "" URI 
  	                    name = "xmlns:" + prefix;
  	
  	                    /* for something like xmlns:abc="w3.pretend.org"
  	             	 	     *  the uri is the value, that is why we pass it in the
  	             	 	     * value, or 5th slot of addAttributeAlways()
  	                 	   */
  	                    addAttributeAlways(XMLNS_URI, prefix, name,"CDATA",uri, false );
  	                }
  	            }
            }
        }
        return pushed;
    }
        

    /**
     * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
     */
    public void comment(char[] arg0, int arg1, int arg2) throws SAXException
    {
        flushPending();
        if (m_lexHandler != null)
            m_lexHandler.comment(arg0, arg1, arg2);
            
        if (m_tracer != null)            
            super.fireCommentEvent(arg0, arg1, arg2);
    }

    /**
     * @see org.xml.sax.ext.LexicalHandler#endCDATA()
     */
    public void endCDATA() throws SAXException
    {
        /* Normally we would do somthing with this but we ignore it.
         * The neccessary call to m_lexHandler.endCDATA() will be made
         * in flushPending().
         * 
         * This is so that if we get calls like these:
         *   this.startCDATA();
         *   this.characters(chars1, off1, len1);
         *   this.endCDATA();
         *   this.startCDATA();
         *   this.characters(chars2, off2, len2);
         *   this.endCDATA();
         * 
         * that we will only make these calls to the wrapped handlers:
         * 
         *   m_lexHandler.startCDATA();
         *   m_saxHandler.characters(chars1, off1, len1);
         *   m_saxHandler.characters(chars1, off2, len2);
         *   m_lexHandler.endCDATA();
         * 
         * We will merge adjacent CDATA blocks.
         */ 
    }

    /**
     * @see org.xml.sax.ext.LexicalHandler#endDTD()
     */
    public void endDTD() throws SAXException
    {
        if (m_lexHandler != null)
            m_lexHandler.endDTD();
    }

    /**
     * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
     */
    public void startEntity(String arg0) throws SAXException
    {
        if (m_lexHandler != null)
            m_lexHandler.startEntity(arg0);
    }

    /**
     * @see ExtendedContentHandler#characters(String)
     */
    public void characters(String chars) throws SAXException
    {
        final int length = chars.length();
        if (length > m_charsBuff.length)
        {
            m_charsBuff = new char[length*2 + 1];
        }
        chars.getChars(0, length, m_charsBuff, 0);
        this.characters(m_charsBuff, 0, length); 
    }

    public ToXMLSAXHandler(ContentHandler handler, String encoding)
    {
        super(handler, encoding);

        initCDATA();
        // initNamespaces();
        m_prefixMap = new NamespaceMappings();
    }

    public ToXMLSAXHandler(
        ContentHandler handler,
        LexicalHandler lex,
        String encoding)
    {
        super(handler, lex, encoding);

        initCDATA();
        //      initNamespaces();
        m_prefixMap = new NamespaceMappings();
    }

    /**
     * Start an element in the output document. This might be an XML element
     * (<elem>data</elem> type) or a CDATA section.
     */
    public void startElement(
    String elementNamespaceURI,
    String elementLocalName,
    String elementName) throws SAXException
    {
        startElement(
            elementNamespaceURI,elementLocalName,elementName, null);


    }
    public void startElement(String elementName) throws SAXException
    {
        startElement(null, null, elementName, null);
    }


    public void characters(char[] ch, int off, int len) throws SAXException
    {
        // We do the first two things in flushPending() but we don't
        // close any open CDATA calls.        
        if (m_needToCallStartDocument)
        {
            startDocumentInternal();
            m_needToCallStartDocument = false;
        }

        if (m_elemContext.m_startTagOpen)
        {
            closeStartTag();
            m_elemContext.m_startTagOpen = false;
        }

        if (m_elemContext.m_isCdataSection && !m_cdataTagOpen
        && m_lexHandler != null) 
        {
            m_lexHandler.startCDATA();
            // We have made a call to m_lexHandler.startCDATA() with
            // no balancing call to m_lexHandler.endCDATA()
            // so we set m_cdataTagOpen true to remember this.
            m_cdataTagOpen = true;
        }
        
        /* If there are any occurances of "]]>" in the character data
         * let m_saxHandler worry about it, we've already warned them with
         * the previous call of m_lexHandler.startCDATA();
         */ 
        m_saxHandler.characters(ch, off, len);

        // time to generate characters event
        if (m_tracer != null)
            fireCharEvent(ch, off, len);
    }
    

    /**
     * @see ExtendedContentHandler#endElement(String)
     */
    public void endElement(String elemName) throws SAXException
    {
        endElement(null, null, elemName);
    }    


    /**
     * Send a namespace declaration in the output document. The namespace
     * declaration will not be include if the namespace is already in scope
     * with the same prefix.
     */
    public void namespaceAfterStartElement(
        final String prefix,
        final String uri)
        throws SAXException
    {
        startPrefixMapping(prefix,uri,false);
    }

    /**
     *
     * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
     * Send a processing instruction to the output document
     */
    public void processingInstruction(String target, String data)
        throws SAXException
    {
        flushPending();

        // Pass the processing instruction to the SAX handler
        m_saxHandler.processingInstruction(target, data);

        // we don't want to leave serializer to fire off this event,
        // so do it here.
        if (m_tracer != null)
            super.fireEscapingEvent(target, data);
    }

    /**
     * Undeclare the namespace that is currently pointed to by a given
     * prefix. Inform SAX handler if prefix was previously mapped.
     */
    protected boolean popNamespace(String prefix)
    {
        try
        {
            if (m_prefixMap.popNamespace(prefix))
            {
                m_saxHandler.endPrefixMapping(prefix);
                return true;
            }
        }
        catch (SAXException e)
        {
            // falls through
        }
        return false;
    }

    public void startCDATA() throws SAXException
    {
        /* m_cdataTagOpen can only be true here if we have ignored the
         * previous call to this.endCDATA() and the previous call 
         * this.startCDATA() before that is still "open". In this way
         * we merge adjacent CDATA. If anything else happened after the 
         * ignored call to this.endCDATA() and this call then a call to 
         * flushPending() would have been made which would have
         * closed the CDATA and set m_cdataTagOpen to false.
         */
        if (!m_cdataTagOpen ) 
        {
            flushPending();
            if (m_lexHandler != null) {
                m_lexHandler.startCDATA();

                // We have made a call to m_lexHandler.startCDATA() with
                // no balancing call to m_lexHandler.endCDATA()
                // so we set m_cdataTagOpen true to remember this.                
                m_cdataTagOpen = true;     
            }              
        }        
    }

    /**
     * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
     */
    public void startElement(
    String namespaceURI,
    String localName,
    String name,
    Attributes atts)
        throws SAXException
    {
        flushPending();
        super.startElement(namespaceURI, localName, name, atts);

        // Handle document type declaration (for first element only)
         if (m_needToOutputDocTypeDecl)
         {
             String doctypeSystem = getDoctypeSystem();
             if (doctypeSystem != null && m_lexHandler != null)
             {
                 String doctypePublic = getDoctypePublic();
                 if (doctypeSystem != null)
                     m_lexHandler.startDTD(
                         name,
                         doctypePublic,
                         doctypeSystem);
             }
             m_needToOutputDocTypeDecl = false;
         }
        m_elemContext = m_elemContext.push(namespaceURI, localName, name);

        // ensurePrefixIsDeclared depends on the current depth, so
        // the previous increment is necessary where it is.
        if (namespaceURI != null)
            ensurePrefixIsDeclared(namespaceURI, name);

        // add the attributes to the collected ones
        if (atts != null)
            addAttributes(atts);

         
        // do we really need this CDATA section state?
        m_elemContext.m_isCdataSection = isCdataSection();
   
    }
 
    private void ensurePrefixIsDeclared(String ns, String rawName)
        throws org.xml.sax.SAXException
    {

        if (ns != null && ns.length() > 0)
        {
            int index;
            final boolean no_prefix = ((index = rawName.indexOf(":")) < 0);
            String prefix = (no_prefix) ? "" : rawName.substring(0, index);


            if (null != prefix)
            {
                String foundURI = m_prefixMap.lookupNamespace(prefix);

                if ((null == foundURI) || !foundURI.equals(ns))
                {
                    this.startPrefixMapping(prefix, ns, false);

                    if (getShouldOutputNSAttr()) {
                        // Bugzilla1133: Generate attribute as well as namespace event.
                        // SAX does expect both.
                        this.addAttributeAlways(
                            "http://www.w3.org/2000/xmlns/",
                            no_prefix ? "xmlns" : prefix,  // local name
                            no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname
                            "CDATA",
                            ns,
                            false);
                    }
                }

            }
        }
    }
    /**
     * Adds the given attribute to the set of attributes, and also makes sure
     * that the needed prefix/uri mapping is declared, but only if there is a
     * currently open element.
     * 
     * @param uri the URI of the attribute
     * @param localName the local name of the attribute
     * @param rawName    the qualified name of the attribute
     * @param type the type of the attribute (probably CDATA)
     * @param value the value of the attribute
     * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
     * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
     */
    public void addAttribute(
        String uri,
        String localName,
        String rawName,
        String type,
        String value,
        boolean XSLAttribute)
        throws SAXException
    {      
        if (m_elemContext.m_startTagOpen)
        {
            ensurePrefixIsDeclared(uri, rawName);
            addAttributeAlways(uri, localName, rawName, type, value, false);
        }

    } 
       
    /**
     * Try's to reset the super class and reset this class for 
     * re-use, so that you don't need to create a new serializer 
     * (mostly for performance reasons).
     * 
     * @return true if the class was successfuly reset.
     * @see Serializer#reset()
     */
    public boolean reset()
    {
        boolean wasReset = false;
        if (super.reset())
        {
            resetToXMLSAXHandler();
            wasReset = true;
        }
        return wasReset;
    }
    
    /**
     * Reset all of the fields owned by ToXMLSAXHandler class
     *
     */
    private void resetToXMLSAXHandler()
    {
        this.m_escapeSetting = true;
    }  

}
