/*
 * 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.backend.serialization;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.lucene.document.Document;
import org.easymock.EasyMock;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.backend.AddLuceneWork;
import org.hibernate.search.backend.LuceneWork;
import org.hibernate.search.engine.integration.impl.ExtendedSearchIntegrator;
import org.hibernate.search.indexes.serialization.spi.Deserializer;
import org.hibernate.search.indexes.serialization.spi.LuceneWorkSerializer;
import org.hibernate.search.indexes.serialization.spi.SerializationProvider;
import org.hibernate.search.indexes.serialization.spi.Serializer;
import org.hibernate.search.spi.IndexedTypeIdentifier;
import org.hibernate.search.spi.impl.PojoIndexedTypeIdentifier;
import org.hibernate.search.testsupport.TestForIssue;
import org.hibernate.search.testsupport.junit.SearchFactoryHolder;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;

/**
 * The implementations of SerializationProvider can choose (by SPI contract) to return
 * the same instance to multiple requestors as an optimisation, but this is correct to do only
 * if their implementation is threadsafe.
 * It's more common that implementors will opt to return different instances
 * when this might be a simpler strategy to satisfy the threadsafety requirement.
 *
 * For such cases, we need to verify that the various clients using such a Service will not
 * cache it internally in their own fields.
 *
 * This test verifies that an IndexManager won't reuse the instance multiple times; it won't
 * cover all configurable scenarios as there are many: threadsafety can't be guaranteed than
 * by careful design, so the point here is to spot the most obvious mistakes, to draw attention
 * on the problem in case of creative refactoring.
 *
 * @author Sanne Grinovero (C) 2014 Red Hat Inc.
 */
@TestForIssue(jiraKey = "HSEARCH-1637")
public class SerializationInstanceNotReusedTest {

	private static final IndexedTypeIdentifier testTypeId = new PojoIndexedTypeIdentifier( Book.class );

	private final CountingSerializationProvider countingServiceInstance = new CountingSerializationProvider();

	@Rule
	public SearchFactoryHolder factoryHolder = new SearchFactoryHolder( Book.class )
			.withService( SerializationProvider.class, countingServiceInstance );

	@Test
	public void testPropertiesIndexing() {
		ExtendedSearchIntegrator searchFactory = factoryHolder.getSearchFactory();
		LuceneWorkSerializer serializer = searchFactory.getServiceManager().requestService( LuceneWorkSerializer.class );

		Assert.assertEquals( 0, countingServiceInstance.serializerGetCount.get() );
		Assert.assertEquals( 0, countingServiceInstance.deserializerGetCount.get() );

		//Serialize some work:
		serializer.toSerializedModel( makeSomeWork() );
		Assert.assertEquals( 1, countingServiceInstance.serializerGetCount.get() );
		Assert.assertEquals( 0, countingServiceInstance.deserializerGetCount.get() );

		//Serialize again, note the point of the test is to request all references again:
		serializer.toSerializedModel( makeSomeWork() );
		Assert.assertEquals( 2, countingServiceInstance.serializerGetCount.get() );
		Assert.assertEquals( 0, countingServiceInstance.deserializerGetCount.get() );

		//Now Deserialize:
		serializer.toLuceneWorks( makeSomeSerializedWork() );
		Assert.assertEquals( 2, countingServiceInstance.serializerGetCount.get() );
		Assert.assertEquals( 1, countingServiceInstance.deserializerGetCount.get() );

		//Now Deserialize again:
		serializer.toLuceneWorks( makeSomeSerializedWork() );
		Assert.assertEquals( 2, countingServiceInstance.serializerGetCount.get() );
		Assert.assertEquals( 2, countingServiceInstance.deserializerGetCount.get() );

		searchFactory.getServiceManager().releaseService( LuceneWorkSerializer.class );
	}

	private byte[] makeSomeSerializedWork() {
		//Random: we won't deserialize this
		return new byte[]{ 0, 1, 2};
	}

	private List<LuceneWork> makeSomeWork() {
		List<LuceneWork> list = new LinkedList<>();
		//just some random data:
		list.add( new AddLuceneWork( Integer.valueOf( 5 ), "id:5", testTypeId, new Document() ) );
		list.add( new AddLuceneWork( Integer.valueOf( 6 ), "id:6", testTypeId, new Document() ) );
		return list;
	}

	@Indexed(index = "books")
	private static class Book {
		@DocumentId long id;
		@Field String title;
	}

	/**
	 * This SerializationProvider mocks Serializer and Deserializer, it won't actually do the work
	 * but it will keep count of how many times each service is being requested.
	 */
	private static class CountingSerializationProvider implements SerializationProvider {

		private final AtomicInteger serializerGetCount = new AtomicInteger();
		private final AtomicInteger deserializerGetCount = new AtomicInteger();
		private final Serializer mockSerializer = EasyMock.createNiceMock( Serializer.class );
		private final Deserializer mockDeserializer = EasyMock.createNiceMock( Deserializer.class );

		CountingSerializationProvider() {
			EasyMock.replay( mockSerializer, mockDeserializer );
		}

		@Override
		public Serializer getSerializer() {
			serializerGetCount.incrementAndGet();
			return mockSerializer;
		}

		@Override
		public Deserializer getDeserializer() {
			deserializerGetCount.incrementAndGet();
			return mockDeserializer;
		}
	}

}
