package com.fasterxml.aalto.dom;

import javax.xml.namespace.NamespaceContext;

import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Context object that holds information about an open element
 * (one for which START_ELEMENT has been sent, but no END_ELEMENT)
 *
 * @author Tatu Saloranta
 */
public class DOMOutputElement
    extends OutputElementBase
{
    /**
     * Reference to the parent element, element enclosing this element.
     * Null for root element.
     * Non-final to allow temporary pooling
     * (on per-writer basis, to keep these short-lived).
     */
    private DOMOutputElement _parent;
    
    /**
     * Actual DOM element for which this element object acts as a proxy.
     */
    private Element _element;

    private boolean _defaultNsSet;

    /**
     * Constructor for the virtual root element
     */
    private DOMOutputElement()
    {
        super();
        _parent = null;
        _element = null;
        _nsMapping = null;
        _nsMapShared = false;
        _defaultNsURI = "";
        _rootNsContext = null;
        _defaultNsSet = false;
    }

    private DOMOutputElement(DOMOutputElement parent,
                             Element element,
                             BijectiveNsMap ns)
    {
        super(parent, ns);
        _parent = parent;
        _element = element;
        _nsMapping = ns;
        _nsMapShared = (ns != null);
        _defaultNsURI = parent._defaultNsURI;
        _rootNsContext = parent._rootNsContext;
        _defaultNsSet = false;
    }

    /**
     * Method called to reuse a pooled instance.
     */
    private void relink(DOMOutputElement parent, Element element)
    {
        super.relink(parent);
        _parent = parent;
        _element = element;
        parent.appendNode(element);
        _defaultNsSet = false;
    }

    public static DOMOutputElement createRoot()
    {
        return new DOMOutputElement();
    }
    
    /**
     * Simplest factory method, which gets called when a 1-argument
     * element output method is called. Element is assumed to
     * use the current default namespace.
     * Will both create the child element and attach it to parent element,
     * or lacking own owner document.
     */
    protected DOMOutputElement createAndAttachChild(Element element)
    {
        if(isRoot()) {
            element.getOwnerDocument().appendChild(element);
        } else {
            _element.appendChild(element);
        }
        return createChild(element);
    }

    protected DOMOutputElement createChild(Element element)
    {
        return new DOMOutputElement(this, element, _nsMapping);
    }

    /**
     * @return New head of the recycle pool
     */
    protected DOMOutputElement reuseAsChild(DOMOutputElement parent,
                                               Element element)
    {
        DOMOutputElement poolHead = _parent;
        relink(parent, element);
        return poolHead;
    }

    /**
     * Method called to temporarily link this instance to a pool, to
     * allow reusing of instances with the same reader.
     */
    protected void addToPool(DOMOutputElement poolHead)
    {
        _parent = poolHead;
    }
    
    /*
    ////////////////////////////////////////////
    // Public API, accessors
    ////////////////////////////////////////////
     */

    public DOMOutputElement getParent() {
        return _parent;
    }

    @Override
    public boolean isRoot() {
        // (Virtual) Root element has no parent...
        return (_parent == null);
    }
    
    /**
     * @return String presentation of the fully-qualified name, in
     *   "prefix:localName" format (no URI). Useful for error and
     *   debugging messages.
     */
    @Override
    public String getNameDesc() {
        if(_element != null) {
            return _element.getLocalName();
        }
        return "#error"; // unexpected case
    }
    
    /*
    ////////////////////////////////////////////
    // Public API, mutators
    ////////////////////////////////////////////
     */

    @Override
    public void setDefaultNsUri(String uri) {
        _defaultNsURI = uri;
        _defaultNsSet = true;
    }

    @Override
    protected void setRootNsContext(NamespaceContext ctxt)
    {
        _rootNsContext = ctxt;
        /* Let's also see if we have an active default ns mapping:
         * (provided it hasn't yet explicitly been set for this element)
         */
        if (!_defaultNsSet) {
            String defURI = ctxt.getNamespaceURI("");
            if (defURI != null && defURI.length() > 0) {
                _defaultNsURI = defURI;
            }
        }
    }

    /*
    ////////////////////////////////////////////
    // Public API, DOM manipulation
    ////////////////////////////////////////////
     */
    
    protected void appendNode(Node n)
    {
        if (isRoot()) {
            _element.getOwnerDocument().appendChild(n);
        } else {
            _element.appendChild(n);
        }
    }

    protected void addAttribute(String pname, String value)
    {
        _element.setAttribute(pname, value);
    }

    protected void addAttribute(String uri, String qname, String value)
    {
        _element.setAttributeNS(uri, qname, value);
    }

    public void appendChild(Node n) {
        _element.appendChild(n);
    }
}
