/*
 * Hibernate OGM, Domain model persistence for NoSQL datastores
 *
 * 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.ogm.backendtck.batchfetching;

import static org.fest.assertions.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.List;
import java.util.Map;

import org.fest.assertions.Assertions;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.ogm.cfg.OgmProperties;
import org.hibernate.ogm.dialect.impl.GridDialects;
import org.hibernate.ogm.dialect.multiget.spi.MultigetGridDialect;
import org.hibernate.ogm.dialect.spi.GridDialect;
import org.hibernate.ogm.utils.InvokedOperationsLoggingDialect;
import org.hibernate.ogm.utils.OgmTestCase;
import org.hibernate.ogm.utils.TestForIssue;
import org.hibernate.ogm.utils.TestHelper;
import org.hibernate.stat.Statistics;
import org.junit.Test;

/**
 * @author Emmanuel Bernard emmanuel@hibernate.org
 */
public class BatchFetchingTest extends OgmTestCase {
	@Override
	protected Class<?>[] getAnnotatedClasses() {
		return new Class<?>[] { Tower.class, Floor.class, CondominiumBuilding.class, Condominium.class };
	}

	@Test
	public void testLoadSeveralFloorsByBatch() throws Exception {
		Session session = openSession();
		Tower tower = prepareTower( session );
		session.clear();

		session.beginTransaction();
		for ( Floor currentFloor : tower.getFloors() ) {
			// load proxies
			assertFalse( Hibernate.isInitialized( session.load( Floor.class, currentFloor.getId() ) ) );
		}
		Statistics statistics = session.getSessionFactory().getStatistics();
		statistics.setStatisticsEnabled( true );
		statistics.clear();

		assertEquals( 0, statistics.getEntityStatistics( Floor.class.getName() ).getFetchCount() );
		getOperationsLogger().reset();
		for ( Floor currentFloor : tower.getFloors() ) {
			// load proxies
			Object entity = session.load( Floor.class, currentFloor.getId() );
			Hibernate.initialize( entity );
			assertTrue( Hibernate.isInitialized( entity ) );
		}

		// if a multiget, we load both entities as one go, otherwise we don't
		int fetchSize = isMultigetDialect() ? 1 : 2;
		assertEquals( fetchSize, statistics.getEntityStatistics( Floor.class.getName() ).getFetchCount() );

		if ( isMultigetDialect() ) {
			assertThat( getOperations() ).containsExactly(
					"getTuples"
			);
		}
		else {
			assertThat( getOperations() ).containsExactly(
					"getTuple",
					"getTuple"
			);
		}

		session.getTransaction().commit();

		cleanTower( session, tower );
		session.close();
	}

	@Test
	public void testLoadSeveralFloorsFromTower() throws Exception {

		Session session = openSession();
		Tower tower = prepareTower( session );
		session.clear();

		// now read the tower and its floors to detect 1+n patterns;
		session.beginTransaction();
		tower = session.get( Tower.class, tower.getId() );

		Statistics statistics = session.getSessionFactory().getStatistics();
		statistics.setStatisticsEnabled( true );
		statistics.clear();
		assertEquals( 0, statistics.getEntityStatistics( Floor.class.getName() ).getFetchCount() );
		getOperationsLogger().reset();
		Assertions.assertThat( tower.getFloors() ).hasSize( 2 );

		// if a multiget, we load both entities as one go, otherwise we don't
		int fetchSize = isMultigetDialect() ? 1 : 2;
		assertEquals( fetchSize, statistics.getEntityStatistics( Floor.class.getName() ).getFetchCount() );
		session.getTransaction().commit();

		if ( isMultigetDialect() ) {
			assertThat( getOperations() ).containsExactly(
					"getAssociation",
					"getTuples"
			);
		}
		else {
			assertThat( getOperations() ).containsExactly(
					"getAssociation",
					"getTuple",
					"getTuple"
			);
		}
		cleanTower( session, tower );
		session.close();
	}

