/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.test.loadplans.plans;

import org.hibernate.cfg.Configuration;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.loader.plan.build.internal.returns.CollectionFetchableElementEntityGraph;
import org.hibernate.loader.plan.spi.BidirectionalEntityReference;
import org.hibernate.loader.plan.spi.CollectionAttributeFetch;
import org.hibernate.loader.plan.spi.EntityFetch;
import org.hibernate.loader.plan.spi.EntityReturn;
import org.hibernate.loader.plan.spi.FetchSource;
import org.hibernate.loader.plan.spi.LoadPlan;
import org.hibernate.persister.entity.OuterJoinLoadable;

import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.hibernate.test.annotations.Country;
import org.hibernate.test.annotations.cid.keymanytoone.Card;
import org.hibernate.test.annotations.cid.keymanytoone.CardField;
import org.hibernate.test.annotations.cid.keymanytoone.Key;
import org.hibernate.test.annotations.cid.keymanytoone.PrimaryKey;
import org.hibernate.test.annotations.collectionelement.Boy;
import org.hibernate.test.annotations.collectionelement.Matrix;
import org.hibernate.test.annotations.collectionelement.TestCourse;
import org.hibernate.test.loadplans.process.EncapsulatedCompositeIdResultSetProcessorTest;
import org.junit.Test;

import static junit.framework.Assert.assertNotNull;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

//import org.hibernate.loader.plan.spi.BidirectionalEntityFetch;

/**
 * Used to assert that "fetch graphs" between JoinWalker and LoadPlan are same.
 *
 * @author Steve Ebersole
 */
