/*
 * 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.envers.test.integration.query;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager;

import org.hibernate.envers.RevisionType;
import org.hibernate.envers.enhanced.SequenceIdRevisionEntity;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.query.criteria.AuditDisjunction;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.entities.StrIntTestEntity;

import org.hibernate.testing.TestForIssue;
import org.junit.Assert;
import org.junit.Test;

/**
 * @author Adam Warski (adam at warski dot org)
 * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
 */
@SuppressWarnings({"unchecked"})
public class MaximalizePropertyQuery extends BaseEnversJPAFunctionalTestCase {
	Integer id1;
	Integer id2;
	Integer id3;
	Integer id4;

	@Override
	protected Class<?>[] getAnnotatedClasses() {
		return new Class[] {StrIntTestEntity.class};
	}

	@Test
	@Priority(10)
	public void initData() {
		// Revision 1
		EntityManager em = getEntityManager();
		em.getTransaction().begin();

		StrIntTestEntity site1 = new StrIntTestEntity( "a", 10 );
		StrIntTestEntity site2 = new StrIntTestEntity( "b", 15 );
		StrIntTestEntity site3 = new StrIntTestEntity( "c", 42 );
		StrIntTestEntity site4 = new StrIntTestEntity( "d", 52 );

		em.persist( site1 );
		em.persist( site2 );
		em.persist( site3 );
		em.persist( site4 );

		id1 = site1.getId();
		id2 = site2.getId();
		id3 = site3.getId();
		id4 = site4.getId();

		em.getTransaction().commit();

		// Revision 2
		em.getTransaction().begin();

		site1 = em.find( StrIntTestEntity.class, id1 );
		site2 = em.find( StrIntTestEntity.class, id2 );

		site1.setStr1( "d" );
		site2.setNumber( 20 );

		em.getTransaction().commit();

		// Revision 3
		em.getTransaction().begin();

		site1 = em.find( StrIntTestEntity.class, id1 );
		site2 = em.find( StrIntTestEntity.class, id2 );

		site1.setNumber( 30 );
		site2.setStr1( "z" );

		em.getTransaction().commit();

		// Revision 4
		em.getTransaction().begin();

		site1 = em.find( StrIntTestEntity.class, id1 );
		site2 = em.find( StrIntTestEntity.class, id2 );

		site1.setNumber( 5 );
		site2.setStr1( "a" );

		em.getTransaction().commit();

		// Revision 5
		em.getTransaction().begin();
		site4 = em.find( StrIntTestEntity.class, id4 );
		em.remove( site4 );
		em.getTransaction().commit();
	}

	@Test
	public void testMaximizeWithIdEq() {
		List revs_id1 = getAuditReader().createQuery()
				.forRevisionsOfEntity( StrIntTestEntity.class, false, true )
				.addProjection( AuditEntity.revisionNumber() )
				.add(
						AuditEntity.property( "number" ).maximize()
								.add( AuditEntity.id().eq( id2 ) )
				)
				.getResultList();

		assert Arrays.asList( 2, 3, 4 ).equals( revs_id1 );
	}

	@Test
	public void testMinimizeWithPropertyEq() {
		List result = getAuditReader().createQuery()
				.forRevisionsOfEntity( StrIntTestEntity.class, false, true )
				.addProjection( AuditEntity.revisionNumber() )
				.add(
						AuditEntity.property( "number" ).minimize()
								.add( AuditEntity.property( "str1" ).eq( "a" ) )
				)
				.getResultList();

		assert Arrays.asList( 1 ).equals( result );
	}

	@Test
	public void testMaximizeRevision() {
		List result = getAuditReader().createQuery()
				.forRevisionsOfEntity( StrIntTestEntity.class, false, true )
				.addProjection( AuditEntity.revisionNumber() )
				.add(
						AuditEntity.revisionNumber().maximize()
								.add( AuditEntity.property( "number" ).eq( 10 ) )
				)
				.getResultList();

		assert Arrays.asList( 2 ).equals( result );
	}

	@Test
	@TestForIssue(jiraKey = "HHH-7800")
	public void testMaximizeInDisjunction() {
		List<Integer> idsToQuery = Arrays.asList( id1, id3 );

		AuditDisjunction disjunction = AuditEntity.disjunction();

		for ( Integer id : idsToQuery ) {
			disjunction.add( AuditEntity.revisionNumber().maximize().add( AuditEntity.id().eq( id ) ) );
		}
		List result = getAuditReader().createQuery()
				.forRevisionsOfEntity( StrIntTestEntity.class, true, true )
				.add( disjunction )
				.getResultList();

		Set<Integer> idsSeen = new HashSet<Integer>();
		for ( Object o : result ) {
			StrIntTestEntity entity = (StrIntTestEntity) o;
			Integer id = entity.getId();
			Assert.assertTrue( "Entity with ID " + id + " returned but not queried for.", idsToQuery.contains( id ) );
			if ( !idsSeen.add( id ) ) {
				Assert.fail( "Multiple revisions returned with ID " + id + "; expected only one." );
			}
		}
	}

	@Test
	@TestForIssue(jiraKey = "HHH-7827")
	public void testAllLatestRevisionsOfEntityType() {
		List result = getAuditReader().createQuery()
				.forRevisionsOfEntity( StrIntTestEntity.class, false, true )
				.add( AuditEntity.revisionNumber().maximize().computeAggregationInInstanceContext() )
				.addOrder( AuditEntity.property( "id" ).asc() )
				.getResultList();

		Assert.assertEquals( 4, result.size() );

		Object[] result1 = (Object[]) result.get( 0 );
		Object[] result2 = (Object[]) result.get( 1 );
		Object[] result3 = (Object[]) result.get( 2 );
		Object[] result4 = (Object[]) result.get( 3 );

		checkRevisionData( result1, 4, RevisionType.MOD, new StrIntTestEntity( "d", 5, id1 ) );
		checkRevisionData( result2, 4, RevisionType.MOD, new StrIntTestEntity( "a", 20, id2 ) );
		checkRevisionData( result3, 1, RevisionType.ADD, new StrIntTestEntity( "c", 42, id3 ) );
		checkRevisionData( result4, 5, RevisionType.DEL, new StrIntTestEntity( null, null, id4 ) );
	}

	private void checkRevisionData(Object[] result, int revision, RevisionType type, StrIntTestEntity entity) {
		Assert.assertEquals( entity, result[0] );
		Assert.assertEquals( revision, ((SequenceIdRevisionEntity) result[1]).getId() );
		Assert.assertEquals( type, result[2] );
	}
}