	@Test
	@TestForIssue(jiraKey = "OGM-945")
	public void testMultigetIsAppliedWithoutExplicitBatchSizeGiven() throws Exception {
		Session session = openSession();
		prepareCondoBuilding( session );
		session.clear();

		// now read the condo building and its appartments to detect 1+n patterns;
		session.beginTransaction();
		CondominiumBuilding condoBuilding = session.get( CondominiumBuilding.class, "cb-1" );

		Statistics statistics = session.getSessionFactory().getStatistics();
		statistics.setStatisticsEnabled( true );
		statistics.clear();
		assertEquals( 0, statistics.getEntityStatistics( Condominium.class.getName() ).getFetchCount() );
		getOperationsLogger().reset();
		Assertions.assertThat( condoBuilding.getCondominiums() ).hasSize( 3 );

		// if a multiget, we load all entities as one go, otherwise we don't
		int fetchSize = isMultigetDialect() ? 1 : 3;
		assertEquals( fetchSize, statistics.getEntityStatistics( Condominium.class.getName() ).getFetchCount() );
		session.getTransaction().commit();

		if ( isMultigetDialect() ) {
			assertThat( getOperations() ).containsExactly(
					"getAssociation",
					"getTuples"
			);
		}
		else {
			assertThat( getOperations() ).containsExactly(
					"getAssociation",
					"getTuple",
					"getTuple",
					"getTuple"
			);
		}
		cleanCondoBuilding( session );
		session.close();
	}

	private void cleanTower(Session session, Tower tower) {
		session.beginTransaction();
		session.delete( session.get( Tower.class, tower.getId() ) );
		for ( Floor currentFloor : tower.getFloors() ) {
			session.delete( session.get( Floor.class, currentFloor.getId() ) );
		}

		session.delete( new CondominiumBuilding( "cb-1" ) );

		session.getTransaction().commit();
	}

	private Tower prepareTower(Session session) {
		session.beginTransaction();
		Tower tower = new Tower();
		tower.setName( "Pise" );

		Floor floor = new Floor();
		floor.setLevel( 0 );
		tower.getFloors().add( floor );

		floor = new Floor();
		floor.setLevel( 1 );
		tower.getFloors().add( floor );

		session.persist( tower );
		session.getTransaction().commit();

		return tower;
	}

	private void cleanCondoBuilding(Session session) {
		session.beginTransaction();
		session.delete( session.get( CondominiumBuilding.class, "cb-1" ) );
		session.getTransaction().commit();
	}

	private CondominiumBuilding prepareCondoBuilding(Session session) {
		session.beginTransaction();

		CondominiumBuilding condoBuilding = new CondominiumBuilding();
		condoBuilding.setId( "cb-1" );

		condoBuilding.getCondominiums().add( new Condominium( "condo-1", 110 ) );
		condoBuilding.getCondominiums().add( new Condominium( "condo-2", 90 ) );
		condoBuilding.getCondominiums().add( new Condominium( "condo-3", 135 ) );
		session.persist( condoBuilding );

		session.getTransaction().commit();

		return condoBuilding;
	}

	private boolean isMultigetDialect() {
		GridDialect gridDialect = getSessionFactory().getServiceRegistry().getService( GridDialect.class );
		return GridDialects.hasFacet( gridDialect, MultigetGridDialect.class );
	}

	@Override
	protected void configure(Map<String, Object> cfg) {
		cfg.put( OgmProperties.GRID_DIALECT, InvokedOperationsLoggingDialect.class );
		TestHelper.enableCountersForInfinispan( cfg );
	}

	private InvokedOperationsLoggingDialect getOperationsLogger() {
		GridDialect gridDialect = getSessionFactory().getServiceRegistry().getService( GridDialect.class );
		InvokedOperationsLoggingDialect invocationLogger = GridDialects.getDelegateOrNull(
				gridDialect,
				InvokedOperationsLoggingDialect.class
		);

		return invocationLogger;
	}

	private List<String> getOperations() {
		return getOperationsLogger().getOperations();
	}
}
