/**
 * Copyright (c) 2017-2024 Linagora
 * 
 * This program/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, either version 2.1 of the License, or (at your
 * option) any later version.
 * 
 * This program/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.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program/library; If not, see http://www.gnu.org/licenses/
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.binding.rest.exchange;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;

import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.junit.Test;
import org.ow2.petals.jbi.xml.BytesSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.ebmwebsourcing.easycommons.stream.EasyByteArrayOutputStream;
import com.ebmwebsourcing.easycommons.xml.SourceHelper;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import de.odysseus.staxon.json.JsonXMLConfig;
import de.odysseus.staxon.json.JsonXMLConfigBuilder;
import de.odysseus.staxon.json.JsonXMLInputFactory;
import de.odysseus.staxon.json.JsonXMLOutputFactory;

/**
 * Unit test about JSON &lt;-&gt; XML conversion
 * 
 * @author Christophe DENEUX - Linagora
 *
 */
public class JSONHelperTest {

    private static final String METADATAS = "metadatas";

    private static final String METADATA = "metadata";

    private static final String NAME = "name";

    private static final String TITLE = "title";

    private static final String AUTHOR = "author";

    private static final String VALUE = "value";

    private static final String TITLE_VAL = "The thruth on ESB";

    private static final String AUTHOR_VAL = "Christophe DENEUX";

    /**
     * Check the conversion of JSON to XML to JSON of a payload containing an array of structure.
     */
    @Test
    public void arrayAndStructure() throws Exception {

        final JsonXMLConfig config = new JsonXMLConfigBuilder().virtualRoot(METADATAS).multiplePI(true).autoArray(true)
                .build();

        final JsonObject metadata1 = new JsonObject();
        metadata1.addProperty(NAME, TITLE);
        metadata1.addProperty(VALUE, TITLE_VAL);
        final JsonObject metadata2 = new JsonObject();
        metadata2.addProperty(NAME, AUTHOR);
        metadata2.addProperty(VALUE, AUTHOR_VAL);
        final JsonArray metadatasIn = new JsonArray();
        metadatasIn.add(metadata1);
        metadatasIn.add(metadata2);
        final JsonObject jsonIn = new JsonObject();
        jsonIn.add(METADATA, metadatasIn);

        System.out.println("Initial JSON: " + jsonIn.toString());

        // JSON to XML conversion
        final InputStream jsonIs = new ByteArrayInputStream(jsonIn.toString().getBytes());
        final EasyByteArrayOutputStream baosJsonAsXml = new EasyByteArrayOutputStream();
        JSONHelper.convertJSONToXML(jsonIs, new StreamResult(baosJsonAsXml), new JsonXMLInputFactory(config));

        System.out.println("JSON --> XML : " + baosJsonAsXml.toString());

        // Check the XML output
        final Document jsonAsXml = SourceHelper.toDocument(new StreamSource(baosJsonAsXml.toByteArrayInputStream()));
        assertNotNull(jsonAsXml);
        final Element rootElt = jsonAsXml.getDocumentElement();
        assertNotNull(rootElt);
        assertEquals("metadatas", rootElt.getLocalName());
        final NodeList metadataNodes = rootElt.getElementsByTagName("metadata");
        assertNotNull(metadataNodes);
        assertEquals(2, metadataNodes.getLength());
        boolean titleFound = false;
        boolean authorFound = false;
        for (int i = 0; i < metadataNodes.getLength(); i++) {
            final NodeList metadataNameNodes = ((Element) metadataNodes.item(i)).getElementsByTagName("name");
            assertEquals(1, metadataNameNodes.getLength());
            final NodeList metadataValueNodes = ((Element) metadataNodes.item(i)).getElementsByTagName("value");
            assertEquals(1, metadataValueNodes.getLength());
            if (TITLE.equals(metadataNameNodes.item(0).getTextContent())) {
                assertEquals(TITLE_VAL, metadataValueNodes.item(0).getTextContent());
                titleFound = true;
            } else if (AUTHOR.equals(metadataNameNodes.item(0).getTextContent())) {
                assertEquals(AUTHOR_VAL, metadataValueNodes.item(0).getTextContent());
                authorFound = true;
            } else {
                fail("Unknown metadata: " + metadataNameNodes.item(0).getTextContent());
            }
        }
        assertTrue(titleFound);
        assertTrue(authorFound);

        // XML to JSON conversion
        final OutputStream jsonOs = new EasyByteArrayOutputStream();
        JSONHelper.convertXMLToJSON(SourceHelper.toDOMSource(jsonAsXml), jsonOs, new JsonXMLOutputFactory(config));

        System.out.println("XML --> JSON : " + jsonOs.toString());

        // Check the JSON output
        final JsonParser jsonParser = new JsonParser();
        final JsonElement xmlAsJson = jsonParser.parse(jsonOs.toString());
        assertTrue(xmlAsJson.isJsonObject());
        final JsonObject jsonOut = xmlAsJson.getAsJsonObject();
        final JsonArray metadatasOut = jsonOut.getAsJsonArray(METADATA);
        assertEquals(2, metadatasOut.size());
    }

