/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2004-2008, Open Source Geospatial Foundation (OSGeo)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    This package contains documentation from OpenGIS specifications.
 *    OpenGIS consortium's work is fully acknowledged here.
 */
package org.geotools.util;

import java.util.Arrays;
import java.util.List;
import java.util.Locale; // For javadoc

import org.opengis.util.GenericName;
import org.opengis.util.InternationalString; // For javadoc
import org.opengis.util.LocalName;
import org.opengis.util.NameSpace;


/**
 * Fully qualified identifier for an object.
 * A {@code ScopedName} contains a {@link LocalName} as
 * {@linkplain #asLocalName head} and a {@linkplain GenericName},
 * which may be a {@link LocalName} or an other {@link org.opengis.util.ScopedName},
 * as {@linkplain #getScope tail}.
 *
 * @since 2.1
 *
 * @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/modules/library/metadata/src/main/java/org/geotools/util/ScopedName.java $
 * @version $Id: ScopedName.java 37298 2011-05-25 05:16:15Z mbedward $
 * @author Martin Desruisseaux (IRD)
 *
 * @see NameFactory
 */
public class ScopedName extends org.geotools.util.GenericName
                     implements org.opengis.util.ScopedName
{
    /**
     * Serial number for interoperability with different versions.
     */
    private static final long serialVersionUID = -7664125655784137729L;

    /**
     * The scope of this variable (also know as the "tail").
     */
    private final GenericName scope;

    /**
     * The separator character.
     */
    private final char separator;

    /**
     * The head as a local name.
     */
    private final LocalName name;

    /**
     * The list of parsed names. Will be constructed only when first needed.
     */
    private transient List<LocalName> parsedNames;

    /**
     * Constructs a scoped name from the specified international string.
     * If the specified name is an {@link InternationalString}, then the
     * <code>{@linkplain InternationalString#toString(Locale) toString}(null)</code>
     * method will be used in order to fetch an unlocalized name. Otherwise, the
     * <code>{@linkplain CharSequence#toString toString}()</code> method will be used.
     *
     * @param scope The scope (or "tail") of the variable.
     * @param name  The head (never {@code null}).
     */
    public ScopedName(final GenericName scope, final CharSequence name) {
        this(scope, DEFAULT_SEPARATOR, name);
    }

    /**
     * Constructs a scoped name from the specified international string.
     * If the specified name is an {@link InternationalString}, then the
     * <code>{@linkplain InternationalString#toString(Locale) toString}(null)</code>
     * method will be used in order to fetch an unlocalized name. Otherwise, the
     * <code>{@linkplain CharSequence#toString toString}()</code> method will be used.
     *
     * @param scope     The scope (or "tail") of the variable.
     * @param separator The separator character (usually <code>':'</code> or <code>'/'</code>).
     * @param name      The head (never {@code null}).
     */
    public ScopedName(final GenericName scope, final char separator, final CharSequence name) {
        AbstractInternationalString.ensureNonNull("scope", scope);
        AbstractInternationalString.ensureNonNull("name",  name);
        this.scope     = scope;
        this.separator = separator;
        this.name      = new org.geotools.util.LocalName(this, name);
    }

    /**
     * Returns the head of this scoped name. This is the first elements in the sequence of
     * {@linkplain #getParsedNames parsed names}. The head element must exists in the same
     * {@linkplain NameSpace name space} than this scoped name. In other words, the following
     * relationship must holds:
     * <p>
     * <ul>
     *   <li><code>head().scope() == this.{@linkplain #scope scope()}</code></li>
     * </ul>
     *
     * @since 2.3
     *
     * @todo Not yet implemented.
     */
    @Override
    public LocalName head() {
        throw new UnsupportedOperationException("Not yet implemented.");
    }

    /**
     * Returns the scope of this name.
     *
     * @deprecated Replaced by {@link #scope()}.
     */
    @Deprecated
    public GenericName getScope() {
        return scope;
    }

    /**
     * Returns the separator character.
     */
    @Override
    public char getSeparator() {
        return separator;
    }

    /**
     * Returns a view of this object as a scoped name. Since this object is already
     * a scoped name, this method always returns {@code this}.
     *
     * @deprecated Replaced by {@link #toFullyQualifiedName}.
     */
    @Deprecated
    public org.opengis.util.ScopedName asScopedName() {
        return this;
    }

    /**
     * Returns a view of this object as a local name. This is the last element in the
     * sequence of {@linkplain #getParsedNames parsed names}. The local name returned
     * by this method will still have the same {@linkplain LocalName#getScope scope}
     * than this scoped name. Note however that the string returned by
     * {@link LocalName#toString} will differs.
     */
    @Override
    public LocalName tip() {
        return name;
    }

    /**
     * Returns the sequence of local name for this {@linkplain GenericName generic name}.
     */
    public List<LocalName> getParsedNames() {
        if (parsedNames == null) {
            final List<? extends LocalName> parents = scope.getParsedNames();
            final int size = parents.size();
            LocalName[] names = new LocalName[size + 1];
            names = parents.toArray(names);
            names[size] = name;
            parsedNames = Arrays.asList(names);
        }
        return parsedNames;
    }

    /**
     * Returns a view of this name as a fully-qualified name. The {@linkplain #scope scope}
     * of a fully qualified name must be {@linkplain NameSpace#isGlobal global}. This method
     * never returns {@code null}.
     *
     * @since 2.3
     */
    public GenericName toFullyQualifiedName() {
        return this;
    }

    /**
     * Returns this name expanded with the specified scope. One may represent this operation
     * as a concatenation of the specified {@code name} with {@code this}. In pseudo-code,
     * the following relationships must hold:
     * <p>
     * <ul>
     *   <li><code>push(<var>name</var>).getParsedList() ==
     *       <var>name</var>.getParsedList().addAll({@linkplain #getParsedNames()})</code></li>
     *   <li><code>push(<var>name</var>).scope() == <var>name</var>.{@linkplain #scope()}</code></li>
     *   <li><code>push({@linkplain ScopedName#head head()}).{@linkplain ScopedName#tail tail()} == this</code></li>
     * </ul>
     * <p>
     * <strong>Note:</strong> Those conditions can be understood in terms of the Java
     * {@link Object#equals equals} method instead of the Java identity comparator {@code ==}.
     *
     * @since 2.3
     *
     * @todo Not yet implemented.
     */
    public org.opengis.util.ScopedName push(GenericName scope) {
        throw new UnsupportedOperationException("Not yet implemented.");
    }

    /**
     * Compares this scoped name with the specified object for equality.
     */
    @Override
    public boolean equals(final Object object) {
        if (object == this) {
            return true;
        }
        if (object!=null && object.getClass().equals(getClass())) {
            final ScopedName that = (ScopedName) object;
            return Utilities.equals(this.name,  that.name);
            // No need to checks the scope, since the LocalName implementation
            // should checks it.
        }
        return false;
    }

    /**
     * Returns a hash code value for this generic name.
     */
    @Override
    public int hashCode() {
        return (int)serialVersionUID ^ name.hashCode() ^ scope.hashCode();
    }
}
