/*
 * Copyright (c) 2011, 2018 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:
//     05/19/2010-2.1 ailitchev - Bug 244124 - Add Nested FetchGroup
package org.eclipse.persistence.testing.tests.jpa.fetchgroups;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.Collection;
import java.util.Map;

import javax.persistence.EntityManagerFactory;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.VersionLockingPolicy;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.indirection.IndirectContainer;
import org.eclipse.persistence.indirection.ValueHolderInterface;
import org.eclipse.persistence.internal.queries.AttributeItem;
import org.eclipse.persistence.internal.queries.EntityFetchGroup;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.queries.FetchGroup;
import org.eclipse.persistence.queries.FetchGroupTracker;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.server.Server;

/**
 * Test utility to verify the state of entities after they are loaded, copied,
 * or detached with respect to a defined FetchGroup.
 *
 * @author dclarke
 * @since EclipseLink 2.1.0
 */
public class FetchGroupAssert {

    /**
     * Verify that a FetchGroup is valid with respect to the mappings of the
     * provided entity class.
     */
    public static boolean isValid(FetchGroup fetchGroup, EntityManagerFactory emf, Class<?> entityClass) {
        assertNotNull(fetchGroup);
        Session session = ((org.eclipse.persistence.jpa.JpaEntityManager)emf.createEntityManager()).getServerSession();
        ClassDescriptor descriptor = session.getDescriptor(entityClass);
        try {
            for (Map.Entry<String, AttributeItem> entry : fetchGroup.getItems().entrySet()) {
                AttributeItem item = entry.getValue();
                DatabaseMapping mapping = descriptor.getMappingForAttributeName(item.getAttributeName());

                if (mapping.isForeignReferenceMapping()) {
                    if (item.getGroup() != null) {
                        if (!isValid((FetchGroup)item.getGroup(), emf, ((ForeignReferenceMapping) mapping).getReferenceClass())) {
                            return false;
                        }
                    }
                } else {
                    return false;
                }
            }
        } catch (QueryException qe) {
            return false;
        }
        return true;
    }

    /**
     * Verify that the attribute path specified is loaded in the provided entity
     */
    public static void assertFetchedAttribute(EntityManagerFactory emf, Object entity, String... attribute) {
        assertNotNull("EntityManagerFactory is null", emf);
        assertNotNull("Entity is null", entity);
        Server session = ((org.eclipse.persistence.jpa.JpaEntityManager)emf.createEntityManager()).getServerSession();
        assertNotNull("No Server session found for: " + emf, session);
        ClassDescriptor desc = session.getClassDescriptor(entity);
        assertNotNull("No descriptor found for: " + entity, desc);

        Object value = entity;
        if (attribute.length > 1) {
            String attrName = attribute[1];

            if (desc.hasFetchGroupManager()) {
                assertTrue("Attribute: '" + attrName + "' not fetched on: " + value, desc.getFetchGroupManager().isAttributeFetched(value, attrName));
            }
            DatabaseMapping mapping = desc.getMappingForAttributeName(attrName);
            value = mapping.getAttributeValueFromObject(value);

            if (value instanceof IndirectContainer) {
                value = ((IndirectContainer) value).getValueHolder();
            }
            if (value instanceof ValueHolderInterface) {
                ValueHolderInterface vhi = (ValueHolderInterface) value;
                assertTrue("ValueHolder for: '" + attrName + "' not instantiated", vhi.isInstantiated());
                value = vhi.getValue();
            }
            String[] tail = new String[attribute.length - 1];
            System.arraycopy(attribute, 1, tail, 0, attribute.length - 1);
            if (value instanceof Collection<?>) {
                for (Object obj : ((Collection<?>) value)) {
                    assertFetchedAttribute(emf, value, tail);
                }
            } else {
                assertFetchedAttribute(emf, value, tail);
            }
        } else {
            // This is where the actual end attribute in the path is validated.
            if (desc.hasFetchGroupManager()) {
                assertTrue(desc.getFetchGroupManager().isAttributeFetched(value, attribute[0]));
            }
        }
    }

