/*
 * Copyright (c) 2007 Henri Sivonen
 * Copyright (c) 2008-2010 Mozilla Foundation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */

package nu.validator.htmlparser.sax;

import nu.validator.htmlparser.impl.HtmlAttributes;
import nu.validator.htmlparser.impl.TreeBuilder;
import nu.validator.saxtree.Characters;
import nu.validator.saxtree.Comment;
import nu.validator.saxtree.DTD;
import nu.validator.saxtree.Document;
import nu.validator.saxtree.DocumentFragment;
import nu.validator.saxtree.Element;
import nu.validator.saxtree.Node;
import nu.validator.saxtree.ParentNode;

import org.xml.sax.SAXException;

class SAXTreeBuilder extends TreeBuilder<Element> {

    private static final char[] ISINDEX_PROMPT = "This is a searchable index. Enter search keywords: ".toCharArray();

    private Document document;

    private Node cachedTable = null;
    
    private Node cachedTablePreviousSibling = null;
    
    SAXTreeBuilder() {
        super();
    }
    
    @Override
    protected void appendComment(Element parent, char[] buf, int start, int length) {
        parent.appendChild(new Comment(tokenizer, buf, start, length));
    }

    @Override
    protected void appendCommentToDocument(char[] buf, int start, int length) {
        document.appendChild(new Comment(tokenizer, buf, start, length));
    }

    @Override
    protected void appendCharacters(Element parent, char[] buf, int start, int length) {
        parent.appendChild(new Characters(tokenizer, buf, start, length));
    }

    /**
     * @see nu.validator.htmlparser.impl.TreeBuilder#appendIsindexPrompt(java.lang.Object)
     */
    @Override protected void appendIsindexPrompt(Element parent)
            throws SAXException {
        parent.appendChild(new Characters(tokenizer, ISINDEX_PROMPT, 0, ISINDEX_PROMPT.length));
    }

    @Override
    protected boolean hasChildren(Element element) {
        return element.getFirstChild() != null;
    }

    @Override
    protected void appendElement(Element child, Element newParent) {
        newParent.appendChild(child);
    }

    @Override
    protected Element createHtmlElementSetAsRoot(HtmlAttributes attributes) {
        Element newElt = new Element(tokenizer, "http://www.w3.org/1999/xhtml", "html", "html", attributes, true, null);
        document.appendChild(newElt);
        return newElt;
    }

    @Override
    protected void addAttributesToElement(Element element, HtmlAttributes attributes) throws SAXException {
        HtmlAttributes existingAttrs = (HtmlAttributes) element.getAttributes();
        existingAttrs.merge(attributes);
    }

    /**
     * @see nu.validator.htmlparser.impl.TreeBuilder#appendDoctypeToDocument(java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    protected void appendDoctypeToDocument(String name, String publicIdentifier, String systemIdentifier) {
         DTD dtd = new DTD(tokenizer, name, publicIdentifier, systemIdentifier);
         dtd.setEndLocator(tokenizer);
         document.appendChild(dtd);
    }
    
    /**
     * Returns the document.
     * 
     * @return the document
     */
    Document getDocument() {
        Document rv = document;
        document = null;
        return rv;
    }
    
    DocumentFragment getDocumentFragment() {
        DocumentFragment rv = new DocumentFragment();
        rv.appendChildren(document.getFirstChild());
        document = null;
        return rv;
    }

    /**
     * @throws SAXException 
     * @see nu.validator.htmlparser.impl.TreeBuilder#end()
     */
    @Override
    protected void end() throws SAXException {
        document.setEndLocator(tokenizer);
        cachedTable = null;
        cachedTablePreviousSibling = null;
    }

    /**
     * @see nu.validator.htmlparser.impl.TreeBuilder#start()
     */
    @Override
    protected void start(boolean fragment) {
        document = new Document(tokenizer);
        cachedTable = null;
        cachedTablePreviousSibling = null;
    }

    @Override
    protected void appendChildrenToNewParent(Element oldParent, Element newParent) throws SAXException {
        newParent.appendChildren(oldParent);
    }

    @Override
    protected Element createElement(String ns, String name, HtmlAttributes attributes) throws SAXException {
        return new Element(tokenizer, ns, name, name, attributes, true, null);
    }

    @Override protected void insertFosterParentedCharacters(char[] buf,
            int start, int length, Element table, Element stackParent) throws SAXException {
        Node child = new Characters(tokenizer, buf, start, length);
        ParentNode parent = table.getParentNode();
        if (parent != null) { // always an element if not null
            parent.insertBetween(child, previousSibling(table), table);
            cachedTablePreviousSibling = child;
        } else {
            stackParent.appendChild(child);
        }        
    }

    @Override protected void insertFosterParentedChild(Element child,
            Element table, Element stackParent) throws SAXException {
        ParentNode parent = table.getParentNode();
        if (parent != null) { // always an element if not null
            parent.insertBetween(child, previousSibling(table), table);
            cachedTablePreviousSibling = child;
        } else {
            stackParent.appendChild(child);
        }
    }

    private Node previousSibling(Node table) {
        if (table == cachedTable) {
            return cachedTablePreviousSibling;   
        } else {
            cachedTable = table;
            return (cachedTablePreviousSibling = table.getPreviousSibling());
        }
    }

    @Override protected void detachFromParent(Element element)
            throws SAXException {
        element.detach();
    }
}
