/*
 * 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.openjpa.persistence.detach;

import java.util.Collection;
import java.util.HashSet;

import org.apache.openjpa.conf.Compatibility;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.meta.ValueMetaData;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;

public class TestDetachNoCascade extends SingleEMFTestCase {
    OpenJPAEntityManager em;
    int id = 0;
    Compatibility compat;

    Entity1 e1;
    Entity7 e7;
    Entity14 e14;
    Collection<Object> allEntities = new HashSet<>();

    @Override
    public void setUp() throws Exception {
        setUp(Entity1.class,    // references Entity14
            Entity7.class,
            Entity14.class,
            CLEAR_TABLES);
        assertNotNull(emf);
        em = emf.createEntityManager();
        assertNotNull(em);
        compat = emf.getConfiguration().getCompatibilityInstance();
        compat.setFlushBeforeDetach(true);
        compat.setCopyOnDetach(false);
        compat.setCascadeWithDetach(false);

    }

    private void create(int id) {
        allEntities.clear();
        e1 = new Entity1(id,"entity1");
        allEntities.add(e1);
        e7 = new Entity7(id, "entity7");
        allEntities.add(e7);
        e14 = new Entity14(id, "entity14");
        allEntities.add(e14);
        e1.setE14(e14);
    }

    // Make sure the default values are the same
    public void testDefaults() {
        assertTrue(compat.getFlushBeforeDetach());
        assertFalse(compat.getCopyOnDetach());
        assertFalse(compat.getCascadeWithDetach());
    }

    // Make sure all the fields in Entity1 are CASCADE_NONE
    public void testCascadeValues() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        em.getTransaction().commit();

        MetaDataRepository repos = emf.getConfiguration()
            .getMetaDataRepositoryInstance();
        ClassMetaData meta = repos.getCachedMetaData(Entity1.class);
        assertNotNull(meta);
        assertEquals(ValueMetaData.CASCADE_NONE,
                meta.getField("id").getCascadeDetach());
        assertEquals(ValueMetaData.CASCADE_NONE,
                meta.getField("name").getCascadeDetach());
        assertEquals(ValueMetaData.CASCADE_NONE,
                meta.getField("e14").getCascadeDetach());
    }

    // Test clear() to clear all entities.
    public void testClearAll() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        assertEquals(allEntities.size(),em.getManagedObjects().size());
        em.clear();
        assertEquals(0,em.getManagedObjects().size());
        assertFalse(em.contains(e1));
        em.getTransaction().commit();
    }

    // Test clear(Object entity) to clear 1 entity. Do not
    // cascade and make sure contained entities and other entities
    // are still managed.
    public void testClearOne() {
        id++;
        boolean cwd = compat.getCascadeWithDetach();
        boolean cod = compat.getCopyOnDetach();
        compat.setCascadeWithDetach(false);
        compat.setCopyOnDetach(false);
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        assertTrue(em.contains(e1));
        em.detach(e1);
        assertFalse(em.contains(e1));
        assertTrue(em.contains(e14));
        assertTrue(em.contains(e7));
        em.getTransaction().commit();
        compat.setCascadeWithDetach(cwd);
        compat.setCopyOnDetach(cod);
    }

    // Test clear on a new, unmanaged object. Nothing should happen. After
    // persist it should still become a managed entity.
    public void testClearNew() {
        id++;
        em.getTransaction().begin();
        create(id);
        assertFalse(em.contains(e1));
        em.detach(e1);
        em.persistAll(allEntities);
        assertTrue(em.contains(e1));
        em.getTransaction().commit();
    }

    // Test clear on dirty object. Make sure the change is not flushed.
    public void testClearDirty() {

        boolean cod = compat.getCopyOnDetach();
        boolean fbd = compat.getFlushBeforeDetach();
        compat.setCopyOnDetach(false);
        compat.setFlushBeforeDetach(false);

        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        em.getTransaction().commit();

        em.getTransaction().begin();
        e1 = em.find(Entity1.class, id);
        assertEquals("entity1",e1.getName());
        e1.setName("new name");
        em.detach(e1);
        em.getTransaction().commit();

        em.getTransaction().begin();
        em.clear();
        e1 = em.find(Entity1.class, id);
        assertNotEquals("new name", e1.getName());
        assertEquals("entity1", e1.getName());
        em.getTransaction().commit();

        compat.setCopyOnDetach(cod);
        compat.setFlushBeforeDetach(fbd);
    }

    // Remove an Entity before clearing it. Make sure it is still in the
    // DB after the commit.
    public void testClearRemove() {

        boolean cwd = compat.getCascadeWithDetach();
        boolean cod = compat.getCopyOnDetach();
        compat.setCascadeWithDetach(false);
        compat.setCopyOnDetach(false);

        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        em.getTransaction().commit();

        em.getTransaction().begin();
        e1 = em.find(Entity1.class, id);
        em.remove(e1);
        em.detach(e1);
        em.getTransaction().commit();

        em.getTransaction().begin();
        em.clear();
        e1 = em.find(Entity1.class, id);
        assertNotNull(e1);
        em.getTransaction().commit();

        compat.setCascadeWithDetach(cwd);
        compat.setCopyOnDetach(cod);
    }

    // Try to clear an entity that has already been cleared. There should be
    // no exception and the entity should still not be there.
    public void testClearOnClearedEntity() {
        boolean cwd = compat.getCascadeWithDetach();
        boolean cod = compat.getCopyOnDetach();
        compat.setCascadeWithDetach(false);
        compat.setCopyOnDetach(false);

        id++;
        em.getTransaction().begin();
        create(id);
        em.persist(e1);
        em.detach(e1);
        em.detach(e1);
        assertFalse(em.contains(e1));
        em.getTransaction().commit();

        compat.setCascadeWithDetach(cwd);
        compat.setCopyOnDetach(cod);
    }

    // Test that no copy is done by default
    public void testNoCopy() {
        boolean cwd = compat.getCascadeWithDetach();
        boolean cod = compat.getCopyOnDetach();
        compat.setCascadeWithDetach(false);
        compat.setCopyOnDetach(false);

        id++;
        em.getTransaction().begin();
        create(id);
        em.persist(e1);
        Entity14 e14PreDetach = e1.getE14();

        // Flip on cascade to detach e14
        compat.setCascadeWithDetach(true);

        em.detach(e1);
        // Assert that e14 was not copied on detachment
        assertEquals(e14, e14PreDetach);
        assertFalse(em.contains(e1));
        assertFalse(em.contains(e14));
        em.getTransaction().commit();

        compat.setCascadeWithDetach(false);

        compat.setCascadeWithDetach(cwd);
        compat.setCopyOnDetach(cod);
    }

    // Change copy option and validate
    public void testCopy() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persist(e1);

        Entity1 e1Det = em.detachCopy(e1);
        assertNotEquals(e1, e1Det);
        assertTrue(em.contains(e1));
        em.getTransaction().commit();

        compat.setCopyOnDetach(false);
    }

    // Change flush option and validate
    public void testFlush() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        em.getTransaction().commit();

        compat.setFlushBeforeDetach(true);

        em.getTransaction().begin();
        e1 = em.find(Entity1.class, id);
        assertEquals("entity1",e1.getName());
        e1.setName("new name");
        em.detach(e1);
        em.getTransaction().commit();

        em.getTransaction().begin();
        em.clear();
        e1 = em.find(Entity1.class, id);
        assertEquals("new name", e1.getName());
        em.getTransaction().commit();

        compat.setFlushBeforeDetach(false);
    }

    // Clear an entity, then recreate it in the same transaction.
    public void testClearRecreate() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        em.detach(e1);
        em.merge(e1);
        em.getTransaction().commit();

        em.getTransaction().begin();
        em.clear();
        Entity1 newE1 = em.find(Entity1.class, id);
        assertNotNull(newE1);
        em.getTransaction().commit();
    }
}