    /**
     * Verify that the attribute path is loaded.
     */
    public static void assertNotFetchedAttribute(EntityManagerFactory emf, Object entity, String... attribute) {
        assertNotNull("EntityManagerFactory is null", emf);
        assertNotNull("Entity is null", entity);
        Server session = ((org.eclipse.persistence.jpa.JpaEntityManager)emf.createEntityManager()).getServerSession();
        assertNotNull("No Server session found for: " + emf, session);
        ClassDescriptor desc = session.getClassDescriptor(entity);
        assertNotNull("No descriptor found for: " + entity, desc);

        Object value = entity;
        for (int index = 0; index < attribute.length - 1; index++) {
            String attrName = attribute[index];

            if (desc.hasFetchGroupManager()) {
                assertTrue("Attribute: '" + attrName + "' not fetched on: " + value, desc.getFetchGroupManager().isAttributeFetched(value, attrName));
            }
            DatabaseMapping mapping = desc.getMappingForAttributeName(attrName);
            value = mapping.getAttributeValueFromObject(value);

            if (value instanceof IndirectContainer) {
                value = ((IndirectContainer) value).getValueHolder();
            }
            if (value instanceof ValueHolderInterface) {
                ValueHolderInterface vhi = (ValueHolderInterface) value;
                assertTrue("ValueHolder for: '" + attrName + "' not instantiated", vhi.isInstantiated());
                value = vhi.getValue();
            }
        }
    }

    /**
     * Assert that the entity provided has the attributes defined in the
     * FetchGroup loaded.
     *
     * @param emf
     * @param entity
     * @param fetchGroup
     */
    public static void assertFetched(EntityManagerFactory emf, Object entity, FetchGroup fetchGroup) {
        assertNotNull("Null entity", entity);
        assertNotNull("No FetchGroup provided", fetchGroup);
        if (!(entity instanceof FetchGroupTracker)) {
            System.out.println();
        }
        assertTrue("Entity does not implement FetchGroupTracker: " + entity, entity instanceof FetchGroupTracker);

        FetchGroupTracker tracker = (FetchGroupTracker) entity;
        assertNotNull("FetchGroup on entity is null", tracker._persistence_getFetchGroup());
        FetchGroup groupToCompare = fetchGroup;
        if(!fetchGroup.isEntityFetchGroup()) {
            groupToCompare = new EntityFetchGroup(fetchGroup);
        }
        assertTrue("FetchGroup on entity does not equal provided", tracker._persistence_getFetchGroup().equals(groupToCompare));

        Server session = ((org.eclipse.persistence.jpa.JpaEntityManager)emf.createEntityManager()).getServerSession();
        assertNotNull(session);
        ClassDescriptor descriptor = session.getClassDescriptor(entity);
        assertNotNull(descriptor);
        assertTrue("", descriptor.getJavaClass().isAssignableFrom(entity.getClass()));

        for (DatabaseMapping mapping : descriptor.getMappings()) {
            if (descriptor.getObjectBuilder().getPrimaryKeyMappings().contains(mapping)) {
                assertTrue("PrimaryKey mapping not fetched: " + entity, tracker._persistence_isAttributeFetched(mapping.getAttributeName()));
            } else if (descriptor.usesOptimisticLocking() && descriptor.getOptimisticLockingPolicy() instanceof VersionLockingPolicy && ((VersionLockingPolicy) descriptor.getOptimisticLockingPolicy()).getVersionMapping() == mapping) {
                assertTrue("Optimistic version mapping not fetched: " + entity, tracker._persistence_isAttributeFetched(mapping.getAttributeName()));
            } else if (tracker._persistence_getFetchGroup().containsAttribute(mapping.getAttributeName())) {
                assertTrue(tracker._persistence_isAttributeFetched(mapping.getAttributeName()));
                // EntityFetchGroup never has nested fetch groups.
/*                AttributeItem attrFI = tracker._persistence_getFetchGroup().getItem(mapping.getAttributeName());
                if (attrFI.getGroup() != null) {
                    Object value = mapping.getAttributeValueFromObject(entity);
                    if (value instanceof IndirectContainer) {
                        assertTrue(((IndirectContainer) value).isInstantiated());
                        Collection<?> values = (Collection<?>) value;
                        for (Object val : values) {
                            assertFetched(emf, val, (FetchGroup)attrFI.getGroup());
                        }
                        return;
                    }
                    if (value instanceof ValueHolderInterface) {
                        assertTrue(((ValueHolderInterface) value).isInstantiated());
                        value = ((ValueHolderInterface) value).getValue();
                    }
                    if (value != null) {
                        assertFetched(emf, value, (FetchGroup)attrFI.getGroup());
                    }
                }*/
            } else { // Should not be fetched
                assertFalse(tracker._persistence_isAttributeFetched(mapping.getAttributeName()));
            }
        }
    }