    /**
     * Check the conversion of JSON to XML to JSON of a payload containing an empty array.
     */
    @Test
    public void emptyArray() throws Exception {

        final JsonXMLConfig config = new JsonXMLConfigBuilder().virtualRoot(METADATAS).multiplePI(true).autoArray(true)
                .build();

        final JsonArray metadatasIn = new JsonArray();
        final JsonObject jsonIn = new JsonObject();
        jsonIn.add(METADATA, metadatasIn);

        System.out.println("Initial JSON: " + jsonIn.toString());

        // JSON to XML conversion
        final InputStream jsonIs = new ByteArrayInputStream(jsonIn.toString().getBytes());
        final EasyByteArrayOutputStream baosJsonAsXml = new EasyByteArrayOutputStream();
        JSONHelper.convertJSONToXML(jsonIs, new StreamResult(baosJsonAsXml), new JsonXMLInputFactory(config));

        System.out.println("JSON --> XML : " + baosJsonAsXml.toString());

        // Check the XML output
        final Document jsonAsXml = SourceHelper.toDocument(new StreamSource(baosJsonAsXml.toByteArrayInputStream()));
        assertNotNull(jsonAsXml);
        final Element rootElt = jsonAsXml.getDocumentElement();
        assertNotNull(rootElt);
        assertEquals("metadatas", rootElt.getLocalName());
        final NodeList metadataNodes = rootElt.getElementsByTagName("*");
        assertNotNull(metadataNodes);
        assertEquals(0, metadataNodes.getLength());

        // XML to JSON conversion
        final OutputStream jsonOs = new EasyByteArrayOutputStream();
        JSONHelper.convertXMLToJSON(SourceHelper.toDOMSource(jsonAsXml), jsonOs, new JsonXMLOutputFactory(config));

        System.out.println("XML --> JSON : " + jsonOs.toString());

        // Check the JSON output
        final JsonParser jsonParser = new JsonParser();
        final JsonElement xmlAsJson = jsonParser.parse(jsonOs.toString());
        assertTrue(xmlAsJson.isJsonObject());
        final JsonObject jsonOut = xmlAsJson.getAsJsonObject();
        final JsonArray metadatasOut = jsonOut.getAsJsonArray(METADATA);
        assertEquals(0, metadatasOut.size());
    }

    /**
     * Check the conversion of JSON to XML to JSON of a payload containing a simple structure.
     */
    @Test
    public void simpleStructure() throws Exception {

        final JsonXMLConfig config = new JsonXMLConfigBuilder().virtualRoot(METADATA).multiplePI(true).autoArray(false)
                .build();

        final JsonObject jsonIn = new JsonObject();
        jsonIn.addProperty(NAME, TITLE);
        jsonIn.addProperty(VALUE, TITLE_VAL);

        System.out.println("Initial JSON: " + jsonIn.toString());

        // JSON to XML conversion
        final InputStream jsonIs = new ByteArrayInputStream(jsonIn.toString().getBytes());
        final EasyByteArrayOutputStream baosJsonAsXml = new EasyByteArrayOutputStream();
        JSONHelper.convertJSONToXML(jsonIs, new StreamResult(baosJsonAsXml), new JsonXMLInputFactory(config));

        System.out.println("JSON --> XML : " + baosJsonAsXml.toString());

        // Check the XML output
        final Document jsonAsXml = SourceHelper.toDocument(new StreamSource(baosJsonAsXml.toByteArrayInputStream()));
        assertNotNull(jsonAsXml);
        final Element rootElt = jsonAsXml.getDocumentElement();
        assertNotNull(rootElt);
        assertEquals("metadata", rootElt.getLocalName());
        final NodeList metadataNodes = rootElt.getElementsByTagName("*");
        assertNotNull(metadataNodes);
        assertEquals(2, metadataNodes.getLength());
        boolean nameFound = false;
        boolean valueFound = false;
        for (int i = 0; i < metadataNodes.getLength(); i++) {
            if ("name".equals(metadataNodes.item(i).getLocalName())) {
                assertEquals(TITLE, metadataNodes.item(i).getTextContent());
                nameFound = true;
            } else if ("value".equals(metadataNodes.item(i).getLocalName())) {
                assertEquals(TITLE_VAL, metadataNodes.item(i).getTextContent());
                valueFound = true;
            } else {
                fail("Unknown metadata: " + metadataNodes.item(i).getTextContent());
            }
        }
        assertTrue(nameFound);
        assertTrue(valueFound);

        // XML to JSON conversion
        final OutputStream jsonOs = new EasyByteArrayOutputStream();
        JSONHelper.convertXMLToJSON(SourceHelper.toDOMSource(jsonAsXml), jsonOs, new JsonXMLOutputFactory(config));

        System.out.println("XML --> JSON : " + jsonOs.toString());

        // Check the JSON output
        final JsonParser jsonParser = new JsonParser();
        final JsonElement xmlAsJson = jsonParser.parse(jsonOs.toString());
        assertTrue(xmlAsJson.isJsonObject());
        final JsonObject jsonOut = xmlAsJson.getAsJsonObject();
        assertEquals(TITLE, jsonOut.get(NAME).getAsString());
        assertEquals(TITLE_VAL, jsonOut.get(VALUE).getAsString());
    }

