/*
 * 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.sis.referencing;

import java.util.Map;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.opengis.util.NameSpace;
import org.opengis.util.LocalName;
import org.opengis.util.ScopedName;
import org.opengis.util.GenericName;
import org.opengis.util.NameFactory;
import org.opengis.util.InternationalString;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.parameter.InvalidParameterValueException;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.metadata.internal.shared.NameToIdentifier;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.util.iso.DefaultNameFactory;

// Specific to the main and geoapi-3.1 branches:
import org.opengis.referencing.ReferenceIdentifier;


/**
 * An identification of a CRS object which is both a {@link Identifier} and a {@link GenericName}.
 * This class implements both interfaces in order to allow usage of the same instance either as an object
 * {@linkplain AbstractIdentifiedObject#getName() name} or {@linkplain AbstractIdentifiedObject#getAlias() alias}.
 * This flexibility make easier to uses object's names in two different models:
 *
 * <ul>
 *   <li>In the ISO 19111 model, objects have a single name of type {@code RS_Identifier} and an arbitrary amount
 *       of aliases of type {@code GenericName}.</li>
 *   <li>In the GML model, objects have an arbitrary number of names of type {@code gml:CodeType},
 *       but do not have any alias.</li>
 * </ul>
 *
 * By using this {@code NamedIdentifier} class, users can declare supplemental object's names as
 * {@linkplain AbstractIdentifiedObject#getAlias() aliases} and have those names used in contexts
 * where {@code Identifier} instances are required, like GML marshalling time.
 *
 * <h2>Name ↔ Identifier mapping</h2>
 * The {@code GenericName} attributes will be inferred from {@code Identifier} attributes as below:
 *
 * <ul>
 *   <li><b>{@linkplain #tip() Tip}:</b> derived from the identifier {@linkplain #getCode() code}.</li>
 *   <li><b>{@linkplain #head() Head}:</b> derived from the identifier {@linkplain #getCodeSpace() code space} if non-null.
 *     If there is no code space, then the scope is derived from the shortest {@linkplain #getAuthority() authority}'s
 *     {@linkplain Citation#getAlternateTitles() alternate titles}, or the {@linkplain Citation#getTitle() main title}
 *     if there are no alternate titles. This policy exploits the ISO 19115 comment saying that citation alternate titles
 *     often contain abbreviation (for example "DCW" as an alternative title for <q>Digital Chart of the World</q>).</li>
 * </ul>
 *
 * <h3>Example</h3>
 * If the identifier attributes are {@code authority} = {@code new DefaultCitation("IOGP")},
 * {@code codeSpace} = {@code "EPSG"} and {@code code} = {@code "4326"}, then the name attributes will be
 * {@code head} = {@code "EPSG"}, {@code tip} = {@code "4326"} and {@link #toString()} = {@code "EPSG:4326"}.
 * Note that the scope does not appear in the string representation of names.
 *
 *
 * <h2>Immutability and thread safety</h2>
 * This class is immutable and thus inherently thread-safe if the {@link Citation} and {@link InternationalString}
 * arguments given to the constructor are also immutable. It is caller's responsibility to ensure that those
 * conditions hold, for example by invoking {@link org.apache.sis.metadata.iso.citation.DefaultCitation#transitionTo
 * DefaultCitation.transitionTo(DefaultCitation.State.FINAL)} before passing the arguments to the constructor.
 * Subclasses shall make sure that any overridden methods remain safe to call from multiple threads and do not change
 * any public {@code NamedIdentifier} state.
 *
 * @author  Martin Desruisseaux (IRD, Geomatys)
 * @version 1.4
 *
 * @see org.apache.sis.metadata.iso.DefaultIdentifier
 * @see org.apache.sis.util.iso.AbstractName
 * @see AbstractIdentifiedObject#getName()
 *
 * @since 0.4
 */
public class NamedIdentifier extends ImmutableIdentifier implements GenericName {
    /**
     * Serial number for inter-operability with different versions.
     */
    private static final long serialVersionUID = -3982456534858346939L;