    public static void assertDefaultFetched(EntityManagerFactory emf, Object entity) {
        assertNotNull("Null entity", entity);

        ClassDescriptor descriptor = ((org.eclipse.persistence.jpa.JpaEntityManager)emf.createEntityManager()).getServerSession().getClassDescriptor(entity);
        assertNotNull("No descriptor found for: " + entity, descriptor);

        assertTrue("No FetchGroupManager on: " + descriptor, descriptor.hasFetchGroupManager());

        FetchGroup defaultFG = descriptor.getFetchGroupManager().getDefaultFetchGroup();
        assertNotNull("No default FetchGroup on: " + descriptor, defaultFG);

        assertFetched(emf, entity, defaultFG);
    }

    public static void assertFetched(EntityManagerFactory emf, Object entity, String fetchGroupName) {
        assertNotNull("Null entity", entity);

        ClassDescriptor descriptor = ((org.eclipse.persistence.jpa.JpaEntityManager)emf.createEntityManager()).getServerSession().getClassDescriptor(entity);
        assertNotNull("No descriptor found for: " + entity, descriptor);

        assertTrue("No FetchGroupManager on: " + descriptor, descriptor.hasFetchGroupManager());

        FetchGroup fg = descriptor.getFetchGroupManager().getFetchGroup(fetchGroupName);

        assertNotNull("No FetchGroup named: " + fetchGroupName, fg);

        assertFetched(emf, entity, fg);
    }

    /**
     * Verify that the provided entity does not have a FetchGroup configured on
     * it.
     */
    public static void assertNoFetchGroup(EntityManagerFactory emf, Object entity) {
        if (entity instanceof FetchGroupTracker) {
            FetchGroupTracker tracker = (FetchGroupTracker) entity;

            assertNull("Entity: " + entity + " has: " + tracker._persistence_getFetchGroup(), tracker._persistence_getFetchGroup());
        }
    }

    public static void assertConfig(EntityManagerFactory emf, String entityName, FetchGroup defaultFetchGroup) {
        assertConfig(emf, entityName, defaultFetchGroup, 0);
    }

    public static void assertConfig(EntityManagerFactory emf, String entityName, FetchGroup defaultFetchGroup, int numNamedFetchGroups) {
        ClassDescriptor descriptor = ((org.eclipse.persistence.jpa.JpaEntityManager)emf.createEntityManager()).getServerSession().getClassDescriptorForAlias(entityName);
        assertNotNull("Not descriptor found for: " + entityName, descriptor);
        assertConfig(descriptor, defaultFetchGroup, numNamedFetchGroups);
    }

    public static void assertConfig(ClassDescriptor descriptor, FetchGroup defaultFetchGroup, int numNamedFetchGroups) {
        String entityName = descriptor.getAlias();

        assertTrue("FetchGroupTracker not implemented by: " + entityName, FetchGroupTracker.class.isAssignableFrom(descriptor.getJavaClass()));

        if (defaultFetchGroup == null) {
            assertNull("Default FetchGroup not null: " + entityName, descriptor.getFetchGroupManager().getDefaultFetchGroup());
        } else {
//            assertEquals("Default FetchGroup does not match", defaultFetchGroup, descriptor.getFetchGroupManager().getDefaultFetchGroup());
            if(defaultFetchGroup != descriptor.getFetchGroupManager().getDefaultFetchGroup()) {
                fail("Default FetchGroup does not match");
            }
        }

        if(numNamedFetchGroups != descriptor.getFetchGroupManager().getFetchGroups().size()) {
            fail("Incorrect number of Named FetchGroups: " + entityName);
        }
    }

    public static void assertConfig(ClassDescriptor descriptor, FetchGroup defaultFetchGroup) {
        assertConfig(descriptor, defaultFetchGroup, 0);
    }
}
