/*
 * 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.query.dsl.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Query;

import org.hibernate.search.annotations.Store;
import org.hibernate.search.engine.integration.impl.ExtendedSearchIntegrator;
import org.hibernate.search.engine.metadata.impl.DocumentFieldMetadata;
import org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity;
import org.hibernate.search.query.dsl.MoreLikeThisTermination;
import org.hibernate.search.query.dsl.MoreLikeThisToEntityContentAndTermination;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;

/**
 * @author Emmanuel Bernard
 */
public abstract class ConnectedMoreLikeThisQueryBuilder {

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

	private final QueryBuildingContext queryContext;
	private final QueryCustomizer queryCustomizer;
	private final FieldsContext fieldsContext;
	private final INPUT_TYPE inputType;
	private final Object input;
	private final MoreLikeThisQueryContext moreLikeThisContext;

	public ConnectedMoreLikeThisQueryBuilder(Object id,
											INPUT_TYPE inputType,
											FieldsContext fieldsContext,
											MoreLikeThisQueryContext moreLikeThisContext,
											QueryCustomizer queryCustomizer,
											QueryBuildingContext queryContext) {
		this.queryContext = queryContext;
		this.queryCustomizer = queryCustomizer;
		this.moreLikeThisContext = moreLikeThisContext;
		this.fieldsContext = fieldsContext;
		this.inputType = inputType;
		this.input = id;
	}

	/**
	 * We could encapsulate that into a MoreLikeThisContentObject but going for this approach for now
	 * to save memory pressure.
	 * If the code becomes too nasty, change it.
	 */
	public enum INPUT_TYPE {
		ID,
		ENTITY,
		READER,
		STRING
	}

	public Query createQuery() {
		Query query;
		final ExtendedSearchIntegrator searchIntegrator = queryContext.getExtendedSearchIntegrator();
		final DocumentBuilderIndexedEntity documentBuilder = queryContext.getDocumentBuilder();
		IndexReader indexReader = searchIntegrator.getIndexReaderAccessor().open( queryContext.getEntityType().asTypeSet() );
		// retrieving the docId and building the more like this query form the term vectors must be using the same index reader
		try {
			String[] fieldNames = getAllCompatibleFieldNames( documentBuilder );
			if ( fieldsContext.size() == 0 ) {
				// Use all compatible fields when comparingAllFields is used
				fieldsContext.addAll( fieldNames );
			}
			query = new MoreLikeThisBuilder( documentBuilder, searchIntegrator )
					.compatibleFieldNames( fieldNames )
					.fieldsContext( fieldsContext )
					.queryContext( queryContext )
					.indexReader( indexReader )
					.inputType( inputType )
					.input( input )
					.otherMoreLikeThisContext( moreLikeThisContext )
					.createQuery();
		}
		finally {
			searchIntegrator.getIndexReaderAccessor().close( indexReader );
		}
		//TODO implement INPUT_TYPE.READER
		//TODO implement INPUT_TYPE.STRING
		return queryCustomizer.setWrappedQuery( query ).createQuery();
	}

	private String[] getAllCompatibleFieldNames(DocumentBuilderIndexedEntity documentBuilder) {
		Collection<DocumentFieldMetadata> allFieldMetadata = documentBuilder.getTypeMetadata().getAllDocumentFieldMetadata();
		List<String> fieldNames = new ArrayList<String>( allFieldMetadata.size() );
		for ( DocumentFieldMetadata fieldMetadata : allFieldMetadata ) {
			boolean hasTermVector = fieldMetadata.getTermVector() != Field.TermVector.NO;
			boolean isStored = fieldMetadata.getStore() != Store.NO;
			boolean isIdOrEmbeddedId = fieldMetadata.isId() || fieldMetadata.isIdInEmbedded();
			if ( ( hasTermVector || isStored ) && !isIdOrEmbeddedId ) {
				fieldNames.add( fieldMetadata.getAbsoluteName() );
			}
		}
		if ( fieldNames.size() == 0 ) {
			throw log.noFieldCompatibleForMoreLikeThis( documentBuilder.getTypeIdentifier() );
		}
		return fieldNames.toArray( new String[fieldNames.size()] );
	}

	public static final class MoreLikeThisTerminationImpl extends ConnectedMoreLikeThisQueryBuilder implements MoreLikeThisTermination {

		public MoreLikeThisTerminationImpl(Object id, INPUT_TYPE inputType, FieldsContext fieldsContext, MoreLikeThisQueryContext moreLikeThisContext, QueryCustomizer queryCustomizer, QueryBuildingContext queryContext) {
			super( id, inputType, fieldsContext, moreLikeThisContext, queryCustomizer, queryContext );
		}
	}

	public static final class MoreLikeThisToEntityContentAndTerminationImpl extends ConnectedMoreLikeThisQueryBuilder implements MoreLikeThisToEntityContentAndTermination {

		public MoreLikeThisToEntityContentAndTerminationImpl(Object id, INPUT_TYPE inputType, FieldsContext fieldsContext, MoreLikeThisQueryContext moreLikeThisContext, QueryCustomizer queryCustomizer, QueryBuildingContext queryContext) {
			super( id, inputType, fieldsContext, moreLikeThisContext, queryCustomizer, queryContext );
		}
	}
}
