/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2005-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.xml.txw2;

import javax.xml.namespace.QName;


/**
 * Start tag.
 *
 * <p>
 * This object implements {@link NamespaceResolver} for attribute values.
 *
 * @author Kohsuke Kawaguchi
 */
class StartTag extends Content implements NamespaceResolver {
    /**
     * Tag name of the element.
     *
     * <p>
     * This field is also used as a flag to indicate
     * whether the start tag has been written.
     * This field is initially set to non-null, and
     * then reset to null when it's written.
     */
    private String uri;
    // but we keep the local name non-null so that
    // we can diagnose an error
    private final String localName;

    private Attribute firstAtt;
    private Attribute lastAtt;

    /**
     * If this {@link StartTag} has the parent {@link ContainerElement},
     * that value. Otherwise null.
     */
    private ContainerElement owner;

    /**
     * Explicitly given namespace declarations on this element.
     *
     * <p>
     * Additional namespace declarations might be necessary to
     * generate child {@link QName}s and attributes.
     */
    private NamespaceDecl firstNs;
    private NamespaceDecl lastNs;

    final Document document;

    public StartTag(ContainerElement owner, String uri, String localName) {
        this(owner.document,uri,localName);
        this.owner = owner;
    }

    public StartTag(Document document, String uri, String localName) {
        assert uri!=null;
        assert localName!=null;

        this.uri = uri;
        this.localName = localName;
        this.document = document;

        // TODO: think about a better way to maintain namespace decls.
        // this requires at least one NamespaceDecl per start tag,
        // which is rather expensive.
        addNamespaceDecl(uri,null,false);
    }

    public void addAttribute(String nsUri, String localName, Object arg) {
        checkWritable();

        // look for the existing ones
        Attribute a;
        for(a=firstAtt; a!=null; a=a.next) {
            if(a.hasName(nsUri,localName)) {
                break;
            }
        }

        // if not found, declare a new one
        if(a==null) {
            a = new Attribute(nsUri,localName);
            if(lastAtt==null) {
                assert firstAtt==null;
                firstAtt = lastAtt = a;
            } else {
                assert firstAtt!=null;
                lastAtt.next = a;
                lastAtt = a;
            }
            if(nsUri.length()>0)
                addNamespaceDecl(nsUri,null,true);
        }

        document.writeValue(arg,this,a.value);
    }

    /**
     * Declares a new namespace URI on this tag.
     *
     * @param uri
     *      namespace URI to be bound. Can be empty, but must not be null.
     * @param prefix
     *      If non-null and non-empty, this prefix is bound to the URI
     *      on this element. If empty, then the runtime will still try to
     *      use the URI as the default namespace, but it may fail to do so
     *      because of the constraints in the XML.
     *      <p>
     *      If this parameter is null, the runtime will allocate an unique prefix.
     * @param requirePrefix
     *      Used only when the prefix parameter is null. If true, this indicates
     *      that the non-empty prefix must be assigned to this URI. If false,
     *      then this URI might be used as the default namespace.
     *      <p>
     *      Normally you just need to set it to false.
     */
    public NamespaceDecl addNamespaceDecl(String uri, String prefix,boolean requirePrefix) {
        checkWritable();

        if(uri==null)
            throw new IllegalArgumentException();
        if(uri.length()==0) {
            if(requirePrefix)
                throw new IllegalArgumentException("The empty namespace cannot have a non-empty prefix");
            if(prefix!=null && prefix.length()>0)
                throw new IllegalArgumentException("The empty namespace can be only bound to the empty prefix");
            prefix = "";
        }

        // check for the duplicate
        for(NamespaceDecl n=firstNs; n!=null; n=n.next) {
            if(uri.equals(n.uri)) {
                if(prefix==null) {
                    // reuse this binding
                    n.requirePrefix |= requirePrefix;
                    return n;
                }
                if(n.prefix==null) {
                    // reuse this binding
                    n.prefix = prefix;
                    n.requirePrefix |= requirePrefix;
                    return n;
                }
                if(prefix.equals(n.prefix)) {
                    // reuse this binding
                    n.requirePrefix |= requirePrefix;
                    return n;
                }
            }
            if(prefix!=null && n.prefix!=null && n.prefix.equals(prefix))
                throw new IllegalArgumentException(
                    "Prefix '"+prefix+"' is already bound to '"+n.uri+'\'');
        }

        NamespaceDecl ns = new NamespaceDecl(document.assignNewId(),uri,prefix,requirePrefix);
        if(lastNs==null) {
            assert firstNs==null;
            firstNs = lastNs = ns;
        } else {
            assert firstNs!=null;
            lastNs.next = ns;
            lastNs = ns;
        }
        return ns;
    }

    /**
     * Throws an error if the start tag has already been committed.
     */
    private void checkWritable() {
        if(isWritten())
            throw new IllegalStateException(
                "The start tag of "+this.localName+" has already been written. " +
                "If you need out of order writing, see the TypedXmlWriter.block method");
    }

    /**
     * Returns true if this start tag has already been written.
     */
    boolean isWritten() {
        return uri==null;
    }

    /**
     * A {@link StartTag} can be only written after
     * we are sure that all the necessary namespace declarations are given.
     */
    boolean isReadyToCommit() {
        if(owner!=null && owner.isBlocked())
            return false;

        for( Content c=getNext(); c!=null; c=c.getNext() )
            if(c.concludesPendingStartTag())
                return true;

        return false;
    }

    public void written() {
        firstAtt = lastAtt = null;
        uri = null;
        if(owner!=null) {
            assert owner.startTag==this;
            owner.startTag = null;
        }
    }

    boolean concludesPendingStartTag() {
        return true;
    }

    void accept(ContentVisitor visitor) {
        visitor.onStartTag(uri,localName,firstAtt,firstNs);
    }

    public String getPrefix(String nsUri) {
        NamespaceDecl ns = addNamespaceDecl(nsUri,null,false);
        if(ns.prefix!=null)
            // if the prefix has already been declared, use it.
            return ns.prefix;
        return ns.dummyPrefix;
    }
}
