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

import java.util.concurrent.atomic.AtomicReference;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.DelegatingAnalyzerWrapper;
import org.hibernate.search.analyzer.impl.ScopedLuceneAnalyzer;
import org.hibernate.search.exception.AssertionFailure;

/**
 * An Analyzer implementation which can dynamically be reconfigured. We use this as temporary solution to workaround
 * LUCENE-6212, until a better solution is found.
 *
 * @author Sanne Grinovero (C) 2015 Red Hat Inc.
 */
public final class ConcurrentlyMutableAnalyzer extends DelegatingAnalyzerWrapper {

	private final AtomicReference<ScopedLuceneAnalyzer> current = new AtomicReference<>();

	public ConcurrentlyMutableAnalyzer(Analyzer initialAnalyzer) {
		super( new ResettableReuseStrategy() );
		if ( initialAnalyzer instanceof ScopedLuceneAnalyzer ) {
			current.set( (ScopedLuceneAnalyzer) initialAnalyzer );
		}
		else {
			current.set( new ScopedLuceneAnalyzer( initialAnalyzer ) );
		}
	}

	@Override
	protected Analyzer getWrappedAnalyzer(String fieldName) {
		return current.get();
	}

	/**
	 * Checks if the currently set Analyzer is the same as the
	 * proposed one, in which case there is no need for
	 * replacements or locking.
	 * Correct concurrency control requires external locking!
	 * @param analyzer the {@link ScopedLuceneAnalyzer} to use for comparison
	 * @return true if there is no need to replace the current Analyzer
	 */
	public boolean isCompatibleWith(ScopedLuceneAnalyzer analyzer) {
		ScopedLuceneAnalyzer currentAnalyzer = current.get();
		return currentAnalyzer.isCompositeOfSameInstances( analyzer );
	}

	/**
	 * Correct concurrency control requires external locking!
	 * @param analyzer the {@link ScopedLuceneAnalyzer} to use for locking
	 */
	public void updateAnalyzer(ScopedLuceneAnalyzer analyzer) {
		current.set( analyzer );
	}

	private static final class ResettableReuseStrategy extends ReuseStrategy {

		@Override
		public TokenStreamComponents getReusableComponents(Analyzer analyzer, String fieldName) {
			if ( analyzer instanceof ConcurrentlyMutableAnalyzer ) {
				ConcurrentlyMutableAnalyzer cma = (ConcurrentlyMutableAnalyzer) analyzer;
				final Analyzer wrappedAnalyzer = cma.getWrappedAnalyzer( fieldName );
				return wrappedAnalyzer.getReuseStrategy().getReusableComponents( wrappedAnalyzer, fieldName );
			}
			else {
				throw new AssertionFailure( "This ReuseStrategy should only be applied to a ConcurrentlyMutableAnalyzer " );
			}
		}

		@Override
		public void setReusableComponents(Analyzer analyzer, String fieldName, TokenStreamComponents components) {
			if ( analyzer instanceof ConcurrentlyMutableAnalyzer ) {
				ConcurrentlyMutableAnalyzer cma = (ConcurrentlyMutableAnalyzer) analyzer;
				final Analyzer wrappedAnalyzer = cma.getWrappedAnalyzer( fieldName );
				wrappedAnalyzer.getReuseStrategy().setReusableComponents( analyzer, fieldName, components );
			}
			else {
				throw new AssertionFailure( "This ReuseStrategy should only be applied to a ConcurrentlyMutableAnalyzer " );
			}
		}
	};

}
