/*
 * 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.metadata.iso;

import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlSeeAlso;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.util.InternationalString;
import org.apache.sis.metadata.TitleProperty;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.xml.Namespaces;

// Specific to the main branch:
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.annotation.UML;

import static org.opengis.annotation.Obligation.OPTIONAL;
import static org.opengis.annotation.Specification.ISO_19115;


/**
 * Value uniquely identifying an object within a namespace.
 * The following property is mandatory in a well-formed metadata according ISO 19115:
 *
 * <div class="preformat">{@code MD_Identifier}
 * {@code   └─code……………} Alphanumeric value identifying an instance in the namespace.</div>
 *
 * One or more {@code Identifier} instances can be associated to some metadata objects like
 * {@linkplain org.apache.sis.metadata.iso.acquisition.DefaultOperation operation},
 * {@linkplain org.apache.sis.metadata.iso.acquisition.DefaultPlatform platform},
 * {@linkplain org.apache.sis.metadata.iso.acquisition.DefaultInstrument instrument},
 * {@linkplain org.apache.sis.metadata.iso.acquisition.DefaultEvent event},
 * {@linkplain org.apache.sis.metadata.iso.lineage.DefaultProcessing processing},
 * {@linkplain org.apache.sis.metadata.iso.lineage.DefaultSource source},
 * {@linkplain org.apache.sis.metadata.iso.content.DefaultImageDescription image description},
 * {@linkplain org.apache.sis.metadata.iso.extent.DefaultGeographicDescription geographic description}
 * and more.
 *
 * <p>Referencing objects like
 * {@linkplain org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis coordinate system axis},
 * {@linkplain org.apache.sis.referencing.datum.DefaultGeodeticDatum geodetic datum},
 * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS} and more
 * rather use the {@link org.apache.sis.referencing.ImmutableIdentifier} implementation,
 * which is a class unrelated to the usual {@code org.apache.metadata} hierarchy
 * because of the immutable nature of referencing objects.</p>
 *
 * <h2>Text, URN and XML representations</h2>
 * The XML representation of {@link DefaultIdentifier} is as the following example:
 *
 * {@snippet lang="xml" :
 *   <mcc:MD_Identifier>
 *     <mcc:code>
 *       <gco:CharacterString>4326</gco:CharacterString>
 *     </mcc:code>
 *     <mcc:authority>
 *       <cit:CI_Citation>
 *         <cit:title>
 *           <gco:CharacterString>EPSG</gco:CharacterString>
 *         </cit:title>
 *       </cit:CI_Citation>
 *     </mcc:authority>
 *   </mcc:MD_Identifier>
 *   }
 *
 * <h2>Limitations</h2>
 * <ul>
 *   <li>Instances of this class are not synchronized for multi-threading.
 *       Synchronization, if needed, is caller's responsibility.</li>
 *   <li>Serialized objects of this class are not guaranteed to be compatible with future Apache SIS releases.
 *       Serialization support is appropriate for short term storage or RMI between applications running the
 *       same version of Apache SIS. For long term storage, use {@link org.apache.sis.xml.XML} instead.</li>
 * </ul>
 *
 * @author  Martin Desruisseaux (IRD, Geomatys)
 * @author  Touraïvane (IRD)
 * @author  Cédric Briançon (Geomatys)
 * @author  Cullen Rombach (Image Matters)
 * @version 1.4
 *
 * @see org.apache.sis.referencing.ImmutableIdentifier
 * @see org.apache.sis.referencing.IdentifiedObjects#toURN(Class, Identifier)
 *
 * @since 0.3
 */
@TitleProperty(name = "code")
@XmlType(name = "MD_Identifier_Type", namespace = Namespaces.MCC, propOrder = {
    "authority",
    "code",
    "codeSpace",
    "version",
    "description"
})
@XmlRootElement(name = "MD_Identifier", namespace = Namespaces.MCC)
@XmlSeeAlso(org.apache.sis.xml.bind.metadata.replace.RS_Identifier.class)
public class DefaultIdentifier extends ISOMetadata implements Identifier {
    /**
     * Serial number for inter-operability with different versions.
     */
    private static final long serialVersionUID = -23375776954553866L;

