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

import java.lang.reflect.Proxy;
import jakarta.xml.bind.JAXBException;
import org.opengis.metadata.citation.Series;
import org.opengis.metadata.citation.Citation;

// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.xml.test.TestCase;
import static org.apache.sis.metadata.Assertions.assertTitleEquals;
import static org.apache.sis.metadata.Assertions.assertXmlEquals;


/**
 * Tests the XML marshalling of object having {@code uuid} or {@code uuidref} attributes.
 *
 * @author  Martin Desruisseaux (Geomatys)
 */
public final class UUIDMarshallingTest extends TestCase {
    /**
     * A random UUID for the tests in this class.
     */
    private static final String UUID_VALUE = "f8f5fcb1-d57b-4013-b3a4-4eaa40df6dcf";

    /**
     * A XML with a {@code uuid} identifier in the {@code <cit:CI_Series>} element.
     */
    private static final String IDENTIFIED_XML =
            "<cit:CI_Citation xmlns:cit=\"" + Namespaces.CIT + '"' +
                            " xmlns:gco=\"" + Namespaces.GCO + "\">\n" +
            "  <cit:title>\n" +
            "    <gco:CharacterString>My data</gco:CharacterString>\n" +
            "  </cit:title>\n" +
            "  <cit:series>\n" +
            "    <cit:CI_Series uuid=\"" + UUID_VALUE + "\">\n" +
            "      <cit:name>\n" +
            "        <gco:CharacterString>My aggregate dataset</gco:CharacterString>\n" +
            "      </cit:name>\n" +
            "    </cit:CI_Series>\n" +
            "  </cit:series>\n" +
            "</cit:CI_Citation>";

    /**
     * A XML with a {@code uuidref} identifier in the {@code <cit:series>} element.
     * This XML declares the method body anyway, which is kind of contradictory with usage of reference.
     */
    private static final String REFERENCED_XML_WITH_BODY =
            "<cit:CI_Citation xmlns:cit=\"" + Namespaces.CIT + '"' +
                            " xmlns:gco=\"" + Namespaces.GCO + "\">\n" +
            "  <cit:title>\n" +
            "    <gco:CharacterString>My data</gco:CharacterString>\n" +
            "  </cit:title>\n" +
            "  <cit:series uuidref=\"" + UUID_VALUE + "\">\n" +
            "    <cit:CI_Series>\n" +
            "      <cit:name>\n" +
            "        <gco:CharacterString>My aggregate dataset</gco:CharacterString>\n" +
            "      </cit:name>\n" +
            "    </cit:CI_Series>\n" +
            "  </cit:series>\n" +
            "</cit:CI_Citation>";

    /**
     * A XML with a {@code uuidref} identifier in the {@code <cit:series>} element.
     */
    private static final String REFERENCED_XML =
            "<cit:CI_Citation xmlns:cit=\"" + Namespaces.CIT + '"' +
                            " xmlns:gco=\"" + Namespaces.GCO + "\">\n" +
            "  <cit:title>\n" +
            "    <gco:CharacterString>My data</gco:CharacterString>\n" +
            "  </cit:title>\n" +
            "  <cit:series uuidref=\"" + UUID_VALUE + "\"/>\n" +
            "</cit:CI_Citation>";

    /**
     * Creates a new test case.
     */
    public UUIDMarshallingTest() {
    }

    /**
     * Tests (un)marshalling of an object identified by the {@code uuid} attribute.
     * The element of interest for this test is the {@code "uuid"} attribute value
     * in the {@code <cit:CI_Series>} element of the following XML fragment:
     *
     * {@snippet lang="xml" :
     *   <cit:CI_Citation>
     *     <cit:title>
     *       <gco:CharacterString>My data</gco:CharacterString>
     *     </cit:title>
     *     <cit:series>
     *       <cit:CI_Series uuid="f8f5fcb1-d57b-4013-b3a4-4eaa40df6dcf">
     *         <cit:name>
     *           <gco:CharacterString>My aggregate dataset</gco:CharacterString>
     *         </cit:name>
     *       </cit:CI_Series>
     *     </cit:series>
     *   </cit:CI_Citation>
     *   }
     *
     * On an implementation note, the {@code uuid} and other attributes of the {@code <cit:CI_Series>}
     * elements are handled by {@link org.apache.sis.xml.bind.gco.PropertyType}.
     *
     * @throws JAXBException if an error occurred during (un)marshalling.
     */
    @Test
    public void testIdentification() throws JAXBException {
        final var citation = assertInstanceOf(Citation.class, XML.unmarshal(IDENTIFIED_XML));
        assertTitleEquals("My data", citation, "citation");
        /*
         * Programmatic verification of the Series properties,
         * which is the main object of interest in this test.
         */
        final Series series = citation.getSeries();
        assertFalse(Proxy.isProxyClass(series.getClass()), "Citation.series.isProxy");
        assertInstanceOf(IdentifiedObject.class, series, "Citation.series");
        final IdentifierMap map = ((IdentifiedObject) series).getIdentifierMap();
        assertEquals("My aggregate dataset",  series.getName().toString(), "series");
        assertNull(map.get(IdentifierSpace.HREF), "href");
        assertEquals(UUID_VALUE, String.valueOf(map.get(IdentifierSpace.UUID)));
        /*
         * Marshal the object back to XML and compare with the original string
         * supplied to this method.
         */
        final String actual = XML.marshal(citation);
        assertXmlEquals(IDENTIFIED_XML, actual, "xmlns:*");
        assertEquals(citation, XML.unmarshal(actual));
    }