public class LoadPlanStructureAssertionTest extends BaseUnitTestCase {
	@Test
	public void testJoinedOneToOne() {
		// tests the mappings defined in org.hibernate.test.onetoone.joined.JoinedSubclassOneToOneTest
		Configuration cfg = new Configuration();
		cfg.addResource( "org/hibernate/test/onetoone/joined/Person.hbm.xml" );
		SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory();

		try {
//			doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( org.hibernate.test.onetoone.joined.Person.class ) );
			doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( org.hibernate.test.onetoone.joined.Entity.class ) );

//			doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( org.hibernate.test.onetoone.joined.Address.class ) );
		}
		finally {
			sf.close();
		}
	}

	@Test
	public void testSpecialOneToOne() {
		// tests the mappings defined in org.hibernate.test.onetoone.joined.JoinedSubclassOneToOneTest
		Configuration cfg = new Configuration();
		cfg.addResource( "org/hibernate/test/onetoone/formula/Person.hbm.xml" );
		SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory();

		try {
			doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( org.hibernate.test.onetoone.formula.Person.class ) );
		}
		finally {
			sf.close();
		}
	}

	@Test
	public void testEncapsulatedCompositeIdNoFetches1() {
		// CardField is an entity with a composite identifier mapped via a @EmbeddedId class (CardFieldPK) defining
		// a @ManyToOne
		Configuration cfg = new Configuration();
		cfg.addAnnotatedClass( EncapsulatedCompositeIdResultSetProcessorTest.CardField.class );
		cfg.addAnnotatedClass( EncapsulatedCompositeIdResultSetProcessorTest.Card.class );
		SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory();

		try {
			doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( EncapsulatedCompositeIdResultSetProcessorTest.CardField.class ) );
			doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( EncapsulatedCompositeIdResultSetProcessorTest.Card.class ) );
		}
		finally {
			sf.close();
		}
	}

	@Test
	public void testEncapsulatedCompositeIdNoFetches2() {
		// Parent is an entity with a composite identifier mapped via a @EmbeddedId class (ParentPK) which is defined
		// using just basic types (strings, ints, etc)
		Configuration cfg = new Configuration();
		cfg.addAnnotatedClass( EncapsulatedCompositeIdResultSetProcessorTest.Parent.class );
		SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory();

		try {
			doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( EncapsulatedCompositeIdResultSetProcessorTest.Parent.class ) );
		}
		finally {
			sf.close();
		}
	}

	@Test
	public void testEncapsulatedCompositeIdWithFetches1() {
		Configuration cfg = new Configuration();
		cfg.addAnnotatedClass( Card.class );
		cfg.addAnnotatedClass( CardField.class );
		cfg.addAnnotatedClass( Key.class );
		cfg.addAnnotatedClass( PrimaryKey.class );

		SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory();

		try {
			final OuterJoinLoadable cardFieldPersister = (OuterJoinLoadable) sf.getClassMetadata( CardField.class );
			doCompare( sf, cardFieldPersister );

			final LoadPlan loadPlan = LoadPlanStructureAssertionHelper.INSTANCE.buildLoadPlan( sf, cardFieldPersister );
			assertEquals( LoadPlan.Disposition.ENTITY_LOADER, loadPlan.getDisposition() );
			assertEquals( 1, loadPlan.getReturns().size() );
			final EntityReturn cardFieldReturn = assertTyping( EntityReturn.class, loadPlan.getReturns().get( 0 ) );
			assertEquals( 0, cardFieldReturn.getFetches().length );

			// CardField defines a composite pk with 2 many-to-ones : Card and Key (the id description acts as the composite);
			// because it is an @EmbeddedId, the ID provided by the application is used "as is"
			// and fetches are not included in the load plan.
			assertFalse( cardFieldReturn.getIdentifierDescription().hasFetches() );

			// we need the readers ordered in a certain manner.  Here specifically: Fetch(Card), Fetch(Key), Return(CardField)
			//
			// additionally, we need Fetch(Card) and Fetch(Key) to be hydrated/semi-resolved before attempting to
			// resolve the EntityKey for Return(CardField)
			//
			// together those sound like argument enough to continue keeping readers for "identifier fetches" as part of
			// a special "identifier reader".  generated aliases could help here too to remove cyclic-ness from the graph.
			// but at any rate, we need to know still when this becomes circularity
		}
		finally {
			sf.close();
		}
	}

	@Test
	public void testEncapsulatedCompositeIdWithFetches2() {
		Configuration cfg = new Configuration();
		cfg.addAnnotatedClass( Card.class );
		cfg.addAnnotatedClass( CardField.class );
		cfg.addAnnotatedClass( Key.class );
		cfg.addAnnotatedClass( PrimaryKey.class );

		final SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory();

		try {
			final OuterJoinLoadable cardPersister = (OuterJoinLoadable) sf.getClassMetadata( Card.class );
			doCompare( sf, cardPersister );

			final LoadPlan cardLoadPlan = LoadPlanStructureAssertionHelper.INSTANCE.buildLoadPlan( sf, cardPersister );
			assertEquals( LoadPlan.Disposition.ENTITY_LOADER, cardLoadPlan.getDisposition() );
			assertEquals( 1, cardLoadPlan.getReturns().size() );

			// Check the root EntityReturn(Card)
			final EntityReturn cardReturn = assertTyping( EntityReturn.class, cardLoadPlan.getReturns().get( 0 ) );
			assertFalse( cardReturn.getIdentifierDescription().hasFetches() );

			// Card should have one fetch, the fields collection
			assertEquals( 1, cardReturn.getFetches().length );
			final CollectionAttributeFetch fieldsFetch = assertTyping( CollectionAttributeFetch.class, cardReturn.getFetches()[0] );
			assertNotNull( fieldsFetch.getElementGraph() );

			// the Card.fields collection has entity elements of type CardField...
			final CollectionFetchableElementEntityGraph cardFieldElementGraph = assertTyping( CollectionFetchableElementEntityGraph.class, fieldsFetch.getElementGraph() );
			// CardField should have no fetches
			assertEquals( 0, cardFieldElementGraph.getFetches().length );
			// But it should have 1 key-many-to-one fetch for Key (Card is already handled)
			assertTrue( cardFieldElementGraph.getIdentifierDescription().hasFetches() );
			final FetchSource cardFieldElementGraphIdAsFetchSource = assertTyping(
					FetchSource.class,
					cardFieldElementGraph.getIdentifierDescription()
			);
			assertEquals( 1, cardFieldElementGraphIdAsFetchSource.getFetches().length );
			assertEquals( 1, cardFieldElementGraphIdAsFetchSource.getBidirectionalEntityReferences().length );

			BidirectionalEntityReference circularCardFetch = assertTyping(
					BidirectionalEntityReference.class,
					cardFieldElementGraphIdAsFetchSource.getBidirectionalEntityReferences()[0]
			);
			assertSame( circularCardFetch.getTargetEntityReference(), cardReturn );

			// the fetch above is to the other key-many-to-one for CardField.primaryKey composite: key
			EntityFetch keyFetch = assertTyping(
					EntityFetch.class,
					cardFieldElementGraphIdAsFetchSource.getFetches()[0]
			);
			assertEquals( Key.class.getName(), keyFetch.getEntityPersister().getEntityName() );
		}
		finally {
			sf.close();
		}
	}

	@Test
	public void testManyToMany() {
		Configuration cfg = new Configuration();
		cfg.addResource( "org/hibernate/test/immutable/entitywithmutablecollection/inverse/ContractVariation.hbm.xml" );
		SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory();

		try {
			doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( org.hibernate.test.immutable.entitywithmutablecollection.Contract.class ) );
		}
		finally {
			sf.close();
		}

	}

	@Test
	public void testAnotherBasicCollection() {
		Configuration cfg = new Configuration();
		cfg.addAnnotatedClass( Boy.class );
		cfg.addAnnotatedClass( Country.class );
		cfg.addAnnotatedClass( TestCourse.class );
		cfg.addAnnotatedClass( Matrix.class );
		SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory();

		try {
			doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( Boy.class ) );
		}
		finally {
			sf.close();
		}
	}

	private void doCompare(SessionFactoryImplementor sf, OuterJoinLoadable persister) {
		LoadPlanStructureAssertionHelper.INSTANCE.performBasicComparison( sf, persister );
	}
}
