/**
 * 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.
 */
package org.apache.cxf.aegis.xml.stax;

import java.io.OutputStream;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.apache.cxf.aegis.DatabindingException;
import org.apache.cxf.aegis.xml.AbstractMessageWriter;
import org.apache.cxf.aegis.xml.MessageWriter;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.staxutils.StaxUtils;

public class ElementWriter extends AbstractMessageWriter {
    private XMLStreamWriter writer;

    private String namespace;
    private String name;
    private String prefix;

    /**
     * Create an ElementWriter but without writing an element name.
     *
     * @param writer
     */
    public ElementWriter(XMLStreamWriter writer) {
        this.writer = writer;
    }

    public ElementWriter(XMLStreamWriter writer, String name, String namespace) {
        this(writer, name, namespace, null);
    }

    public ElementWriter(XMLStreamWriter streamWriter, QName name) {
        this(streamWriter, name.getLocalPart(), name.getNamespaceURI());
    }

    public ElementWriter(XMLStreamWriter writer, String name, String namespace, String prefix) {
        this.writer = writer;
        this.namespace = namespace;
        this.name = name;
        this.prefix = prefix;

        try {
            writeStartElement();
        } catch (XMLStreamException e) {
            throw new DatabindingException("Error writing document.", e);
        }
    }

    /**
     * @param os
     * @throws XMLStreamException
     */
    public ElementWriter(OutputStream os, String name, String namespace) throws XMLStreamException {
        XMLOutputFactory ofactory = XMLOutputFactory.newInstance();
        this.writer = ofactory.createXMLStreamWriter(os);

        this.namespace = namespace;
        this.name = name;

        try {
            writeStartElement();
        } catch (XMLStreamException e) {
            throw new DatabindingException("Error writing document.", e);
        }
    }

    private void writeStartElement() throws XMLStreamException {
        if (!StringUtils.isEmpty(namespace)) {
            boolean declare = false;
            // Did the user declare a prefix?
            String decPrefix = writer.getNamespaceContext().getPrefix(namespace);

            // If the user didn't specify a prefix, create one
            if (StringUtils.isEmpty(prefix)
                && decPrefix == null) {

                if (!StringUtils.isEmpty(namespace)) {
                    declare = true;
                    prefix = StaxUtils.getUniquePrefix(writer);
                } else {
                    prefix = "";
                    if (!StringUtils.isEmpty(writer.getNamespaceContext().getNamespaceURI(""))) {
                        declare = true;
                    }
                }
            } else if (StringUtils.isEmpty(prefix)) {
                prefix = decPrefix;
            } else if (!prefix.equals(decPrefix)) {
                declare = true;
            }

            writer.writeStartElement(prefix, name, namespace);

            if (declare) {
                writer.setPrefix(prefix, namespace);
                writer.writeNamespace(prefix, namespace);
            }
        } else {
            writer.writeStartElement(name);
        }
    }

    /**
     * @see org.apache.cxf.aegis.xml.MessageWriter#writeValue(java.lang.Object)
     */
    public void writeValue(Object value) {
        try {
            if (value != null) {
                writer.writeCharacters(value.toString());
            }
        } catch (XMLStreamException e) {
            throw new DatabindingException("Error writing document.", e);
        }
    }

    /**
     * @see org.apache.cxf.aegis.xml.MessageWriter#getWriter(java.lang.String)
     */
    public MessageWriter getElementWriter(String nm) {
        return new ElementWriter(writer, nm, namespace);
    }

    public MessageWriter getElementWriter(String nm, String ns) {
        return new ElementWriter(writer, nm, ns);
    }

    public MessageWriter getElementWriter(QName qname) {
        /*
         * No one really wants xmlns= in their XML, prefixes are preferred.
         * If the input qname has no prefix, go ahead and use the constructor that will
         * generate one.
         */
        if ("".equals(qname.getPrefix())) {
            return new ElementWriter(writer, qname.getLocalPart(), qname.getNamespaceURI());
        }
        return new ElementWriter(writer, qname.getLocalPart(),
                                 qname.getNamespaceURI(),
                                 qname.getPrefix());
    }

    public String getNamespace() {
        return namespace;
    }

    public void close() {
        try {
            writer.writeEndElement();
        } catch (XMLStreamException e) {
            throw new DatabindingException("Error writing document.", e);
        }
    }

    public void flush() throws XMLStreamException {
        writer.flush();
    }

    public XMLStreamWriter getXMLStreamWriter() {
        return writer;
    }

    public MessageWriter getAttributeWriter(String nm) {
        return new AttributeWriter(writer, nm, namespace);
    }

    public MessageWriter getAttributeWriter(String nm, String ns) {
        return new AttributeWriter(writer, nm, ns);
    }

    public MessageWriter getAttributeWriter(QName qname) {
        return new AttributeWriter(writer, qname.getLocalPart(), qname.getNamespaceURI());
    }

    public String getPrefixForNamespace(String ns) {
        try {
            String pfx = writer.getPrefix(ns);

            if (pfx == null) {
                pfx = StaxUtils.getUniquePrefix(writer);

                writer.setPrefix(pfx, ns);
                writer.writeNamespace(pfx, ns);
            }

            return prefix;
        } catch (XMLStreamException e) {
            throw new DatabindingException("Error writing document.", e);
        }
    }

    public String getPrefixForNamespace(String ns, String hint) {
        try {
            String pfx = writer.getPrefix(ns);
            String contextPfx = writer.getNamespaceContext().getPrefix(ns);

            if (pfx == null) {
                String ns2 = writer.getNamespaceContext().getNamespaceURI(hint);
                // if the hint is "" (the default) and the context does
                if (ns2 == null && !"".equals(hint)) {
                    pfx = hint;
                } else if (ns.equals(ns2)) {
                    // just because it's in the context, doesn't mean it has been written.
                    pfx = hint;
                } else if (contextPfx != null) {
                    pfx = contextPfx;
                } else {
                    pfx = StaxUtils.getUniquePrefix(writer);
                }

                writer.setPrefix(pfx, ns);
                writer.writeNamespace(pfx, ns);
            }

            return pfx;
        } catch (XMLStreamException e) {
            throw new DatabindingException("Error writing document.", e);
        }
    }
}
