/*
 * 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.storage.gpx;

import java.net.URI;
import java.util.List;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.citation.CitationDate;
import org.opengis.metadata.citation.DateType;
import org.opengis.metadata.citation.OnlineResource;
import org.opengis.metadata.citation.PresentationForm;
import org.opengis.metadata.citation.Role;
import org.opengis.metadata.constraint.LegalConstraints;
import org.opengis.metadata.constraint.Restriction;
import org.opengis.util.InternationalString;
import org.apache.sis.util.iso.Types;

// Specific to the main branch:
import org.opengis.metadata.citation.Contact;
import org.opengis.metadata.citation.Series;
import org.opengis.metadata.citation.ResponsibleParty;
import org.apache.sis.metadata.iso.citation.AbstractParty;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
import org.apache.sis.metadata.iso.constraint.DefaultConstraints;


/**
 * Information about the copyright holder and any license governing use of a GPX file.
 * Information in this element allow to place data into the public domain or grant additional usage rights.
 * This element provides 3 properties:
 *
 * <ul>
 *   <li>The {@linkplain #author}, which is the only mandatory property.</li>
 *   <li>The copyright {@linkplain #year} (optional).</li>
 *   <li>An URI to the {@linkplain #license} (optional).</li>
 * </ul>
 *
 * Those properties can be read or modified directly. All methods defined in this class are bridges to
 * the ISO 19115 metadata model and can be ignored if the user only wants to manipulate the GPX model.
 *
 * @author  Johann Sorel (Geomatys)
 * @author  Martin Desruisseaux (Geomatys)
 */
public final class Copyright implements LegalConstraints, ResponsibleParty, Citation, CitationDate {
    /**
     * The copyright holder.
     * This field is mandatory in principle, but {@code Copyright} implementation is robust to null value.
     * This field is mapped to the {@code getName() responsible party name} in ISO 19115 metadata.
     */
    @XmlAttribute(name = Attributes.AUTHOR, required = true)
    public String author;

    /**
     * The copyright year, or {@code null} if unspecified.
     * This field is mapped to the {@linkplain #getDate() citation date} in ISO 19115 metadata.
     *
     * @see #getDate()
     */
    @XmlElement(name = Tags.YEAR)
    public Integer year;

    /**
     * Link to an external file containing the license text, or {@code null} if none.
     * This field is mapped to the online resources in ISO 19115 metadata.
     */
    @XmlElement(name = Tags.LICENSE)
    public URI license;

    /**
     * Creates an initially empty instance.
     * Callers should set at least the {@link #author} field after construction.
     */
    public Copyright() {
    }

    /**
     * Copies properties from the given ISO 19115 metadata.
     */
    private Copyright(final LegalConstraints c, final Locale locale) {
        if (!(c instanceof DefaultConstraints)) {
            return;
        }
resp:   for (final DefaultResponsibility r : ((DefaultConstraints) c).getResponsibleParties()) {
            for (final AbstractParty p : r.getParties()) {
                author = Types.toString(p.getName(), locale);
                if (author != null) break resp;
            }
        }
        for (final Citation ci : ((DefaultConstraints) c).getReferences()) {
            for (final CitationDate d : ci.getDates()) {
                final Date date = d.getDate();
                if (date != null) {
                    year = date.getYear() + 1900;
                    break;
                }
            }
            if (!(ci instanceof DefaultCitation)) continue;
            for (final OnlineResource r : ((DefaultCitation) ci).getOnlineResources()) {
                license = r.getLinkage();
                if (license != null) break;
            }
        }
    }

    /**
     * Returns the given ISO 19115 metadata as a {@code Copyright} instance.
     * This method copies the data only if needed.
     *
     * @param  c       the ISO 19115 metadata, or {@code null}.
     * @param  locale  the locale to use for localized strings.
     * @return the GPX metadata, or {@code null}.
     */
    public static Copyright castOrCopy(final LegalConstraints c, final Locale locale) {
        return (c == null || c instanceof Copyright) ? (Copyright) c : new Copyright(c, locale);
    }

    /**
     * ISO 19115 metadata property fixed to {@link Restriction#COPYRIGHT}.
     *
     * @return restrictions or limitations on obtaining the data.
     */
    @Override
    public Collection<Restriction> getAccessConstraints() {
        return Collections.singleton(Restriction.COPYRIGHT);
    }

