/*
 * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     rbarkhouse - 2010-03-04 12:22:11 - initial implementation
package org.eclipse.persistence.testing.jaxb.dynamic;

import org.eclipse.persistence.dynamic.DynamicEntity;
import org.eclipse.persistence.internal.dynamic.DynamicEntityImpl;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.XMLRoot;
import org.eclipse.persistence.testing.jaxb.JAXBTestCases;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;

public class DynamicJAXBFromSessionsXMLTestCases extends JAXBTestCases {

    private static final String SESSION_NAMES =
        "org.eclipse.persistence.testing.jaxb.dynamic:org.eclipse.persistence.testing.jaxb.dynamic.secondproject";
    private static final String XML_RESOURCE =
        "org/eclipse/persistence/testing/jaxb/dynamic/root-instance.xml";

    private static final String DOCWRAPPER_CLASS_NAME =
        "org.persistence.testing.jaxb.dynamic.xxx.DocWrapper";
    private static final String ROOT_CLASS_NAME =
        "org.persistence.testing.jaxb.dynamic.xxx.Root";
    private static final String COMPOBJ_CLASS_NAME =
        "org.persistence.testing.jaxb.dynamic.xxx.CompositeObjectTarget";
    private static final String COMPCOLL_CLASS_NAME =
        "org.persistence.testing.jaxb.dynamic.xxx.CompositeCollectionTarget";
    private static final String ANYOBJ_CLASS_NAME =
        "org.persistence.testing.jaxb.dynamic.xxx.AnyObjectTarget";
    private static final String ANYCOLL_CLASS_NAME =
        "org.persistence.testing.jaxb.dynamic.xxx.AnyCollectionTarget";
    private static final String ANYATT_CLASS_NAME =
        "org.persistence.testing.jaxb.dynamic.xxx.AnyAttributeTarget";
    private static final String OBJREFSUB_CLASS_NAME =
        "org.persistence.testing.jaxb.dynamic.xxx.ObjectReferenceSubclassTarget";
    private static final String COLLREF_CLASS_NAME =
        "org.persistence.testing.jaxb.dynamic.xxx.CollectionReferenceTarget";

    private static final String EMP_CLASS_NAME =
        "org.persistence.testing.jaxb.dynamic.zzz.Employee";

    private static final Map<String, Object> PROPERTIES = new HashMap<>(1);
    static {
        PROPERTIES.put(JAXBContextProperties.MOXY_FACTORY, JAXBContextProperties.Factory.DYNAMIC);
    }

    protected ArrayList objectsAlreadyCheckedForEquality;

    public DynamicJAXBFromSessionsXMLTestCases(String name) throws Exception {
        super(name);

        setControlDocument(XML_RESOURCE);

        // Calling newInstance requires eclipselink.moxy.factory="dynamic" to end up in DynamicJAXBContextFactory.createContext
        jaxbContext = DynamicJAXBContext.newInstance(SESSION_NAMES, Thread.currentThread().getContextClassLoader(), PROPERTIES);
        jaxbMarshaller = jaxbContext.createMarshaller();
        jaxbUnmarshaller = jaxbContext.createUnmarshaller();

        xmlContext = ((DynamicJAXBContext) jaxbContext).getXMLContext();
    }

    @Override
    protected Object getControlObject() {
        DynamicEntity docWrapper = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(DOCWRAPPER_CLASS_NAME);

        DynamicEntity root = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(ROOT_CLASS_NAME);

        DynamicEntity compObj = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(COMPOBJ_CLASS_NAME);
        compObj.set("value", null);
        Vector anyColl = new Vector(2);
        DynamicEntity anyItem1 = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(ANYCOLL_CLASS_NAME);
        anyItem1.set("value", "aNyOne");
        anyColl.add(anyItem1);
        DynamicEntity anyItem2 = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(ANYCOLL_CLASS_NAME);
        anyItem2.set("value", "aNyTwo");
        anyColl.add(anyItem2);
        compObj.set("anyColl", anyColl);
        root.set("compObj", compObj);

        ArrayList compColl = new ArrayList();
        DynamicEntity item1 = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(COMPCOLL_CLASS_NAME);
        item1.set("value", "123");
        DynamicEntity item2 = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(COMPCOLL_CLASS_NAME);
        item2.set("value", "456");
        item1.set("invRef", root);
        item2.set("invRef", root);
        compColl.add(item1);
        compColl.add(item2);
        root.set("compColl", compColl);

        Vector comDirColl = new Vector(2);
        comDirColl.add("qWe");
        comDirColl.add("rTy");
        root.set("compDirColl", comDirColl);

        root.set("binData", new byte[] { 'a', 'b', 'c', 'd', 'e' });

        Vector binDataColl = new Vector();
        binDataColl.add(new byte[] { '1', '2', '3' });
        binDataColl.add(new byte[] { '4', '5', '6' });
        root.set("binDataColl", binDataColl);

        DynamicEntity anyObj = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(ANYOBJ_CLASS_NAME);
        anyObj.set("value", "aNy");
        root.set("anyObj", anyObj);

        DynamicEntity anyAtt = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(ANYATT_CLASS_NAME);
        Properties anyAttProps = new Properties();
        anyAttProps.put(new QName("", "anyAtt1"), "aNyaTT1");
        anyAttProps.put(new QName("", "anyAtt2"), "aNyAttTWO");
        anyAtt.set("value", anyAttProps);
        root.set("anyAtt", anyAtt);

        String[] transform = new String[2];
        transform[0] = "transformValue1";
        transform[1] = "transformValue2";
        root.set("transform", transform);

        Document doc;
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.newDocument();
        } catch(ParserConfigurationException e) {
            throw new RuntimeException(e);
        }

        Element elem = doc.createElementNS("", "frag");
        Text text = doc.createTextNode("XML Fragment Text Content");
        elem.appendChild(text);
        root.set("frag", elem);

        Vector fragColl = new Vector(2);
        Element elem1 = doc.createElementNS("", "frag-coll");
        Text text1 = doc.createTextNode("XML Fragment Collection One");
        elem1.appendChild(text1);
        Element elem2 = doc.createElementNS("", "frag-coll");
        Text text2 = doc.createTextNode("XML Fragment Collection Two");
        elem2.appendChild(text2);
        fragColl.add(elem1);
        fragColl.add(elem2);
        root.set("fragColl", fragColl);

        DynamicEntity objRef = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(OBJREFSUB_CLASS_NAME);
        objRef.set("id", "13579");
        objRef.set("superclassValue", "SUPER");
        objRef.set("subclassValue", "SUB");
        root.set("objRef", objRef);

        Vector collRef = new Vector(2);
        DynamicEntity collRef1 = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(COLLREF_CLASS_NAME);
        collRef1.set("id", "112233");
        collRef1.set("value", "collRefVal1");
        DynamicEntity collRef2 = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(COLLREF_CLASS_NAME);
        collRef2.set("id", "445566");
        collRef2.set("value", "collRefVal2");
        collRef.add(collRef1);
        collRef.add(collRef2);
        root.set("collRef", collRef);

        root.set("choice", 2112);

        Vector choiceColl = new Vector(3);
        choiceColl.add(3.14159);
        choiceColl.add("Pi");
        choiceColl.add(Boolean.TRUE);
        root.set("choiceColl", choiceColl);

        docWrapper.set("root", root);
        docWrapper.set("objRefTarget", objRef);
        docWrapper.set("collRefTarget", collRef);

        return docWrapper;
    }

    public void testSecondProject() throws Exception {
        // Ensure that the second project's descriptor is also available from this context

        DynamicEntity emp = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(EMP_CLASS_NAME);
        assertNotNull("Could not instantiate Descriptor from second project.", emp);
    }

    public void testGetSetByXPath() throws Exception {
        NamespaceResolver nsResolver = new NamespaceResolver();
        nsResolver.put("ns0", "myNamespace");
        nsResolver.put("xsi", javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);

        URL url = ClassLoader.getSystemResource(resourceName);
        DynamicEntity docWrapper = (DynamicEntity) jaxbUnmarshaller.unmarshal(url);

        DynamicEntity root = ((DynamicJAXBContext) jaxbContext).getValueByXPath(docWrapper, "ns0:root", nsResolver, DynamicEntity.class);
        assertNotNull(root);

        DynamicEntity anyAtt = root.get("anyAtt");
        DynamicEntity anyAttFromXPath = ((DynamicJAXBContext) jaxbContext).getValueByXPath(root, "any-att", nsResolver, DynamicEntity.class);
        assertEquals(anyAtt, anyAttFromXPath);

        DynamicEntity newAnyAtt = ((DynamicJAXBContext) jaxbContext).newDynamicEntity(ANYATT_CLASS_NAME);
        Properties anyAttProps = new Properties();
        anyAttProps.put(new QName("", "anyAtt1"), "333");
        anyAttProps.put(new QName("", "anyAtt2"), "fourfourfour");
        newAnyAtt.set("value", anyAttProps);

        ((DynamicJAXBContext) jaxbContext).setValueByXPath(root, "any-att", nsResolver, newAnyAtt);

        DynamicEntity newAnyAttFromXPath = ((DynamicJAXBContext) jaxbContext).getValueByXPath(root, "any-att", nsResolver, DynamicEntity.class);
        assertEquals(newAnyAtt, newAnyAttFromXPath);

        Object compColl = root.get("compColl");
        Object compCollFromXPath = ((DynamicJAXBContext) jaxbContext).getValueByXPath(root, "comp-coll/item", nsResolver, ArrayList.class);
        assertEquals(compColl, compCollFromXPath);
    }

    protected void compareObjects(Object controlObject, Object testObject) throws Exception {
        if (objectsAlreadyCheckedForEquality.contains(testObject)) {
            // To handle cyclic relationships, only check each pair of DynamicEntities once
            return;
        }

        if (controlObject == null && testObject == null) {
            // Nothing to check
            return;
        }

        if (controlObject == null && testObject != null) {
            fail("testObject was [" + testObject + "] but controlObject was [" + controlObject + "].");
        }

        if (controlObject != null && testObject == null) {
            fail("testObject was [" + testObject + "] but controlObject was [" + controlObject + "].");
        }

        if (testObject instanceof Element && controlObject instanceof Element) {
            Element testElement = (Element) testObject;
            Element controlElement = (Element) controlObject;

            boolean equal = true;
            try {
                if (testElement.getNodeType() == Node.ATTRIBUTE_NODE && controlElement.getNodeType() == Node.ATTRIBUTE_NODE) {
                    Attr att1 = (Attr) testElement;
                    Attr att2 = (Attr) controlElement;
                    equal = equal && att1.getNodeValue().equals(att2.getNodeValue());
                } else if (testElement.getNodeType() == Node.TEXT_NODE && controlElement.getNodeType() == Node.TEXT_NODE) {
                    Text text1 = (Text) testElement;
                    Text text2 = (Text) controlElement;
                    equal = equal && text1.getNodeValue().equals(text2.getNodeValue());
                } else if (testElement.getNodeType() == Node.ELEMENT_NODE && controlElement.getNodeType() == Node.ELEMENT_NODE) {
                    Element elem1 = testElement;
                    Element elem2 = controlElement;
                    equal = equal && elem1.getNodeName().equals(elem2.getNodeName());
                    equal = equal && (elem1.getChildNodes().getLength() == elem2.getChildNodes().getLength());
                    compareObjects(elem1.getFirstChild().getNodeValue(), elem2.getFirstChild().getNodeValue());
                }
                if (!equal) {
                    fail("testElement was [" + testElement + "] but controlElement was [" + controlElement + "].");
                }
            } catch (Exception e) {
                fail("Element comparison failed: " + e.getLocalizedMessage());
            }
            return;
        }

        if (testObject instanceof List && controlObject instanceof List) {
            if ((((List) testObject).size()) != (((List) controlObject).size())) {
                fail("testObject had [" + ((List) testObject).size() + "] elements but controlObject had [" + ((List) controlObject).size() + "].");
            }
            for (int i = 0; i < ((List) controlObject).size(); i++) {
                compareObjects(((List) controlObject).get(i), ((List) testObject).get(i));
            }
            // We've now checked all the elements so return
            return;
        }

        if (testObject.getClass().isArray() && controlObject.getClass().isArray()) {
            if (!Helper.comparePotentialArrays(testObject, controlObject)) {
                fail("controlObject [" + controlObject + "] is not equal to testObject [" + testObject + "].");
            }
            // We've now checked all the elements so return
            return;
        }

        if (controlObject instanceof DynamicEntityImpl && testObject instanceof DynamicEntityImpl) {
            DynamicEntityImpl dynamicControl = (DynamicEntityImpl) controlObject;
            DynamicEntityImpl dynamicTest = (DynamicEntityImpl) testObject;

            String controlClass = dynamicControl.getType().getClassName();
            String testClass = dynamicTest.getType().getClassName();
            if (!controlClass.equals(testClass)) {
                fail("testObject [" + testClass + "] and controlObject [" + controlClass + "] are not the same class.");
            }

            objectsAlreadyCheckedForEquality.add(testObject);

            if (dynamicControl.getType().getNumberOfProperties() != dynamicTest.getType().getNumberOfProperties()) {
                fail("testObject and controlObject did not have the same number of properties.");
            }

            List<String> propNames = dynamicControl.getType().getPropertiesNames();
            Iterator<String> it = propNames.iterator();
            while (it.hasNext()) {
                String propName = it.next();

                Object controlValue = dynamicControl.get(propName);
                Object testValue = dynamicTest.get(propName);

                compareObjects(controlValue, testValue);
            }
        } else {
            if (!testObject.equals(controlObject)) {
                fail("testObject was [" + testObject + "] but controlObject was [" + controlObject + "].");
            }
        }
    }

    @Override
    public void xmlToObjectTest(Object testObject) throws Exception {
        log("\n**xmlToObjectTest**");
        log("Expected:");
        Object controlObject = getReadControlObject();
        if (null == controlObject) {
            log((String) null);
        } else {
            log(controlObject.toString());
        }
        log("Actual:");
        if (null == testObject) {
            log((String) null);
        } else {
            log(testObject.toString());
        }

        if ((controlObject instanceof XMLRoot) && (testObject instanceof XMLRoot)) {
            XMLRoot controlRoot = (XMLRoot) controlObject;
            XMLRoot testRoot = (XMLRoot) testObject;
            compareXMLRootObjects(controlRoot, testRoot);
        } else {
            objectsAlreadyCheckedForEquality = new ArrayList();
            compareObjects(controlObject, testObject);
        }
    }

}