    /**
     * The name of this identifier as a generic name.
     * If {@code null}, will be constructed only when first needed.
     */
    private transient GenericName name;

    /**
     * {@code true} if {@link #name} has been given explicitly by the user.
     * Consider this field as final - it is not only for constructors convenience.
     */
    private transient boolean isNameSupplied;

    /**
     * Creates a new identifier from the specified one. This is a copy constructor which get the code,
     * codespace, authority, version and the description (if available) from the given identifier.
     *
     * <p>If the given identifier implements the {@link GenericName} interface, then calls to
     * {@link #tip()}, {@link #head()}, {@link #scope()} and similar methods will delegate
     * to that name.</p>
     *
     * @param  identifier  the identifier to copy.
     *
     * @see #castOrCopy(ReferenceIdentifier)
     */
    public NamedIdentifier(final ReferenceIdentifier identifier) {
        super(identifier);
        if (identifier instanceof GenericName) {
            name = (GenericName) identifier;
            isNameSupplied = true;
        }
    }

    /**
     * Creates a new identifier from the specified name. This constructor infers the identifier attributes
     * (code, codespace and authority) from the given name. Calls to name-related methods like {@link #tip()},
     * {@link #head()} and {@link #scope()} will delegate to the given name.
     *
     * @param  name  the name to wrap.
     *
     * @see #castOrCopy(GenericName)
     */
    public NamedIdentifier(final GenericName name) {
        super(name instanceof ReferenceIdentifier ? (ReferenceIdentifier) name : new NameToIdentifier(name));
        this.name = name;
        isNameSupplied = true;
    }

    /**
     * Constructs an identifier from the given properties. The content of the properties map is used as
     * described in the {@linkplain ImmutableIdentifier#ImmutableIdentifier(Map) super-class constructor},
     * with the addition of an optional {@code "name"} property.
     *
     * <table class="sis">
     *   <caption>Recognized properties</caption>
     *   <tr>
     *     <th>Property name</th>
     *     <th>Value type</th>
     *     <th>Returned by</th>
     *   </tr><tr>
     *     <td>{@code "name"}</td>
     *     <td>{@link GenericName}</td>
     *     <td>(many)</td>
     *   </tr><tr>
     *     <th colspan="3" class="hsep">Defined in parent class (reminder)</th>
     *   </tr><tr>
     *     <td>{@value org.opengis.metadata.Identifier#CODE_KEY}</td>
     *     <td>{@link String}</td>
     *     <td>{@link #getCode()}</td>
     *   </tr><tr>
     *     <td>"codespace"</td>
     *     <td>{@link String}</td>
     *     <td>{@link #getCodeSpace()}</td>
     *   </tr><tr>
     *     <td>{@value org.opengis.metadata.Identifier#AUTHORITY_KEY}</td>
     *     <td>{@link String} or {@link Citation}</td>
     *     <td>{@link #getAuthority()}</td>
     *   </tr><tr>
     *     <td>"version"</td>
     *     <td>{@link String}</td>
     *     <td>{@link #getVersion()}</td>
     *   </tr><tr>
     *     <td>"description"</td>
     *     <td>{@link String} or {@link InternationalString}</td>
     *     <td>{@link #getDescription()}</td>
     *   </tr><tr>
     *     <td>{@value org.apache.sis.referencing.AbstractIdentifiedObject#LOCALE_KEY}</td>
     *     <td>{@link Locale}</td>
     *     <td>(none)</td>
     *   </tr>
     * </table>
     *
     * The {@value org.opengis.metadata.Identifier#CODE_KEY} property is mandatory and all other properties
     * are optional. If a {@code "name"} property is provided, then calls to name-related methods like
     * {@link #tip()}, {@link #head()} and {@link #scope()} will delegate to the given name.
     *
     * @param  properties  the properties to be given to this identifier.
     * @throws InvalidParameterValueException if a property has an invalid value.
     * @throws IllegalArgumentException if a property is invalid for some other reason.
     */
    public NamedIdentifier(final Map<String,?> properties) throws IllegalArgumentException {
        super(properties);
        name = (GenericName) properties.get("name");
        isNameSupplied = (name != null);
    }

