/*
 * 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.inheritance.polymorphic;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.openjpa.persistence.ArgumentException;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;

/**
 * Tests a domain model with following characteristics:
 * a) A typical bidirectional ManyToOne/OneToMany relation
 *    EntityA references a single instance of EntityB
 *    EntityB references a collection of EntityA
 * b) EntityB itself is abstract
 * c) Many concrete subclasses of EntityB exist
 * d) EntityB uses TABLE_PER_CLASS inheritance strategy, hence no mapping table
 *    exists for EntityB itself.
 * e) Relation field in EntityA is declared as abstract type EntityB (for which
 * f) all the domain classes i.e. EntityA, EntityB and all its subclasses is
 *    derived from an abstract MappedSuperClass which holds primary key and
 *    version fields.
 *
 *  The test addresses a reported error [1] in mapping the above domain model.
 *  The test verifies basic persist, query and delete operations on the domain
 *  model.
 *
 *  [1] <A HREF="https://issues.apache.org/jira/browse/OPENJPA-602">
 *  OPENJPA-602</A>}
 *
 * @author Pinaki Poddar
 *
 */
public class TestTablePerClassInheritanceWithAbstractRoot extends
		SingleEMFTestCase {
	Class<?>[] UNJOINED_SUBCLASSES = {
			EnglishParagraph.class,
			FrenchParagraph.class,
			GermanParagraph.class};

    @Override
    public void setUp() {
        setUp(CLEAR_TABLES,
        		Translation.class, BaseEntity.class,
        		EnglishParagraph.class, FrenchParagraph.class,
        		GermanParagraph.class, Translatable.class);
    }

    public void populate() {
        OpenJPAEntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        /**
         * Aggregate query operations can not be performed on unjoined
         * subclasses. Hence all concrete subclasses of abstract base
         * class is counted separately to count all Translatable instances.
         */

        EnglishParagraph english = new EnglishParagraph();
        FrenchParagraph french   = new FrenchParagraph();
        GermanParagraph german   = new GermanParagraph();

        Translation translation1 = new Translation();
        Translation translation2 = new Translation();
        Translation translation3 = new Translation();
        Translation translation4 = new Translation();

        english.setContent("Hello");
        french.setContent("Bon jour");
        german.setContent("Guten Tag");


        translation1.setTranslatable(english);
        translation2.setTranslatable(english);
        translation3.setTranslatable(french);
        translation4.setTranslatable(german);

        english.addTranslation(translation1);
        english.addTranslation(translation2);
        french.addTranslation(translation3);
        german.addTranslation(translation4);

        em.persist(translation1);
        em.persist(translation2);
        em.persist(translation3);
        em.persist(translation4);
        em.getTransaction().commit();
        em.close();
    }

	@SuppressWarnings("unchecked")
    public void testConsistency() {
        OpenJPAEntityManager em = emf.createEntityManager();

        int nTranslatableBefore = count(UNJOINED_SUBCLASSES);
        int nTranslationBefore = count(Translation.class);

        populate();
		int nTranslatableAfter = count(UNJOINED_SUBCLASSES);
		int nTranslationAfter  = count(Translation.class);

		assertEquals(nTranslatableBefore+3, nTranslatableAfter);
		assertEquals(nTranslationBefore+4, nTranslationAfter);

		/**
         * Verify that if A refers to B then A must be a member of the set
		 * referred by B
		 */
		em.getTransaction().begin();
        List<Translation> result = em.createQuery("SELECT p FROM Translation p")
			.getResultList();
		assertTrue(!result.isEmpty());
		for (Translation translation : result) {
			assertTrue(translation.getTranslatable()
                    .getTranslations().contains(translation));
		}
		em.getTransaction().rollback();
		em.close();
	}


    void linkConsistently(Translation translation, Translatable translatable) {
		translatable.addTranslation(translation);
		translation.setTranslatable(translatable);
	}

	/**
     * Count the number of instances in the given class by aggregate JPQL query.
	 */
	@Override
    public int count(Class c) {
		OpenJPAEntityManager em = emf.createEntityManager();
		Number n = ((Number) em.createQuery("SELECT COUNT(p) FROM " +
                c.getSimpleName() + " p").getSingleResult()).intValue();
		closeEM(em);
		return n.intValue();
	}

	/**
     * Count total number of instances of all the given classes by separate JPQL
	 * aggregate query. Useful when a base class has unjoined subclasses.
	 */
	public int count(Class<?>... classes) {
		int total = 0;
		for (Class<?> c:classes) {
			total += count(c);
		}
		return total;
	}

    public void testEntityTypeForTablePerClassInheritance() {
        populate();
        OpenJPAEntityManager em = emf.createEntityManager();
        String query = "select tr from Translatable tr join tr.translations t where " +
                "TYPE(tr) = EnglishParagraph";
        List rs = em.createQuery(query).getResultList();
        assertEquals(2, rs.size());
        for (Object o : rs) assertTrue(o instanceof EnglishParagraph);

        query = "select distinct tr from Translatable tr join tr.translations t where " +
            "TYPE(tr) = EnglishParagraph or TYPE(tr) = FrenchParagraph";
        rs = em.createQuery(query).getResultList();
        assertEquals(2, rs.size());
        for (Object r : rs) assertTrue(!(r instanceof GermanParagraph));

        query = "select distinct tr from Translatable tr join tr.translations t where " +
            "TYPE(tr) in (?1, ?2)";
        try {
            rs = em.createQuery(query).setParameter(1, EnglishParagraph.class).
            setParameter(2, FrenchParagraph.class).getResultList();
        } catch(ArgumentException e) {
            // as expected
            //System.out.println(e.getMessage());
        }

        query = "select tr from Translatable tr join tr.translations t where " +
            "TYPE(tr) <> EnglishParagraph";
        try {
            rs = em.createQuery(query).getResultList();
        } catch(ArgumentException e) {
            // as expected
            //System.out.println(e.getMessage());
        }

        String query1 = "select t from Translation t where TYPE(t.translatable) = EnglishParagraph";
        try {
            rs = em.createQuery(query1).getResultList();
        } catch(ArgumentException e) {
            // as expected
            //System.out.println(e.getMessage());
        }

        query1 = "select tr from Translatable tr join tr.translations t where " +
                "TYPE(tr) in ?1";
        Collection<Class <?>> params = new ArrayList(2);
        params.add(EnglishParagraph.class);
        params.add(FrenchParagraph.class);
        try {
            rs = em.createQuery(query).setParameter(1, params).getResultList();
        } catch(ArgumentException e) {
            // as expected
            //System.out.println("e.getMessages()");
        } finally {
            em.close();
        }
    }
}
