/*
 * 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.userguide.batch;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

import org.hibernate.CacheMode;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.hibernate.Transaction;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.resource.transaction.spi.TransactionStatus;
import org.hibernate.userguide.model.Call;
import org.hibernate.userguide.model.Partner;
import org.hibernate.userguide.model.Person;
import org.hibernate.userguide.model.Phone;

import org.junit.Test;

import org.jboss.logging.Logger;

import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;

/**
 * @author Vlad Mihalcea
 */
public class BatchTest extends BaseEntityManagerFunctionalTestCase {

	@Override
	protected Class<?>[] getAnnotatedClasses() {
		return new Class<?>[] {
			Person.class,
			Phone.class,
			Call.class,
			Partner.class
		};
	}

	@Test
	public void testScroll() {
		withScroll();
	}

	@Test
	public void testStatelessSession() {
		withStatelessSession();
	}

	@Test
	public void testBulk() {
		doInJPA( this::entityManagerFactory, entityManager -> {
			entityManager.persist( new Person( "Vlad" ) );
			entityManager.persist( new Person( "Mihalcea" ) );
		} );
		doInJPA( this::entityManagerFactory, entityManager -> {
			String oldName = "Vlad";
			String newName = "Alexandru";
			//tag::batch-session-jdbc-batch-size-example[]
			entityManager
				.unwrap( Session.class )
				.setJdbcBatchSize( 10 );
			//end::batch-session-jdbc-batch-size-example[]
		} );
		doInJPA( this::entityManagerFactory, entityManager -> {
			String oldName = "Vlad";
			String newName = "Alexandru";
			//tag::batch-bulk-jpql-update-example[]
			int updatedEntities = entityManager.createQuery(
				"update Person p " +
				"set p.name = :newName " +
				"where p.name = :oldName" )
			.setParameter( "oldName", oldName )
			.setParameter( "newName", newName )
			.executeUpdate();
			//end::batch-bulk-jpql-update-example[]
			assertEquals(1, updatedEntities);
		} );

		doInJPA( this::entityManagerFactory, entityManager -> {
			String oldName = "Alexandru";
			String newName = "Vlad";

			Session session = entityManager.unwrap( Session.class );
			//tag::batch-bulk-hql-update-example[]
			int updatedEntities = session.createQuery(
				"update Person " +
				"set name = :newName " +
				"where name = :oldName" )
			.setParameter( "oldName", oldName )
			.setParameter( "newName", newName )
			.executeUpdate();
			//end::batch-bulk-hql-update-example[]
			assertEquals(1, updatedEntities);
		} );

		doInJPA( this::entityManagerFactory, entityManager -> {
			String oldName = "Vlad";
			String newName = "Alexandru";

			Session session = entityManager.unwrap( Session.class );
			//tag::batch-bulk-hql-update-version-example[]
			int updatedEntities = session.createQuery(
				"update versioned Person " +
				"set name = :newName " +
				"where name = :oldName" )
			.setParameter( "oldName", oldName )
			.setParameter( "newName", newName )
			.executeUpdate();
			//end::batch-bulk-hql-update-version-example[]
			assertEquals(1, updatedEntities);
		} );

		doInJPA( this::entityManagerFactory, entityManager -> {
			String name = "Alexandru";

			//tag::batch-bulk-jpql-delete-example[]
			int deletedEntities = entityManager.createQuery(
				"delete Person p " +
				"where p.name = :name" )
			.setParameter( "name", name )
			.executeUpdate();
			//end::batch-bulk-jpql-delete-example[]
			assertEquals(1, deletedEntities);
		} );

		doInJPA( this::entityManagerFactory, entityManager -> {

			Session session = entityManager.unwrap( Session.class );
			//tag::batch-bulk-hql-insert-example[]
			int insertedEntities = session.createQuery(
				"insert into Partner (id, name) " +
				"select p.id, p.name " +
				"from Person p ")
			.executeUpdate();
			//end::batch-bulk-hql-insert-example[]
			assertEquals(1, insertedEntities);
		} );

		doInJPA( this::entityManagerFactory, entityManager -> {
			String name = "Mihalcea";

			Session session = entityManager.unwrap( Session.class );
			//tag::batch-bulk-hql-delete-example[]
			int deletedEntities = session.createQuery(
				"delete Person " +
				"where name = :name" )
			.setParameter( "name", name )
			.executeUpdate();
			//end::batch-bulk-hql-delete-example[]
			assertEquals(1, deletedEntities);
		} );
	}