    /**
     * Constructs an identifier from an authority and code.
     * This is a convenience constructor for commonly-used parameters.
     *
     * <p>If the given code is an {@link InternationalString}, then the {@code code.toString(Locale.ROOT)}
     * return value will be used for the {@link #getCode() code} property, and the complete international
     * string will be used for the {@link #getName() name} property.</p>
     *
     * @param authority
     *          organization or party responsible for definition and maintenance of the code space or code,
     *          or {@code null} if not available.
     * @param code
     *          identifier code or name, optionally from a controlled list or pattern defined by the authority.
     *          The code cannot be null.
     */
    @SuppressWarnings("this-escape")    // The invoked method does not store `this` and is not overrideable.
    public NamedIdentifier(final Citation authority, final CharSequence code) {
        super(authority, Citations.toCodeSpace(authority), toString(code));
        if (code instanceof InternationalString) {
            name = createName(authority, super.getCodeSpace(), code);
            isNameSupplied = true;                              // Because 'code' is an international string.
        }
    }

    /**
     * Constructs an identifier from an authority and localizable code,
     * with an optional version number and description.
     *
     * <p>If the given code is an {@link InternationalString}, then the {@code code.toString(Locale.ROOT)}
     * return value will be used for the {@link #getCode() code} property, and the complete international
     * string will be used for the {@link #getName() name} property.</p>
     *
     * @param authority
     *          organization or party responsible for definition and maintenance of the code space or code,
     *          or {@code null} if not available.
     * @param codeSpace
     *          name or identifier of the person or organization responsible for namespace, or {@code null}
     *          if not available. This is often an abbreviation of the authority name.
     * @param code
     *          identifier code or name, optionally from a controlled list or pattern defined by a code space.
     *          The code cannot be null.
     * @param version
     *          the version of the associated code space or code as specified by the code authority,
     *          or {@code null} if none.
     * @param description
     *          natural language description of the meaning of the code value, or {@code null} if none.
     */
    public NamedIdentifier(final Citation authority, final String codeSpace, final CharSequence code,
            final String version, final InternationalString description)
    {
        super(authority, codeSpace, toString(code), version, description);
        if (code instanceof InternationalString) {
            name = createName(authority, codeSpace, code);
            isNameSupplied = true;                              // Because 'code' is an international string.
        }
    }