    /**
     * Check the conversion of JSON to XML to JSON of a payload containing a simple structure with a root.
     */
    @Test
    public void simpleStructureWithRoot() throws Exception {

        final JsonXMLConfig config = new JsonXMLConfigBuilder().multiplePI(true).autoArray(false).build();

        final JsonObject metadata = new JsonObject();
        metadata.addProperty(NAME, TITLE);
        metadata.addProperty(VALUE, TITLE_VAL);
        final JsonObject jsonIn = new JsonObject();
        jsonIn.add(METADATA, metadata);

        System.out.println("Initial JSON: " + jsonIn.toString());

        // JSON to XML conversion
        final InputStream jsonIs = new ByteArrayInputStream(jsonIn.toString().getBytes());
        final EasyByteArrayOutputStream baosJsonAsXml = new EasyByteArrayOutputStream();
        JSONHelper.convertJSONToXML(jsonIs, new StreamResult(baosJsonAsXml), new JsonXMLInputFactory(config));

        System.out.println("JSON --> XML : " + baosJsonAsXml.toString());

        // Check the XML output
        final Document jsonAsXml = SourceHelper.toDocument(new StreamSource(baosJsonAsXml.toByteArrayInputStream()));
        assertNotNull(jsonAsXml);
        final Element rootElt = jsonAsXml.getDocumentElement();
        assertNotNull(rootElt);
        assertEquals("metadata", rootElt.getLocalName());
        final NodeList metadataNodes = rootElt.getElementsByTagName("*");
        assertNotNull(metadataNodes);
        assertEquals(2, metadataNodes.getLength());
        boolean nameFound = false;
        boolean valueFound = false;
        for (int i = 0; i < metadataNodes.getLength(); i++) {
            if ("name".equals(metadataNodes.item(i).getLocalName())) {
                assertEquals(TITLE, metadataNodes.item(i).getTextContent());
                nameFound = true;
            } else if ("value".equals(metadataNodes.item(i).getLocalName())) {
                assertEquals(TITLE_VAL, metadataNodes.item(i).getTextContent());
                valueFound = true;
            } else {
                fail("Unknown metadata: " + metadataNodes.item(i).getTextContent());
            }
        }
        assertTrue(nameFound);
        assertTrue(valueFound);

        // XML to JSON conversion
        final OutputStream jsonOs = new EasyByteArrayOutputStream();
        JSONHelper.convertXMLToJSON(SourceHelper.toDOMSource(jsonAsXml), jsonOs, new JsonXMLOutputFactory(config));

        System.out.println("XML --> JSON : " + jsonOs.toString());

        // Check the JSON output
        final JsonParser jsonParser = new JsonParser();
        final JsonElement xmlAsJson = jsonParser.parse(jsonOs.toString());
        assertTrue(xmlAsJson.isJsonObject());
        final JsonObject jsonOut = xmlAsJson.getAsJsonObject();
        assertNotNull(jsonOut.get(METADATA));
        assertNotNull(jsonOut.get(METADATA).getAsJsonObject());
        assertEquals(TITLE, jsonOut.get(METADATA).getAsJsonObject().get(NAME).getAsString());
        assertEquals(TITLE_VAL, jsonOut.get(METADATA).getAsJsonObject().get(VALUE).getAsString());
    }

    @Test
    public void arrayWithOneElement() throws Exception {

        final JsonXMLConfig config = new JsonXMLConfigBuilder().multiplePI(true).build();
        final String input = "<metadatas><?xml-multiple metadata?><metadata name=\"meta-1\">val-1</metadata></metadatas>";

        System.out.println("XML : " + input);
        final OutputStream jsonOs = new EasyByteArrayOutputStream();
        JSONHelper.convertXMLToJSON(new BytesSource(input.getBytes()), jsonOs, new JsonXMLOutputFactory(config));
        System.out.println("XML --> JSON : " + jsonOs.toString());
        assertEquals("{\"metadatas\":{\"metadata\":[{\"@name\":\"meta-1\",\"$\":\"val-1\"}]}}", jsonOs.toString());
    }

    // TODO reenable staxon-gson when https://github.com/beckchr/staxon/issues/38 is fixed
    @Test
    public void newlineInXML() throws Exception {

        final JsonXMLConfig config = new JsonXMLConfigBuilder().multiplePI(true).build();

        final String input = "<metadatas>\n  <?xml-multiple metadata?>\n</metadatas>";

        System.out.println("XML : " + input);

        final OutputStream jsonOs = new EasyByteArrayOutputStream();

        JSONHelper.convertXMLToJSON(new BytesSource(input.getBytes()), jsonOs, new JsonXMLOutputFactory(config));

        System.out.println("XML --> JSON : " + jsonOs.toString());
    }
}