	private void withoutBatch() {
		//tag::batch-session-batch-example[]
		EntityManager entityManager = null;
		EntityTransaction txn = null;
		try {
			entityManager = entityManagerFactory().createEntityManager();

			txn = entityManager.getTransaction();
			txn.begin();

			for ( int i = 0; i < 100_000; i++ ) {
				Person Person = new Person( String.format( "Person %d", i ) );
				entityManager.persist( Person );
			}

			txn.commit();
		} catch (RuntimeException e) {
			if ( txn != null && txn.isActive()) txn.rollback();
				throw e;
		} finally {
			if (entityManager != null) {
				entityManager.close();
			}
		}
		//end::batch-session-batch-example[]
	}

	private void withBatch() {
		int entityCount = 100;
		//tag::batch-session-batch-insert-example[]
		EntityManager entityManager = null;
		EntityTransaction txn = null;
		try {
			entityManager = entityManagerFactory().createEntityManager();

			txn = entityManager.getTransaction();
			txn.begin();

			int batchSize = 25;

			for ( int i = 0; i < entityCount; i++ ) {
				if ( i > 0 && i % batchSize == 0 ) {
					//flush a batch of inserts and release memory
					entityManager.flush();
					entityManager.clear();
				}

				Person Person = new Person( String.format( "Person %d", i ) );
				entityManager.persist( Person );
			}

			txn.commit();
		} catch (RuntimeException e) {
			if ( txn != null && txn.isActive()) txn.rollback();
				throw e;
		} finally {
			if (entityManager != null) {
				entityManager.close();
			}
		}
		//end::batch-session-batch-insert-example[]
	}

	private void withScroll() {
		withBatch();

		//tag::batch-session-scroll-example[]
		EntityManager entityManager = null;
		EntityTransaction txn = null;
		ScrollableResults scrollableResults = null;
		try {
			entityManager = entityManagerFactory().createEntityManager();

			txn = entityManager.getTransaction();
			txn.begin();

			int batchSize = 25;

			Session session = entityManager.unwrap( Session.class );

			scrollableResults = session
				.createQuery( "select p from Person p" )
				.setCacheMode( CacheMode.IGNORE )
				.scroll( ScrollMode.FORWARD_ONLY );

			int count = 0;
			while ( scrollableResults.next() ) {
				Person Person = (Person) scrollableResults.get( 0 );
				processPerson(Person);
				if ( ++count % batchSize == 0 ) {
					//flush a batch of updates and release memory:
					entityManager.flush();
					entityManager.clear();
				}
			}

			txn.commit();
		} catch (RuntimeException e) {
			if ( txn != null && txn.isActive()) txn.rollback();
				throw e;
		} finally {
			if (scrollableResults != null) {
				scrollableResults.close();
			}
			if (entityManager != null) {
				entityManager.close();
			}
		}
		//end::batch-session-scroll-example[]
	}

	private void withStatelessSession() {
		withBatch();

		//tag::batch-stateless-session-example[]
		StatelessSession statelessSession = null;
		Transaction txn = null;
		ScrollableResults scrollableResults = null;
		try {
			SessionFactory sessionFactory = entityManagerFactory().unwrap( SessionFactory.class );
			statelessSession = sessionFactory.openStatelessSession();

			txn = statelessSession.getTransaction();
			txn.begin();

			scrollableResults = statelessSession
				.createQuery( "select p from Person p" )
				.scroll(ScrollMode.FORWARD_ONLY);

			while ( scrollableResults.next() ) {
				Person Person = (Person) scrollableResults.get( 0 );
				processPerson(Person);
				statelessSession.update( Person );
			}

			txn.commit();
		} catch (RuntimeException e) {
			if ( txn != null && txn.getStatus() == TransactionStatus.ACTIVE) txn.rollback();
				throw e;
		} finally {
			if (scrollableResults != null) {
				scrollableResults.close();
			}
			if (statelessSession != null) {
				statelessSession.close();
			}
		}
		//end::batch-stateless-session-example[]
	}

	private void processPerson(Person Person) {
		if ( Person.getId() % 1000 == 0 ) {
			log.infof( "Processing [%s]", Person.getName());
		}
	}

}