    /**
     * Returns the unlocalized string representation of the given code.
     */
    private static String toString(final CharSequence code) {
        // Implicit null check below.
        final String c;
        if (code instanceof InternationalString) {
            c = ((InternationalString) code).toString(Locale.ROOT);
        } else {
            c = code.toString();
        }
        if (c != null) {
            return c;
        }
        /*
         * May happen if the user gave us an instance of 'org.apache.sis.xml.bind.gcx.Anchor' class
         * (maybe he got the instance indirectly) and the construction of that instance is not completed.
         */
        throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentClass_2, "code", code.getClass()));
    }

    /**
     * Returns the generic name of this identifier.
     * The name will be constructed automatically the first time it will be needed.
     * The name's head is inferred from the shortest alternative title (if any).
     * This heuristic rule is compatible to the ISO 19115 remark saying that the
     * {@linkplain Citation#getAlternateTitles() alternate titles} often contains abbreviation
     * (for example "DCW" as an alternative title for "Digital Chart of the World").
     * If no alternative title is found or if the main title is yet shorter, then it is used.
     *
     * @category Generic name
     */
    private synchronized GenericName getName() {
        if (name == null) {
            name = createName(super.getAuthority(), super.getCodeSpace(), super.getCode());
        }
        return name;
    }

    /**
     * Constructs a generic name from the specified authority and code.
     *
     * @param  authority  the authority, or {@code null} if none.
     * @param  codeSpace  the code space, or {@code null} if none.
     * @param  code       the code.
     * @return a new generic name for the given authority and code.
     * @category Generic name
     *
     * @see <a href="https://issues.apache.org/jira/browse/SIS-197">SIS-197</a>
     */
    private static GenericName createName(final Citation authority, String codeSpace, final CharSequence code) {
        if (codeSpace == null) {
            codeSpace = Citations.toCodeSpace(authority);           // Whitespaces trimed by Citations.
        }
        final NameFactory factory = DefaultNameFactory.provider();
        if (codeSpace != null) {
            return factory.createGenericName(null, codeSpace, code);
        } else {
            return factory.createLocalName(null, code);
        }
    }

    /**
     * Returns a SIS identifier implementation with the values of the given arbitrary implementation.
     * This method performs the first applicable action in the following choices:
     *
     * <ul>
     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
     *   <li>Otherwise if the given object is already an instance of
     *       {@code NamedIdentifier}, then it is returned unchanged.</li>
     *   <li>Otherwise a new {@code NamedIdentifier} instance is created using the
     *       {@linkplain #NamedIdentifier(ReferenceIdentifier) copy constructor} and returned.
     *       Note that this is a <em>shallow</em> copy operation, because the other
     *       metadata contained in the given object are not recursively copied.</li>
     * </ul>
     *
     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
     * @return a SIS implementation containing the values of the given object (may be the
     *         given object itself), or {@code null} if the argument was null.
     *
     * @since 1.0
     */
    public static NamedIdentifier castOrCopy(final ReferenceIdentifier object) {
        if (object == null || object instanceof NamedIdentifier) {
            return (NamedIdentifier) object;
        }
        return new NamedIdentifier(object);
    }

    /**
     * Returns a SIS name implementation with the values of the given arbitrary implementation.
     * This method performs the first applicable action in the following choices:
     *
     * <ul>
     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
     *   <li>Otherwise if the given object is already an instance of
     *       {@code NamedIdentifier}, then it is returned unchanged.</li>
     *   <li>Otherwise a new {@code NamedIdentifier} instance is created using the
     *       {@linkplain #NamedIdentifier(GenericName) copy constructor} and returned.
     *       Note that this is a <em>shallow</em> copy operation, because the other
     *       metadata contained in the given object are not recursively copied.</li>
     * </ul>
     *
     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
     * @return a SIS implementation containing the values of the given object (may be the
     *         given object itself), or {@code null} if the argument was null.
     *
     * @since 1.0
     */
    public static NamedIdentifier castOrCopy(final GenericName object) {
        if (object == null || object instanceof NamedIdentifier) {
            return (NamedIdentifier) object;
        }
        return new NamedIdentifier(object);
    }

    /**
     * The last element in the sequence of {@linkplain #getParsedNames() parsed names}.
     * By default, this is the same value as the {@linkplain #getCode() code} provided as a local name.
     *
     * @return the last element in the list of {@linkplain #getParsedNames() parsed names}.
     *
     * @see #getCode()
     */
    @Override
    public LocalName tip() {
        return getName().tip();
    }

    /**
     * Returns the first element in the sequence of {@linkplain #getParsedNames() parsed names}.
     * By default, this is the same value as the {@linkplain #getCodeSpace() code space} provided as a local name.
     *
     * @return the first element in the list of {@linkplain #getParsedNames() parsed names}.
     *
     * @see #scope()
     * @see #getCodeSpace()
     */
    @Override
    public LocalName head() {
        return getName().head();
    }

    /**
     * Returns the scope (name space) in which this name is local.
     * By default, this is the same value as the {@link #getAuthority() authority} provided as a name space.
     *
     * @see #head()
     * @see #getAuthority()
     *
     * @return the scope of this name.
     */
    @Override
    public NameSpace scope() {
        return getName().scope();
    }

    /**
     * Returns the depth of this name within the namespace hierarchy.
     *
     * @return the depth of this name.
     */
    @Override
    public int depth() {
        return getName().depth();
    }

    /**
     * Returns the sequence of {@linkplain LocalName local names} making this generic name.
     * The length of this sequence is the {@linkplain #depth() depth}.
     * It does not include the {@linkplain #scope() scope}.
     *
     * @return the local names making this generic name, without the {@linkplain #scope() scope}.
     *         Shall never be {@code null} neither empty.
     */
    @Override
    public List<? extends LocalName> getParsedNames() {
        return getName().getParsedNames();
    }

    /**
     * Returns this name expanded with the specified scope. One may represent this operation
     * as a concatenation of the specified {@code name} with {@code this}.
     *
     * @param scope The name to use as prefix.
     * @return a concatenation of the given scope with this name.
     */
    @Override
    public ScopedName push(final GenericName scope) {
        return getName().push(scope);
    }

    /**
     * Returns a view of this name as a fully-qualified name.
     *
     * @return the fully-qualified name (never {@code null}).
     */
    @Override
    public GenericName toFullyQualifiedName() {
        return getName().toFullyQualifiedName();
    }

    /**
     * Returns a local-dependent string representation of this generic name.
     * This string is similar to the one returned by {@link #toString()} except that each element has
     * been localized in the {@linkplain InternationalString#toString(Locale) specified locale}.
     * If no international string is available, then this method returns an implementation mapping
     * to {@code toString()} for all locales.
     *
     * @return a localizable string representation of this name.
     */
    @Override
    public InternationalString toInternationalString() {
        return getName().toInternationalString();
    }

    /**
     * Returns a string representation of this generic name. This string representation
     * is local-independent. It contains all elements listed by {@link #getParsedNames()}
     * separated by a namespace-dependent character (usually {@code :} or {@code /}).
     *
     * @return a local-independent string representation of this generic name.
     *
     * @see IdentifiedObjects#toString(Identifier)
     */
    @Override
    public String toString() {
        return getName().toString();
    }

    /**
     * Compares this name with the specified object for order. Returns a negative integer,
     * zero, or a positive integer as this name lexicographically precedes, is equal to,
     * or follows the specified object.
     *
     * @param  object  the object to compare with.
     * @return -1 if this identifier precedes the given object, +1 if it follows it.
     */
    @Override
    public int compareTo(final GenericName object) {
        return getName().compareTo(object);
    }

    /**
     * Compares this identifier with the specified object for equality.
     *
     * @param  object  the object to compare with this name.
     * @return {@code true} if the given object is equal to this name.
     */
    @Override
    public boolean equals(final Object object) {
        if (object == this) {
            return true;
        }
        if (super.equals(object)) {
            if (!isNameSupplied) {
                return true;            // No need to compare names if they are computed from the same values.
            }
            final NamedIdentifier that = (NamedIdentifier) object;
            return Objects.equals(this.getName(), that.getName());
        }
        return false;
    }

    /**
     * Returns a hash code value for this object.
     */
    @Override
    public int hashCode() {
        /*
         * We do not use the name because it is usually inferred from existing properties.
         * We only revert the bits for differentiating this NamedIdentifier class from its parent class.
         */
        return ~super.hashCode();
    }

    /**
     * Invoked on serialization for writing the {@linkplain #name} if it was supplied by the user.
     * Otherwise, we will let {@link #getName()} recompute the name only when needed.
     *
     * @param  out  the output stream where to serialize this named identifier.
     * @throws IOException if an I/O error occurred while writing.
     */
    private void writeObject(final ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject(isNameSupplied ? name : null);
    }

    /**
     * Invoked on deserialization for reading the name written by {@link #writeObject(ObjectOutputStream)},
     * if any.
     *
     * @param  in  the input stream from which to deserialize a named identifier.
     * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
     * @throws ClassNotFoundException if the class serialized on the stream is not on the module path.
     */
    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        name = (GenericName) in.readObject();
        isNameSupplied = (name != null);
    }
}
