/*
 * Hibernate Search, full-text search for your domain model
 *
 * 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.search.test.engine.optimizations;

import java.lang.annotation.ElementType;

import org.hibernate.Transaction;

import org.hibernate.collection.internal.PersistentBag;
import org.hibernate.collection.internal.PersistentSet;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.cfg.EntityMapping;
import org.hibernate.search.cfg.IndexedMapping;
import org.hibernate.search.cfg.SearchMapping;
import org.hibernate.search.test.util.FullTextSessionBuilder;
import org.hibernate.search.testsupport.TestForIssue;
import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
 * Verify that updates to collections that are not indexed do not trigger indexing.
 * Updating a collection in an entity which is not indexed, but has some other property marked
 * as containedIn, should generally not trigger indexing of the containedIn objects, unless
 * we can't tell for sure the index state is not going to be affected.
 *
 * @author Sanne Grinovero
 * @author Tom Waterhouse
 */
@TestForIssue(jiraKey = "HSEARCH-679")
public class CollectionUpdateEventTest {

	private static final boolean WITH_CLASS_BRIDGE_ON_ITEM = true;
	private static final boolean WITHOUT_CLASS_BRIDGE_ON_ITEM = false;

	private static final boolean WITH_CLASS_BRIDGE_ON_CATALOG = true;
	private static final boolean WITHOUT_CLASS_BRIDGE_ON_CATALOG = false;

	/**
	 * If the top level class has a class bridge or dynamic boost, then we can't safely
	 * enable this optimization.
	 */
	@Test
	public void testWithClassBridge() {
		testScenario( WITH_CLASS_BRIDGE_ON_ITEM, 2, WITHOUT_CLASS_BRIDGE_ON_CATALOG );
	}

	/**
	 * The indexing should be skipped when no custom bridges are used.
	 */
	@Test
	public void testWithoutClassBridge() {
		testScenario( WITHOUT_CLASS_BRIDGE_ON_ITEM, 2, WITHOUT_CLASS_BRIDGE_ON_CATALOG );
	}

	/**
	 * Test having a depth which is not enough to reach the dirty collection.
	 * We should be able to optimize this case.
	 */
	@Test
	public void testWithNoEnoughDepth() {
		testScenario( WITH_CLASS_BRIDGE_ON_ITEM, 1, WITHOUT_CLASS_BRIDGE_ON_CATALOG );
	}

	/**
	 * Test again with a no-enough-depth scenario, but having the class bridge
	 * defined close to the collection (far from the root entity)
	 */
	@Test
	public void testWithDeepClassBridge() {
		testScenario( WITHOUT_CLASS_BRIDGE_ON_ITEM, 1, WITH_CLASS_BRIDGE_ON_CATALOG );
	}

	private void testScenario(boolean withClassBridgeOnItem, int depth, boolean withClassBridgeOnCatalog) {
		FullTextSessionBuilder fulltextSessionBuilder = configure(
				withClassBridgeOnItem,
				depth,
				withClassBridgeOnCatalog
		);
		try {
			initializeData( fulltextSessionBuilder );
			FullTextSession fullTextSession = fulltextSessionBuilder.openFullTextSession();
			try {
				Catalog catalog = (Catalog) fullTextSession.get( Catalog.class, 1L );
				PersistentSet catalogItems = (PersistentSet) catalog.getCatalogItems();
				PersistentBag consumers = (PersistentBag) catalog.getConsumers();

				assertFalse( "consumers should not be initialized", consumers.wasInitialized() );
				assertFalse( "catalogItems should not be initialized", consumers.wasInitialized() );

				updateCatalogsCollection( fullTextSession, catalog );

				if ( ( withClassBridgeOnItem || withClassBridgeOnCatalog ) && depth > 1 ) {
					assertTrue( "catalogItems should have been initialized", catalogItems.wasInitialized() );
				}
				else {
					assertFalse( "catalogItems should not be initialized", catalogItems.wasInitialized() );
				}
			}
			finally {
				fullTextSession.close();
			}
		}
		finally {
			fulltextSessionBuilder.close();
		}
	}

	private FullTextSessionBuilder configure(boolean withClassBridgeOnItem, int depth, boolean withClassBridgeOnCatalog) {
		FullTextSessionBuilder builder = new FullTextSessionBuilder()
				.addAnnotatedClass( Catalog.class )
				.addAnnotatedClass( CatalogItem.class )
				.addAnnotatedClass( Consumer.class )
				.addAnnotatedClass( Item.class );
		SearchMapping fluentMapping = builder.fluentMapping();
		// mapping for Catalog
		EntityMapping catalogMapping = fluentMapping
				.entity( Catalog.class );
		if ( withClassBridgeOnCatalog ) {
			catalogMapping.classBridge( NoopClassBridge.class );
		}
		catalogMapping
				.property( "catalogItems", ElementType.FIELD ).containedIn();

		// mapping for CatalogItem
		fluentMapping.entity( CatalogItem.class )
				.property( "item", ElementType.FIELD ).containedIn()
				.property( "catalog", ElementType.FIELD ).indexEmbedded();

		// mapping for Item
		IndexedMapping itemMapping = fluentMapping
				.entity( Item.class )
				.indexed();
		if ( withClassBridgeOnItem ) {
			itemMapping.classBridge( NoopClassBridge.class );
		}
		itemMapping.property( "catalogItems", ElementType.FIELD ).indexEmbedded().depth( depth );
		return builder.build();
	}

	private void initializeData(FullTextSessionBuilder fulltextSessionBuilder) {
		FullTextSession fullTextSession = fulltextSessionBuilder.openFullTextSession();
		try {
			final Transaction transaction = fullTextSession.beginTransaction();

			Catalog catalog = new Catalog();
			catalog.setCatalogId( 1L );
			catalog.setName( "parts" );
			fullTextSession.persist( catalog );

			for ( int i = 0; i < 5; i++ ) {
				Item item = new Item();
				item.setName( "battery" );
				fullTextSession.persist( item );

				CatalogItem catalogItem = new CatalogItem();
				catalogItem.setCatalog( catalog );
				catalogItem.setItem( item );
				fullTextSession.persist( catalogItem );

				item.getCatalogItems().add( catalogItem );
				fullTextSession.merge( item );

				catalog.getCatalogItems().add( catalogItem );
				fullTextSession.merge( catalog );
			}

			transaction.commit();
		}
		finally {
			fullTextSession.close();
		}
	}

	/**
	 * Update a non-indexed collection of an entity contained in a collection. No indexing work should be created.
	 */
	private void updateCatalogsCollection(FullTextSession fullTextSession, Catalog catalog) {
		final Transaction transaction = fullTextSession.beginTransaction();

		Consumer consumer = new Consumer();
		consumer.setName( "consumer" );
		consumer.getCatalogs().add( catalog );
		fullTextSession.persist( consumer );

		catalog.getConsumers().add( consumer );
		fullTextSession.merge( catalog );

		transaction.commit();
	}

}
