/*******************************************************************************
 * Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/  
package org.eclipse.persistence.internal.oxm;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.persistence.internal.oxm.record.UnmarshalRecord;
import org.eclipse.persistence.platform.xml.XMLPlatform;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.eclipse.persistence.platform.xml.SAXDocumentBuilder;

/**
 *  @version $Header: SAXFragmentBuilder.java 18-sep-2007.14:36:11 dmahar Exp $
 *  @author  mmacivor
 *  @since   release specific (what release of product did this appear in)
 */
public class SAXFragmentBuilder extends SAXDocumentBuilder {
    private UnmarshalRecord owningRecord;
    private boolean mixedContent;

    public SAXFragmentBuilder(UnmarshalRecord unmarshalRecord) {
        super();
        owningRecord = unmarshalRecord;
    }

    public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
        if (!mixedContent) {
            boolean bufferContainsOnlyWhitespace = stringBuffer.toString().trim().length() == 0;
            if (bufferContainsOnlyWhitespace) {
                stringBuffer.reset();
            }
        }

        if ((stringBuffer.length() > 0) && !(nodes.size() == 1)) {
            Text text = getInitializedDocument().createTextNode(stringBuffer.toString());
            Node parent = this.nodes.get(nodes.size() - 1);
            parent.appendChild(text);
            processNamespacesForText(text.getTextContent(), (Element)parent);
            stringBuffer.reset();
        }
        if (null != namespaceURI && namespaceURI.length() == 0) {
            namespaceURI = null;
        }
        if(qName == null){
            qName = localName;
            if(namespaceURI != null){
                if(owningRecord != null){
                    String prefix = owningRecord.resolveNamespaceUri(namespaceURI);
                    if(prefix != null && prefix.length() > 0){
                        qName = prefix +Constants.COLON+ qName;
                    }
                }
             }
        }
        int qNameColonIndex = qName.indexOf(Constants.COLON);
        if ((namespaceURI != null) && (qNameColonIndex == -1)) {
            //check for a prefix from the unmarshal record:
            String prefix = owningRecord.resolveNamespaceUri(namespaceURI);
            if (prefix != null && prefix.length() >0){
                qName = prefix + Constants.COLON + qName;
                qNameColonIndex = prefix.length();
            }
        }

        Element element = getInitializedDocument().createElementNS(namespaceURI, qName);
        Node parentNode = nodes.get(nodes.size() - 1);

        appendChildNode(parentNode, element);
        nodes.add(element);

        if (qNameColonIndex > -1) {
            String prefix = qName.substring(0, qNameColonIndex);
            String parentUri = null;
            if (element.getParentNode() != null) {
                parentUri = XMLPlatformFactory.getInstance().getXMLPlatform().resolveNamespacePrefix(element.getParentNode(), prefix);
            }
            if ((parentUri == null) || parentUri.length() == 0) {
                startPrefixMapping(prefix, namespaceURI);
            }
        }

        if (null != namespaceDeclarations) {
            Iterator namespaces = namespaceDeclarations.entrySet().iterator();
            while (namespaces.hasNext()) {
            	Map.Entry entry = (Map.Entry)namespaces.next();
                addNamespaceDeclaration(element, (String)entry.getKey(), (String)entry.getValue());
            }
            namespaceDeclarations = null;
        }

        int numberOfAttributes = atts.getLength();
        String attributeNamespaceURI, attributeQName, attributeValue;
        for (int x = 0; x < numberOfAttributes; x++) {
            attributeNamespaceURI = atts.getURI(x);
            attributeQName = atts.getQName(x);
            attributeValue = atts.getValue(x);
            // Empty string will be treated as a null URI
            if (null != attributeNamespaceURI && attributeNamespaceURI.length() == 0) {
                attributeNamespaceURI = null;
            }
            // Handle case where prefix/uri are not set on an xmlns prefixed attribute
            if (attributeNamespaceURI == null && attributeQName.startsWith(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) {
                attributeNamespaceURI = javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
            }
            element.setAttributeNS(attributeNamespaceURI, attributeQName, attributeValue == null ? Constants.EMPTY_STRING : attributeValue);
            if (attributeValue != null) {
                processNamespacesForText(attributeValue, element);
            }
        }
    }

    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
        if (super.nodes.size() == 2) {
            Element endedElement = (Element)nodes.get(nodes.size() -1);
            if (stringBuffer.length() > 0) {
                Text text = getInitializedDocument().createTextNode(stringBuffer.toString());
                endedElement.appendChild(text);
                stringBuffer.reset();
                processNamespacesForText(text.getTextContent(), endedElement);
            }

            while(owningRecord.isSelfRecord() && owningRecord.getParentRecord() != null){
            	owningRecord = owningRecord.getParentRecord();
            }
            
            //just the doc left in the stack. Finish this off.
            owningRecord.getXMLReader().setContentHandler(owningRecord);
            owningRecord.endElement(namespaceURI, localName, qName);

        } else {
            super.endElement(namespaceURI, localName, qName);
        }
    }

    
    
    public void endSelfElement(String namespaceURI, String localName, String qName) throws SAXException {        
    	
    	if (super.nodes.size() == 2) {
            Element endedElement = (Element)nodes.get(nodes.size() -1);
            if (stringBuffer.length() > 0) {
                Text text = getInitializedDocument().createTextNode(stringBuffer.toString());
                endedElement.appendChild(text);
                stringBuffer.reset();
            }         
        } else {
            super.endElement(namespaceURI, localName, qName);
        }
    }
    public List<Node> getNodes() {
        return super.nodes;
    }

    public void setOwningRecord(UnmarshalRecord record) {
        this.owningRecord = record;
    }

    public void appendChildNode(Node parent, Node child) {
        if (parent != this.getDocument()) {
            parent.appendChild(child);
        }
    }

    public Attr buildAttributeNode(String namespaceURI, String localName, String value) {
        try {
            Attr attribute = getInitializedDocument().createAttributeNS(namespaceURI, localName);
            attribute.setValue(value);
            return attribute;
        } catch (SAXException ex) {
        }
        return null;
    }

    public Text buildTextNode(String textValue) {
        try {
            Text text = getInitializedDocument().createTextNode(textValue);
            return text;
        } catch (SAXException ex) {
        }
        return null;

    } 
    
    /**
     * Adds a namespace declaration to the parent element if the textValue represents a
     * prefixed qualified name. The determination of a qname is based on the existance of a
     * colon character and the ability to resolve the characters before the colon to a 
     * namespace uri. 
     * @param textValue
     * @param parentNode
     */
    private void processNamespacesForText(String textValue, Element parentNode) {
        //If the text value is a qname, we may need to do namespace processing
        int colon = textValue.indexOf(':');
        if(colon != -1) {
            String prefix = textValue.substring(0, colon);
            XMLPlatform platform = XMLPlatformFactory.getInstance().getXMLPlatform();
            String uri = platform.resolveNamespacePrefix(parentNode, prefix);
            if(uri == null) {
                uri = this.owningRecord.resolveNamespacePrefix(prefix);
                if(uri != null) {
                    //add namespace declaration
                    addNamespaceDeclaration(parentNode, prefix, uri);
                }
            }
        }
    }

    public void setMixedContent(boolean mixedContent) {
        this.mixedContent = mixedContent;
    }
}