/*
 * 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.HashMap;
import java.util.HashSet;
import java.util.Map;

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 TestDetachCascade extends SingleEMFTestCase {
    OpenJPAEntityManager em;
    int id = 0;
    Compatibility compat;

    Entity1 e1;     // references e14 - no cascade
    Entity3 e3;     // references e4 - cascade ALL
    Entity4 e4;     // references e5 - cascade CLEAR
    Entity5 e5;     // references e6 - no cascade
    Entity6 e6;     // references e7 - cascade CLEAR
    Entity7 e7;
    Entity8 e8;     // references e9 - cascade ALL
    Entity8 e8a;    // same as e8
    Entity9 e9;
    Entity10 e10;   // references a Collection<Entity8> - cascade ALL
    Entity11 e11;
    Entity12 e12;
    Entity13 e13;   // references a Map<String, Entity11> - cascade ALL
    Entity14 e14;

    Collection<Object> allEntities = new HashSet<>();

    @Override
    public void setUp() throws Exception {
        setUp(Entity1.class,
            Entity3.class,
            Entity4.class,
            Entity5.class,
            Entity6.class,
            Entity7.class,
            Entity8.class,
            Entity9.class,
            Entity10.class,
            Entity11.class,
            Entity12.class,
            Entity13.class,
            Entity14.class);
        assertNotNull(emf);
        em = emf.createEntityManager();
        assertNotNull(em);
        compat = emf.getConfiguration().getCompatibilityInstance();
        assertNotNull(compat);
        compat.setFlushBeforeDetach(false);
        compat.setCopyOnDetach(false);
        compat.setCascadeWithDetach(true);
    }

    private void create(int id) {
        allEntities.clear();
        e1 = new Entity1(id, "entity1");
        allEntities.add(e1);
        e3 = new Entity3(id, "entity3");
        allEntities.add(e3);
        e4 = new Entity4(id, "entity4");
        allEntities.add(e4);
        e5 = new Entity5(id, "entity5");
        allEntities.add(e5);
        e6 = new Entity6(id, "entity6");
        allEntities.add(e6);
        e7 = new Entity7(id, "entity7");
        allEntities.add(e7);
        e8 = new Entity8(id, "entity8");
        allEntities.add(e8);
        e8a = new Entity8(id + 100, "entity8a");
        allEntities.add(e8a);
        e9 = new Entity9(id, "entity9");
        allEntities.add(e9);
        e10 = new Entity10(id, "entity10");
        allEntities.add(e10);
        e11 = new Entity11(id, "entity11");
        allEntities.add(e11);
        e12 = new Entity12(id, "entity12");
        allEntities.add(e12);
        e13 = new Entity13(id, "entity13");
        allEntities.add(e13);
        e14 = new Entity14(id, "entity14");
        allEntities.add(e14);
        e1.setE14(e14);
        e3.setE4(e4);
        e4.setE5(e5);
        e5.setE6(e6);
        e6.setE7(e7);
        e8.setE9(e9);

        Collection<Entity8> collection = new HashSet<>();
        collection.add(e8);
        collection.add(e8a); // e8a contains a null value for Entity9
        e10.setCollection(collection);

        Map<String, Entity11> map = new HashMap<>();
        map.put(e11.getName(), e11);
        e13.setMap(map);

    }

    // Test that detach cascade values are set correctly
    public void testCascadeValues() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        em.getTransaction().commit();

        MetaDataRepository repos = emf.getConfiguration()
            .getMetaDataRepositoryInstance();
        // Test CascadeType.ALL
        ClassMetaData meta3 = repos.getCachedMetaData(Entity3.class);
        assertNotNull(meta3);
        assertEquals(ValueMetaData.CASCADE_IMMEDIATE,
            meta3.getField("e4").getCascadeDetach());
        // Test CascadeType.CLEAR
        ClassMetaData meta4 = repos.getCachedMetaData(Entity4.class);
        assertNotNull(meta4);
        assertEquals(ValueMetaData.CASCADE_IMMEDIATE,
            meta4.getField("e5").getCascadeDetach());
    }

    // Make sure cascade is no longer done by default
    public void testNoCascade() {
        boolean cwd = compat.getCascadeWithDetach();
        compat.setCascadeWithDetach(false);
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        assertTrue(em.contains(e1));
        assertTrue(em.contains(e14));
        em.detach(e1);
        assertFalse(em.contains(e1));
        assertTrue(em.contains(e14));
        em.getTransaction().commit();
        compat.setCascadeWithDetach(cwd);
    }

    // Change to the previous behavior to always cascade
    public void testAlwaysCascade() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        assertTrue(em.contains(e1));
        assertTrue(em.contains(e14));

        compat.setCascadeWithDetach(true);

        em.detach(e1);
        assertFalse(em.contains(e1));
        assertFalse(em.contains(e14));
        em.getTransaction().commit();

        // reset compatibility option to default
        compat.setCascadeWithDetach(false);
    }

    // Test explicit cascade of entities
    public void testCascadeOfEntities() {
        boolean cwd = compat.getCascadeWithDetach();
        compat.setCascadeWithDetach(false);
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        assertTrue(em.contains(e3));
        assertTrue(em.contains(e4));
        em.detach(e3);
        assertTrue(em.contains(e1));
        assertFalse(em.contains(e3));
        assertFalse(em.contains(e4));
        assertFalse(em.contains(e5));
        assertTrue(em.contains(e6));
        assertTrue(em.contains(e7));
        em.getTransaction().commit();
        compat.setCascadeWithDetach(cwd);
    }

    // Test always cascade of entities
    public void testAlwaysCascadeOfEntities() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        assertTrue(em.contains(e3));
        assertTrue(em.contains(e4));
        compat.setCascadeWithDetach(true);
        em.detach(e3);
        assertTrue(em.contains(e1));
        assertFalse(em.contains(e3));
        assertFalse(em.contains(e4));
        assertFalse(em.contains(e5));
        assertFalse(em.contains(e6));
        assertFalse(em.contains(e7));
        em.getTransaction().commit();
        compat.setCascadeWithDetach(false);
    }

    // test single cascade in new transaction with no fetch of e4
    public void testSingleCascadeNoFetch() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        assertTrue(em.contains(e3));
        assertTrue(em.contains(e4));
        em.getTransaction().commit();

        em.close();
        em = emf.createEntityManager();

        em.getTransaction().begin();
        em.clear();
        e3 = em.find(Entity3.class, id);
        assertTrue(em.contains(e3));
        assertFalse(em.contains(e4));
        em.getTransaction().commit();
        em.detach(e3);
        assertFalse(em.contains(e3));
        assertFalse(em.contains(e4));
    }

    // test cascade of a simple collection
    public void testCascadeOfCollection() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persist(e10);
        assertTrue(em.contains(e10));
        assertTrue(em.contains(e8));
        assertTrue(em.contains(e9));
        assertTrue(em.contains(e8a));
        em.detach(e10);
        assertFalse(em.contains(e10));
        assertFalse(em.contains(e8));
        assertFalse(em.contains(e9));
        assertFalse(em.contains(e8a));
        em.getTransaction().commit();
    }

    // test cascade of Map
    public void testCascadeOfMap() {
        id++;
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        assertTrue(em.contains(e11));
        assertTrue(em.contains(e13));
        em.detach(e13);
        assertFalse(em.contains(e13));
        assertFalse(em.contains(e11));
        em.getTransaction().commit();
    }

    // Test old detach behavior - flush, copy, and cascade
    public void testOldDetachBehavior() {
        id++;

        compat.setFlushBeforeDetach(true);
        em.getTransaction().begin();
        create(id);
        em.persistAll(allEntities);
        Entity1 e1Det = em.detachCopy(e1);
        assertNotEquals(e1, e1Det); // check for copy
        Entity14 e14Det = e1.getE14();
        assertEquals(e14, e14Det);
        em.getTransaction().commit();

        // check for flushed and cascaded flushed
        Entity1 e1Saved = em.find(Entity1.class, id);
        assertNotNull(e1Saved);
        Entity14 e14Saved = e1Saved.getE14();
        assertNotNull(e14Saved);

        compat.setFlushBeforeDetach(false);
    }

}