    /**
     * ISO 19115 metadata property determined by the {@link #license} field.
     *
     * @return restrictions or limitations or warnings on using the data.
     */
    @Override
    public Collection<Restriction> getUseConstraints() {
        if (license != null) {
            return List.of(Restriction.COPYRIGHT, Restriction.valueOf("LICENCE"));
        } else {
            return Collections.singleton(Restriction.COPYRIGHT);
        }
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     *
     * @return other restrictions and legal prerequisites for accessing and using the resource.
     */
    @Override
    public Collection<InternationalString> getOtherConstraints() {
        return Collections.emptySet();
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     *
     * @return limitation affecting the fitness for use of the resource.
     */
    @Override
    public Collection<InternationalString> getUseLimitations() {
        return Collections.emptySet();
    }


    /* -------------------------------------------------------------------------------------------------
     * Implementation of the Responsibility object returned by LegalConstraints.getResponsibleParties().
     * Contains information about 'author' only (not 'year' or 'license').
     * ------------------------------------------------------------------------------------------------- */

    /**
     * ISO 19115 metadata property fixed to {@link Role#OWNER}.
     * This is part of the properties returned by {@code getResponsibleParties()}.
     *
     * @return function performed by the responsible party.
     */
    @Override
    public Role getRole() {
        return Role.OWNER;
    }

    /**
     * ISO 19115 metadata property not specified by GPX. Actually could be the {@link #author},
     * but we have no way to know if the author is an individual or an organization.
     *
     * @return name of the organization, or {@code null} if none.
     */
    @Override
    public InternationalString getOrganisationName() {
        return null;
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     *
     * @return position of the individual in the organization, or {@code null} if none.
     */
    @Override
    public InternationalString getPositionName() {
        return null;
    }

    /**
     * ISO 19115 metadata property determined by the {@link #author} field.
     * This is part of the properties returned by {@code getParties()}.
     *
     * @return name of the party, or {@code null} if none.
     */
    @Override
    public String getIndividualName() {
        return author;
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getParties()}.
     *
     * @return contact information for the party.
     */
    @Override
    public Contact getContactInfo() {
        return null;
    }

    /**
     * Returns citation or party identifiers, which is an empty list.
     *
     * @return empty.
     */
    @Override
    public Collection<Identifier> getIdentifiers() {
        return Collections.emptyList();
    }


    /* -----------------------------------------------------------------------------------
     * Implementation of the Citation object returned by LegalConstraints.getReferences().
     * Contains information about 'year' or 'license' only (not 'author').
     * ----------------------------------------------------------------------------------- */

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getReferences()}.
     * It would be the license title if that information was provided.
     *
     * @return the license name.
     */
    @Override
    public InternationalString getTitle() {
        return null;
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getReferences()}.
     *
     * @return other names for the resource.
     */
    @Override
    public Collection<InternationalString> getAlternateTitles() {
        return Collections.emptySet();
    }

    /**
     * ISO 19115 metadata property determined by the {@link #year} field.
     * This is part of the properties returned by {@code getReferences()}.
     * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
     * to the {@link #getDate()} method.
     *
     * @return reference date for the cited resource.
     *
     * @see #getDate()
     * @see #getDateType()
     */
    @Override
    public Collection<? extends CitationDate> getDates() {
        return thisOrEmpty(year != null);
    }

    /**
     * ISO 19115 metadata property determined by the {@link #year} field.
     * This is part of the properties returned by {@link #getDates()}.
     *
     * @return reference date for the cited resource.
     */
    @Override
    public Date getDate() {
        if (year != null) {
            return new Date(year - 1900, 0, 1);
        }
        return null;
    }

    /**
     * ISO 19115 metadata property fixed to {@code DateType.IN_FORCE}.
     * This is part of the properties returned by {@link #getDates()}.
     *
     * @return event used for reference date.
     */
    @Override
    public DateType getDateType() {
        return DateType.valueOf("IN_FORCE");
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getReferences()}.
     *
     * @return the license version, or {@code null} if none.
     */
    @Override
    public InternationalString getEdition() {
        return null;
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getReferences()}.
     *
     * @return the license edition date, or {@code null} if none.
     */
    @Override
    public Date getEditionDate() {
        return null;
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getReferences()}.
     * It would be the license author if that information was provided.
     *
     * @return the information for individuals or organisations that are responsible for the license.
     */
    @Override
    public Collection<ResponsibleParty> getCitedResponsibleParties() {
        return Collections.emptySet();
    }

    /**
     * ISO 19115 metadata property fixed to {@link PresentationForm#DOCUMENT_DIGITAL}.
     * This is part of the properties returned by {@code getReferences()}.
     *
     * @return the presentation mode of the license.
     */
    @Override
    public Collection<PresentationForm> getPresentationForms() {
        return Collections.singleton(PresentationForm.DOCUMENT_DIGITAL);
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getReferences()}.
     *
     * @return the series or aggregate dataset of which the dataset is a part.
     */
    @Override
    public Series getSeries() {
        return null;
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getReferences()}.
     *
     * @return other details.
     */
    @Override
    public InternationalString getOtherCitationDetails() {
        return null;
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getReferences()}.
     *
     * @return the common title.
     */
    @Override
    @Deprecated
    public InternationalString getCollectiveTitle() {
        return null;
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getReferences()}.
     *
     * @return the International Standard Book Number.
     */
    @Override
    public String getISBN() {
        return null;
    }

    /**
     * ISO 19115 metadata property not specified by GPX.
     * This is part of the properties returned by {@code getReferences()}.
     *
     * @return the International Standard Serial Number.
     */
    @Override
    public String getISSN() {
        return null;
    }

    /**
     * Returns this object as a singleton if the given condition is {@code true},
     * or an empty set if the given condition is {@code false}.
     */
    private Collection<Copyright> thisOrEmpty(final boolean condition) {
        return condition ? Collections.singleton(this) : Collections.emptySet();
    }

    /**
     * Compares this {@code Copyright} with the given object for equality.
     *
     * @param  obj  the object to compare with this {@code Copyright}.
     * @return {@code true} if both objects are equal.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Copyright) {
            final Copyright that = (Copyright) obj;
            return Objects.equals(this.author,  that.author) &&
                   Objects.equals(this.year,    that.year) &&
                   Objects.equals(this.license, that.license);
        }
        return false;
    }

    /**
     * Returns a hash code value for this {@code Copyright}.
     *
     * @return a hash code value.
     */
    @Override
    public int hashCode() {
        return Objects.hash(author, year, license);
    }

    /**
     * Returns a string representation of the copyright statement.
     * The statement is formatted in a way similar to the copyright statements found in file header.
     * Example:
     *
     * <blockquote>
     * Copyright 2016 John Smith
     * http://john.smith.com
     * </blockquote>
     *
     * @return a string representation of the copyright statement.
     */
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Copyright");
        if (year    != null) sb.append(' ').append(year);
        if (author  != null) sb.append(' ').append(author);
        if (license != null) {
            sb.append(System.lineSeparator()).append(license);
        }
        return sb.toString();
    }
}