    /**
     * Person or party responsible for maintenance of the namespace.
     *
     * @see #getAuthority()
     */
    @SuppressWarnings("serial")
    private Citation authority;

    /**
     * Alphanumeric value identifying an instance in the namespace.
     *
     * @see #getCode()
     */
    private String code;

    /**
     * Identifier or namespace in which the code is valid.
     *
     * @see #getCodeSpace()
     */
    private String codeSpace;

    /**
     * Version identifier for the namespace, as specified by the code authority.
     * This version is included only when the {@linkplain #getCode code} uses versions.
     * When appropriate, the edition is identified by the effective date, coded using ISO 8601 date format.
     *
     * @see #getVersion()
     */
    private String version;

    /**
     * Natural language description of the meaning of the code value.
     *
     * @see #getDescription()
     */
    @SuppressWarnings("serial")
    private InternationalString description;

    /**
     * Construct an initially empty identifier.
     */
    public DefaultIdentifier() {
    }

    /**
     * Creates an identifier initialized to the given code.
     *
     * @param  code  the alphanumeric value identifying an instance in the namespace, or {@code null} if none.
     */
    public DefaultIdentifier(final String code) {
        this.code = code;
    }

    /**
     * Creates a new identifier initialized to the given code, code space and version number.
     *
     * @param codeSpace  identifier or namespace in which the code is valid, or {@code null} if not available.
     * @param code       alphanumeric value identifying an instance in the namespace, or {@code null} if none.
     * @param version    the version identifier for the namespace as specified by the code authority, or {@code null} if none.
     *
     * @since 1.0
     */
    public DefaultIdentifier(final String codeSpace, final String code, final String version) {
        this.code      = code;
        this.codeSpace = codeSpace;
        this.version   = version;
    }

    /**
     * Creates an identifier initialized to the given authority and code.
     * This constructor automatically initializes the {@linkplain #getCodeSpace() code space} to a value inferred
     * from the given {@code authority}, if a suitable value can be found. This constructor proceeds by searching
     * for the first suitable property in the following list:
     *
     * <ol>
     *   <li>The value of {@link org.apache.sis.xml.IdentifierSpace#getName()}.</li>
     *   <li>A {@linkplain org.apache.sis.metadata.iso.citation.DefaultCitation#getIdentifiers() citation identifier}
     *       which is a valid
     *       {@linkplain org.apache.sis.util.CharSequences#isUnicodeIdentifier(CharSequence) unicode identifier}.</li>
     *   <li>Only if the citation has no identifier, a citation title or
     *       {@linkplain org.apache.sis.metadata.iso.citation.DefaultCitation#getAlternateTitles() alternate title}
     *       which is a valid
     *       {@linkplain org.apache.sis.util.CharSequences#isUnicodeIdentifier(CharSequence) unicode identifier}.</li>
     * </ol>
     *
     * @param authority  the person or party responsible for maintenance of the namespace, or {@code null} if none.
     * @param code       the alphanumeric value identifying an instance in the namespace, or {@code null} if none.
     *
     * @see org.apache.sis.metadata.iso.citation.Citations#toCodeSpace(Citation)
     */
    public DefaultIdentifier(final Citation authority, final String code) {
        this.authority = authority;
        this.code = code;
        codeSpace = Citations.toCodeSpace(authority);
    }

    /**
     * Constructs a new instance initialized with the values from the specified metadata object.
     * This is a <em>shallow</em> copy constructor, because the other metadata contained in the
     * given object are not recursively copied.
     *
     * @param  object  the metadata to copy values from, or {@code null} if none.
     *
     * @see #castOrCopy(Identifier)
     */
    public DefaultIdentifier(final Identifier object) {
        super(object);
        if (object != null) {
            code        = object.getCode();
            authority   = object.getAuthority();
            if (object instanceof DefaultIdentifier) {
                final DefaultIdentifier c = (DefaultIdentifier) object;
                codeSpace   = c.getCodeSpace();
                version     = c.getVersion();
                description = c.getDescription();
            } else if (object instanceof ReferenceIdentifier) {
                final ReferenceIdentifier c = (ReferenceIdentifier) object;
                codeSpace = c.getCodeSpace();
                version   = c.getVersion();
            }
        }
    }