    /**
     * Tests (un)marshalling of an object referenced by a {@code uuidref} attribute.
     * This test does not try to resolve the reference, but only check that the identifier is properly saved.
     *
     * <p>The element of interest for this test is the {@code "uuidref"} part
     * in the {@code <cit:series>} property of the following XML fragment:</p>
     *
     * {@snippet lang="xml" :
     *   <cit:CI_Citation>
     *     <cit:title>
     *       <gco:CharacterString>My data</gco:CharacterString>
     *     </cit:title>
     *     <cit:series uuidref="f8f5fcb1-d57b-4013-b3a4-4eaa40df6dcf">
     *       <cit:CI_Series>
     *         <cit:name>
     *           <gco:CharacterString>My aggregate dataset</gco:CharacterString>
     *         </cit:name>
     *       </cit:CI_Series>
     *     </cit:series>
     *   </cit:CI_Citation>
     *   }
     *
     * On an implementation note, the {@code uuidref}, {@code xlink:href} and other attributes of the
     * {@code <cit:series>} element are handled by {@link org.apache.sis.xml.bind.gco.PropertyType}.
     *
     * @throws JAXBException if an error occurred during (un)marshalling.
     */
    @Test
    public void testReference() throws JAXBException {
        final var citation = assertInstanceOf(Citation.class, XML.unmarshal(REFERENCED_XML_WITH_BODY));
        assertTitleEquals("My data", citation, "citation");
        /*
         * Programmatic verification of the Series properties,
         * which is the main object of interest in this test.
         */
        final Series series = citation.getSeries();
        assertInstanceOf(IdentifiedObject.class, series, "Citation.series");
        assertFalse(Proxy.isProxyClass(series.getClass()), "Citation.series.isProxy");
        assertEquals("My aggregate dataset", series.getName().toString(), "Citation.series.name");
        final IdentifierMap map = ((IdentifiedObject) series).getIdentifierMap();
        assertNull(map.get(IdentifierSpace.HREF), "href");
        assertEquals(UUID_VALUE, map.get(IdentifierSpace.UUID), "uuid");
        /*
         * Marshal the object back to XML and compare with the expected result. The result shall be
         * slightly different than the original XML, since the UUID in the <cit:series> element shall
         * move to the <cit:CI_Series> element. This is the expected behavior because we have a fully
         * constructed object, not a reference to an object defined elsewhere.
         */
        final String actual = XML.marshal(citation);
        assertXmlEquals(IDENTIFIED_XML, actual, "xmlns:*");
        assertEquals(citation, XML.unmarshal(actual));
    }

    /**
     * The same test as {@link #testReference()}, except that the {@code <cit:CI_Series>} element is empty.
     * This situation shall force the creation of a new, empty, element for storing the {@code uuidref} information.
     *
     * @throws JAXBException if an error occurred during (un)marshalling.
     */
    @Test
    public void testReferenceInEmptyObject() throws JAXBException {
        final var citation = assertInstanceOf(Citation.class, XML.unmarshal(REFERENCED_XML));
        assertTitleEquals("My data", citation, "citation");
        /*
         * Programmatic verification of the Series properties,
         * which is the main object of interest in this test.
         */
        final Series series = citation.getSeries();
        assertInstanceOf(IdentifiedObject.class, series, "Citation.series");
        assertNull(series.getName(), "Citation.series.name");
        assertTrue(Proxy.isProxyClass(series.getClass()), "Citation.series.isProxy");
        assertInstanceOf(NilObject.class, series, "Citation.series");
        assertEquals("Series[{gco:uuid=“" + UUID_VALUE + "”}]", series.toString());
        final IdentifierMap map = ((IdentifiedObject) series).getIdentifierMap();
        assertNull(map.get(IdentifierSpace.HREF), "href");
        assertEquals(UUID_VALUE, map.get(IdentifierSpace.UUID), "uuid");
        /*
         * Marshal the object back to XML and compare with the expected result.
         */
        final String actual = XML.marshal(citation);
        assertXmlEquals(REFERENCED_XML, actual, "xmlns:*");
        assertEquals(citation, XML.unmarshal(actual));
    }
}
