/* Woodstox Lite ("wool") XML processor
 *
 * Copyright (c) 2006- Tatu Saloranta, tatu.saloranta@iki.fi
 *
 * Licensed under the License specified in the file LICENSE which is
 * included with the source code.
 * You may not use this file except in compliance with the License.
 *
 * 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 com.fasterxml.aalto.in;

import javax.xml.namespace.QName;

/**
 * Prefixed Name is similar to {@link javax.xml.namespace.QName} (qualified name),
 * but only contains information about local name optionally prefixed by
 * a prefix and colon, without namespace binding information.
 */
public abstract class PName
{
    protected final String _prefixedName;
    protected final String _prefix;
    protected final String _localName;

    /**
     * Binding of this qualified/prefixed name. Null if there is no
     * prefix; in which case name is either bound to the default namespace
     * (when element name), or no namespace (when other name, like attribute)
     */
    protected NsBinding _namespaceBinding = null;

    /*
    /**********************************************************************
    /* Life-cycle
    /**********************************************************************
     */

    protected PName(String pname, String prefix, String ln)
    {
        _prefixedName = pname;
        _prefix = prefix;
        _localName = ln;
    }

    public abstract PName createBoundName(NsBinding nsb);

    /*
      // 26-Jun-2006, TSa: Doesn't seem to be needed any more...
    protected void bind(NsBinding nsb)
    {
        if (mNsBinding != null) { // !!! Temporary assertion
            throw new RuntimeException("Trying to re-set binding (for '"+getPrefixedName()+"'), was: "+mNsBinding+", new: "+nsb);
        }
        mNsBinding = nsb;
    }
    */

    /*
    /**********************************************************************
    /* Accessors
    /**********************************************************************
     */

    public final String getPrefixedName() { return _prefixedName; }

    /**
     * @return Prefix of this name, if it has one; null if not.
     */
    public final String getPrefix() { return _prefix; }
    public final String getLocalName() { return _localName; }

    public boolean hasPrefix() { return _prefix != null; }
    public boolean hasPrefixedName(String n) { return _prefixedName.equals(n); }

    public final NsBinding getNsBinding() { return _namespaceBinding; }

    public final String getNsUri() {
        return (_namespaceBinding == null) ? null : _namespaceBinding.mURI;
    }

    public final QName constructQName()
    {
        String pr = _prefix;
        String uri = (_namespaceBinding == null) ? null : _namespaceBinding.mURI;
        // Stupid QName: some impls barf on nulls...
        return new QName((uri == null) ? "" : uri,
                         _localName,
                         (pr == null) ? "" : pr);
    }

    /**
     * Method called to construct a QName representation of elemented
     * represented by this PName. Because of namespace defaulting,
     * current default namespace binding also needs to be passed
     * (since only explicit ones get bound to PName instances).
     */
    public final QName constructQName(NsBinding defaultNs)
    {
        String pr = _prefix;
        if (pr == null) { // QName barfs on nulls
            pr = "";
        }
        // Do we have a local binding?
        if (_namespaceBinding != null) {
            String uri = _namespaceBinding.mURI;
            if (uri != null) { // yup
                return new QName(uri,  _localName, pr);
            }
        }
        // Nope. Default ns?
        String uri = defaultNs.mURI;
        return new QName((uri == null) ? "" : uri,  _localName, pr);
    }

    /*
    /**********************************************************************
    /* Namespace binding
    /**********************************************************************
     */

    /**
     * @return True if the name has no binding object, but will need
     *    one (has prefix)
     */
    public final boolean needsBinding() {
        return (_prefix != null) && (_namespaceBinding == null);
    }

    /**
     * @return True if the name as described either has no prefix (either
     *    belongs to the default ns [elems], or to 'no namespace' [attrs]),
     *    or has a prefix that is bound currently. False if name has a prefix
     *    that is unbound.
     */
    public final boolean isBound()
    {
        return (_namespaceBinding == null) || (_namespaceBinding.mURI != null);
    }

    /**
     * Method that compares two bound PNames for semantic equality. This
     * means that the local name, as well as bound URI are compared.
     */
    public final boolean boundEquals(PName other)
    {
        if (other == null || other._localName != _localName) {
            return false;
        }
        // Let's assume URIs are canonicalized at least on per-doc basis?
        return other.getNsUri() == getNsUri();
    }

    public final boolean unboundEquals(PName other)
    {
        return (other._prefixedName == _prefixedName);
    }

    public final boolean boundEquals(String nsUri, String ln)
    {
        if (!_localName.equals(ln)) {
            return false;
        }
        String thisUri = getNsUri();
        if (nsUri == null || nsUri.length() == 0) {
            return (thisUri == null);
        }
        return nsUri.equals(thisUri);
    }

    public final int unboundHashCode()
    {
        return _prefixedName.hashCode();
    }

    public final int boundHashCode()
    {
        /* How often do we have same local name, but differing URI?
         * Probably not often... thus, let's only use local name's hash.
         */
        return _localName.hashCode();
    }

    public static int boundHashCode(String nsURI, String localName)
    {
        return localName.hashCode();
    }

    /*
    /**********************************************************************
    /* Redefined standard methods
    /**********************************************************************
     */

    @Override
    public final String toString() { return _prefixedName; }

    @Override
    public int hashCode() {
        return _prefixedName.hashCode();
    }
    
    @Override
    public final boolean equals(Object o)
    {
        if (o == this) {
            return true;
        }
        if (!(o instanceof PName)) {
            return false;
        }
        PName other = (PName) o;
        /* Only prefix and ln are interned, not the full prefixed name...
         * so let's compare separately. Can use identity comparison with
         * those though:
         */
        return (other._prefix == _prefix) && (other._localName == _localName);
    }

    /*
    /**********************************************************************
    /* Methods for package/core parser
    /**********************************************************************
     */

    /* Note: These 3 methods really should be in the byte-based sub-class...
     * but there are performance reasons to keep there, to remove
     * some otherwise necessary casts.
     */

    public abstract int sizeInQuads();

    public abstract int getFirstQuad();

    public abstract int getQuad(int index);

    public abstract int getLastQuad();
}
