/*
 * Copyright (c) 1998, 2018 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 v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
// mmacivor - June 24/2009 - 1.0 - Initial implementation
package org.eclipse.persistence.oxm.record;

import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.NamespaceResolver;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.record.DomToXMLStreamWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * <p>Use this type of MarshalRecord when the marshal target is an XMLStreamWriter
 * <p><code>
 * XMLContext xmlContext = new XMLContext("session-name");<br>
 * XMLMarshaller xmlMarshaller = xmlContext.createMarshaller();<br>
 * XMLStreamWriterRecord writerRecord = new XMLStreamWriterRecord(xmlStreamWriter);<br>
 * xmlMarshaller.marshal(myObject, writerRecord);<br>
 * </code>
 * @see org.eclipse.persistence.oxm.XMLMarshaller
 */
public class XMLStreamWriterRecord extends MarshalRecord {

    private DomToXMLStreamWriter domToStreamWriter;
    private Map<String, String> prefixMapping;
    private NamespaceResolver namespaceResolver;
    private XMLStreamWriter xmlStreamWriter;

    public XMLStreamWriterRecord(XMLStreamWriter xmlStreamWriter) {
        this.xmlStreamWriter = xmlStreamWriter;
    }

    public XMLStreamWriter getXMLStreamWriter() {
        return xmlStreamWriter;
    }

    public void setXMLStreamWriter(XMLStreamWriter anXMLStreamWriter) {
        this.xmlStreamWriter = anXMLStreamWriter;
    }

    private DomToXMLStreamWriter getDomToXMLStreamWriter() {
        if(null == domToStreamWriter) {
            domToStreamWriter = new DomToXMLStreamWriter();
        }
        return domToStreamWriter;
    }

    public void attribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, String value) {
        try {
            String namespaceURI = xPathFragment.getNamespaceURI();
            if(namespaceURI == null) {
                xmlStreamWriter.writeAttribute(xPathFragment.getLocalName(), value);

            } else {
                String prefix = getPrefixForFragment(xPathFragment);
                if(prefix == null) {
                    xmlStreamWriter.writeAttribute(namespaceURI, xPathFragment.getLocalName(), value);
                } else {
                    xmlStreamWriter.writeAttribute(prefix, namespaceURI, xPathFragment.getLocalName(), value);
                }
            }
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void defaultNamespaceDeclaration(String defaultNamespace){
        try{
            xmlStreamWriter.writeDefaultNamespace(defaultNamespace);
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void namespaceDeclaration(String prefix, String namespaceURI){
        try{
            xmlStreamWriter.writeNamespace(prefix, namespaceURI);
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void attributeWithoutQName(String namespaceURI, String localName, String prefix, String value){
        try {
            if(namespaceURI == null || namespaceURI.length() == 0) {
                xmlStreamWriter.writeAttribute(localName, value);
            } else {
                xmlStreamWriter.writeAttribute(xmlStreamWriter.getNamespaceContext().getPrefix(namespaceURI), namespaceURI, localName, value);
            }
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void attribute(String namespaceURI, String localName, String name, String value) {
        try {
             if(namespaceURI == null || namespaceURI.length() == 0) {
                 xmlStreamWriter.writeAttribute(localName, value);
             } else {
                xmlStreamWriter.writeAttribute(xmlStreamWriter.getNamespaceContext().getPrefix(namespaceURI), namespaceURI, localName, value);
             }
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void cdata(String value) {
        try {
            for (String part : MarshalRecord.splitCData(value)) {
                xmlStreamWriter.writeCData(part);
            }
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void characters(String value) {
        try {
            xmlStreamWriter.writeCharacters(value);
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public boolean isNamespaceAware() {
        return true;
    }

    public void closeStartElement() {
    }

    public void openStartElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
        super.openStartElement(xPathFragment, namespaceResolver);
        try {
            String namespaceURI = xPathFragment.getNamespaceURI();
            if(namespaceURI == null) {
                NamespaceContext namespaceContext = xmlStreamWriter.getNamespaceContext();
                if(null == namespaceContext) {
                    xmlStreamWriter.writeStartElement(xPathFragment.getLocalName());
                } else {
                    String defaultNamespace = namespaceContext.getNamespaceURI(Constants.EMPTY_STRING);
                    xmlStreamWriter.writeStartElement(Constants.EMPTY_STRING, xPathFragment.getLocalName(), Constants.EMPTY_STRING);
                    if(defaultNamespace != null && defaultNamespace.length() > 0 ) {
                        xmlStreamWriter.writeDefaultNamespace(Constants.EMPTY_STRING);
                    }
                }
            } else {
                String prefix = getPrefixForFragment(xPathFragment);
                if(prefix == null) {
                    prefix = Constants.EMPTY_STRING;
                }
                xmlStreamWriter.writeStartElement(prefix, xPathFragment.getLocalName(), namespaceURI);
                if(xPathFragment.isGeneratedPrefix()){
                    namespaceDeclaration(xPathFragment.getPrefix(), xPathFragment.getNamespaceURI());
                }

            }
            writePrefixMappings();
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void element(XPathFragment frag) {
        try {
            xmlStreamWriter.writeStartElement(getNameForFragment(frag));
            xmlStreamWriter.writeEndElement();
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void endDocument() {
        try {
            xmlStreamWriter.writeEndDocument();
            xmlStreamWriter.flush();
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void endElement(XPathFragment pathFragment, NamespaceResolver namespaceResolver) {
        try {
            xmlStreamWriter.writeEndElement();
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void node(Node node, NamespaceResolver resolver,String uri, String name) {
        try {
            if(node.getNodeType() == Node.DOCUMENT_NODE) {
                node = ((Document)node).getDocumentElement();
            }
            getDomToXMLStreamWriter().writeToStream(node, uri, name , this.xmlStreamWriter);
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void startDocument(String encoding, String version) {
        try {
            if(Constants.DEFAULT_XML_ENCODING.equals(encoding)) {
                xmlStreamWriter.writeStartDocument(version);
            } else {
                xmlStreamWriter.writeStartDocument(encoding, version);
            }
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void startPrefixMapping(String prefix, String namespaceUri) {
        if(null == this.prefixMapping) {
            this.prefixMapping = new HashMap<String, String>();
        }
        this.prefixMapping.put(prefix, namespaceUri);
    }

    private void writePrefixMappings() {
        try {
            if(null != namespaceResolver) {
                String defaultNamespace = namespaceResolver.getDefaultNamespaceURI();
                if(defaultNamespace != null) {
                    xmlStreamWriter.writeNamespace(Constants.EMPTY_STRING, defaultNamespace);
                }
                if(namespaceResolver.hasPrefixesToNamespaces()) {
                    for(Map.Entry<String, String> entry:this.namespaceResolver.getPrefixesToNamespaces().entrySet()) {
                        xmlStreamWriter.writeNamespace(entry.getKey(), entry.getValue());
                    }
                }
                namespaceResolver = null;
            }
            if(null != prefixMapping) {
                for(Map.Entry<String, String> entry:this.prefixMapping.entrySet()) {
                    xmlStreamWriter.writeNamespace(entry.getKey(), entry.getValue());
                }
                prefixMapping = null;
            }
        } catch(XMLStreamException e) {
            throw XMLMarshalException.marshalException(e);
        }
    }

    public void namespaceDeclarations(NamespaceResolver namespaceResolver) {
    }

    public void startPrefixMappings(NamespaceResolver namespaceResolver) {
        this.namespaceResolver = namespaceResolver;
    }

}
