/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat, Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */

package org.hibernate.search.query.dsl.impl;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;

import org.hibernate.annotations.common.AssertionFailure;
import org.hibernate.search.SearchException;
import org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity;
import org.hibernate.search.engine.spi.EntityIndexBinder;
import org.hibernate.search.engine.spi.SearchFactoryImplementor;

/**
 * @author Emmanuel Bernard
 */
class Helper {
	/**
	 * return the analyzed value for a given field. If several terms are created, an exception is raised.
	 */
	static String getAnalyzedTerm(String fieldName, String value, String name, Analyzer queryAnalyzer, FieldContext fieldContext) {
		if ( fieldContext.isIgnoreAnalyzer() ) {
			return value;
		}

		try {
			final List<String> termsFromText = getAllTermsFromText(
					fieldName, value.toString(), queryAnalyzer
			);
			if (termsFromText.size() > 1) {
				StringBuilder error = new StringBuilder( "The ")
						.append( name )
						.append( " parameter leads to several terms when analyzed: " );
				for ( String term : termsFromText ) {
					error.append( term ).append( ", " );
				}
				final int length = error.length();
				throw new SearchException( error.delete( length - 1, length ).toString() );
			}
			return termsFromText.size() == 0 ? null : termsFromText.get( 0 );
		}
		catch ( IOException e ) {
			throw new AssertionFailure("IO exception while reading String stream??", e);
		}
	}

	static List<String> getAllTermsFromText(String fieldName, String localText, Analyzer analyzer) throws IOException {
		List<String> terms = new ArrayList<String>();

		// Can't deal with null at this point. Likely returned by some FieldBridge not recognizing the type.
		if ( localText == null ) {
			throw new SearchException( "Search parameter on field " + fieldName + " could not be converted. " +
					"Are the parameter and the field of the same type?" +
					"Alternatively, apply the ignoreFieldBridge() option to " +
					"pass String parameters" );
		}
		Reader reader = new StringReader(localText);
		TokenStream stream = analyzer.reusableTokenStream( fieldName, reader);
		CharTermAttribute attribute = stream.addAttribute( CharTermAttribute.class );
		stream.reset();

		while ( stream.incrementToken() ) {
			if ( attribute.length() > 0 ) {
				String term = new String( attribute.buffer(), 0, attribute.length() );
				terms.add( term );
			}
		}
		stream.end();
		stream.close();
		return terms;
	}

	static DocumentBuilderIndexedEntity<?> getDocumentBuilder(QueryBuildingContext queryContext) {
		final SearchFactoryImplementor factory = queryContext.getFactory();
		final Class<?> type = queryContext.getEntityType();
		EntityIndexBinder indexBinding = factory.getIndexBindingForEntity( type );
		if ( indexBinding == null ) {
			throw new AssertionFailure( "Class is not indexed: " + type );
		}
		return indexBinding.getDocumentBuilder();
	}
}