    /**
     * Returns a SIS metadata 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 DefaultIdentifier}, then it is returned unchanged.</li>
     *   <li>Otherwise a new {@code DefaultIdentifier} instance is created using the
     *       {@linkplain #DefaultIdentifier(Identifier) 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.
     */
    public static DefaultIdentifier castOrCopy(final Identifier object) {
        if (object == null || object instanceof DefaultIdentifier) {
            return (DefaultIdentifier) object;
        }
        return new DefaultIdentifier(object);
    }

    /**
     * Returns the person or party responsible for maintenance of the namespace.
     * The organization's abbreviation is often the same than this identifier
     * {@linkplain #getCodeSpace() code space}, but not necessarily.
     *
     * @return person or party responsible for maintenance of the namespace, or {@code null} if not available.
     */
    @Override
    @XmlElement(name = "authority")
    public Citation getAuthority() {
        return authority;
    }

    /**
     * Sets the person or party responsible for maintenance of the namespace.
     *
     * @param  newValue  the new authority.
     */
    public void setAuthority(final Citation newValue) {
        checkWritePermission(authority);
        authority = newValue;
    }

    /**
     * Returns the alphanumeric value identifying an instance in the namespace.
     * The code is optionally from a controlled list or pattern.
     *
     * <h4>Example</h4>
     * {@code "4326"}
     *
     * The code is mandatory according ISO specification, but this {@code DefaultIdentifier}
     * implementation does not enforce this restriction.
     *
     * @return value identifying an instance in the namespace.
     */
    @Override
    @XmlElement(name = "code", required = true)
    public String getCode() {
        return code;
    }

    /**
     * Sets the alphanumeric value identifying an instance in the namespace.
     * Should avoid characters that are not legal in URLs.
     *
     * @param  newValue  the new code, or {@code null}.
     */
    public void setCode(final String newValue) {
        checkWritePermission(code);
        code = newValue;
    }

    /**
     * Returns the identifier or namespace in which the code is valid.
     * This is often the {@linkplain #getAuthority() authority}'s abbreviation, but not necessarily.
     *
     * <h4>Example</h4>
     * {@code "EPSG"}
     *
     * @return the identifier or namespace in which the code is valid, or {@code null} if none.
     *
     * @since 0.5
     */
    @XmlElement(name = "codeSpace")
    @UML(identifier="codeSpace", obligation=OPTIONAL, specification=ISO_19115)
    public String getCodeSpace() {
        return codeSpace;
    }

    /**
     * Sets the identifier or namespace in which the code is valid.
     *
     * @param  newValue  the new code space, or {@code null} if none.
     *
     * @since 0.5
     */
    public void setCodeSpace(final String newValue) {
        checkWritePermission(codeSpace);
        codeSpace = newValue;
    }

    /**
     * Returns the version identifier for the namespace, as specified by the code authority.
     * This version is included only when the {@linkplain #getCode() code} uses versions.
     * When appropriate, the edition is identified by the effective date, coded using ISO 8601 date format.
     *
     * <h4>Example</h4>
     * The version of the underlying EPSG database.
     *
     * @return the version identifier for the namespace, or {@code null} if none.
     */
    @XmlElement(name = "version")
    @UML(identifier="version", obligation=OPTIONAL, specification=ISO_19115)
    public String getVersion() {
        return version;
    }

    /**
     * Sets the version identifier for the namespace.
     *
     * @param  newValue  the new version, or {@code null} if none.
     */
    public void setVersion(final String newValue) {
        checkWritePermission(version);
        version = newValue;
    }

    /**
     * Returns the natural language description of the meaning of the code value.
     *
     * <h4>Example</h4>
     * "World Geodetic System 1984".
     *
     * @return the natural language description, or {@code null} if none.
     *
     * @since 0.5
     */
    @XmlElement(name = "description")
    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
    public InternationalString getDescription() {
        return description;
    }

    /**
     * Sets the natural language description of the meaning of the code value.
     *
     * @param  newValue  the new natural language description, or {@code null} if none.
     *
     * @since 0.5
     */
    public void setDescription(final InternationalString newValue) {
        checkWritePermission(description);
        description = newValue;
    }
}
