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

import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;

import org.hibernate.search.backend.IndexingMonitor;
import org.hibernate.search.backend.LuceneWork;
import org.hibernate.search.exception.impl.ErrorContextBuilder;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;

/**
 * Apply the operations to Lucene directories.
 *
 * @author Emmanuel Bernard
 * @author Hardy Ferentschik
 * @author John Griffin
 * @author Sanne Grinovero
 */
final class LuceneBackendQueueTask implements Runnable {

	private static final Log log = LoggerFactory.make();

	private final Lock modificationLock;
	private final LuceneBackendResources resources;
	private final Iterable<LuceneWork> workList;
	private final IndexingMonitor monitor;

	LuceneBackendQueueTask(Iterable<LuceneWork> workList, LuceneBackendResources resources, IndexingMonitor monitor) {
		this.workList = workList;
		this.resources = resources;
		this.monitor = monitor;
		this.modificationLock = resources.getParallelModificationLock();
	}

	@Override
	public void run() {
		modificationLock.lock();
		try {
			applyUpdates();
		}
		catch (InterruptedException e) {
			log.interruptedWhileWaitingForIndexActivity( e );
			Thread.currentThread().interrupt();
			handleException( e );
		}
		catch (Exception e) {
			log.backendError( e );
			handleException( e );
		}
		finally {
			modificationLock.unlock();
		}
	}

	private void handleException(Exception e) {
		ErrorContextBuilder builder = new ErrorContextBuilder();
		builder.allWorkToBeDone( workList );
		builder.errorThatOccurred( e ).indexManager( resources.getIndexManager() );
		resources.getErrorHandler().handle( builder.createErrorContext() );
	}

	/**
	 * Applies all modifications to the index in parallel using the workers executor
	 * @throws ExecutionException
	 * @throws InterruptedException
	 */
	private void applyUpdates() throws InterruptedException, ExecutionException {
		AbstractWorkspaceImpl workspace = resources.getWorkspace();

		ErrorContextBuilder errorContextBuilder = new ErrorContextBuilder();
		errorContextBuilder.indexManager( resources.getIndexManager() );
		errorContextBuilder.allWorkToBeDone( workList );

		IndexWriterDelegate delegate = workspace.getIndexWriterDelegate( errorContextBuilder );
		if ( delegate == null ) {
			log.cannotOpenIndexWriterCausePreviousError();
			return;
		}

		boolean someFailureHappened = true;
		LuceneWork currentOperation = null; // to nicely report errors
		try {
			for ( LuceneWork luceneWork : workList ) {
				currentOperation = luceneWork;
				performWork( luceneWork, resources, delegate, monitor );
				errorContextBuilder.workCompleted( currentOperation );
			}
			currentOperation = null;
			workspace.optimizerPhase();
			someFailureHappened = false;
		}
		catch (RuntimeException re) {
			errorContextBuilder.errorThatOccurred( re ).indexManager( resources.getIndexManager() );
			if ( currentOperation != null ) {
				errorContextBuilder.addWorkThatFailed( currentOperation );
			}
			resources.getErrorHandler().handle( errorContextBuilder.createErrorContext() );
		}
		finally {
			workspace.afterTransactionApplied( someFailureHappened, false );
		}
	}

	static void performWork(final LuceneWork work, final LuceneBackendResources resources, final IndexWriterDelegate delegate, final IndexingMonitor monitor) {
		work.acceptIndexWorkVisitor( resources.getWorkVisitor(), null ).performWork( work, delegate, monitor );
	}

}
