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

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Field;

import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Norms;
import org.hibernate.search.annotations.NumericField;
import org.hibernate.search.annotations.Spatial;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.TwoWayFieldBridge;
import org.hibernate.search.bridge.builtin.impl.NullEncodingTwoWayFieldBridge;
import org.hibernate.search.bridge.impl.BridgeFactory;
import org.hibernate.search.engine.BoostStrategy;
import org.hibernate.search.engine.spi.AbstractDocumentBuilder;
import org.hibernate.search.impl.ConfigContext;
import org.hibernate.search.util.impl.ReflectionHelper;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;

/**
 * Encapsulating the metadata for a single {@code @Field} annotation.
 *
 * @author Hardy Ferentschik
 */
public class FieldMetadata {
	private static final Log log = LoggerFactory.make();

	private final XProperty fieldGetter;
	private final String fieldName;
	private final Store store;
	private final Field.Index index;
	private final Float boost;
	private final BoostStrategy dynamicBoostStrategy;
	private final Field.TermVector termVector;
	private final Integer precisionStep;
	private final String nullToken;
	private final FieldBridge fieldBridge;
	private final Analyzer analyzer;

	public FieldMetadata(String prefix,
						XProperty member,
						org.hibernate.search.annotations.Field fieldAnn,
						NumericField numericFieldAnn,
						Spatial spatialAnn,
						ConfigContext context,
						ReflectionManager reflectionManager) {
		ReflectionHelper.setAccessible( member );
		fieldGetter = member;
		String indexNullAs;
		Analyzer tmpAnalyzer;
		if ( fieldAnn != null ) {
			index = AnnotationProcessingHelper.getIndex( fieldAnn.index(), fieldAnn.analyze(), fieldAnn.norms() );
			store = fieldAnn.store();
			fieldName = prefix + ReflectionHelper.getAttributeName( member, fieldAnn.name() );
			boost = AnnotationProcessingHelper.getBoost( member, fieldAnn );
			termVector = AnnotationProcessingHelper.getTermVector( fieldAnn.termVector() );
			// null token
			indexNullAs = fieldAnn.indexNullAs();
			if ( indexNullAs.equals( org.hibernate.search.annotations.Field.DO_NOT_INDEX_NULL ) ) {
				indexNullAs = null;
			}
			else if ( indexNullAs.equals( org.hibernate.search.annotations.Field.DEFAULT_NULL_TOKEN ) ) {
				indexNullAs = context.getDefaultNullToken();
			}
			nullToken = indexNullAs;
			tmpAnalyzer = AnnotationProcessingHelper.getAnalyzer( fieldAnn.analyzer(), context );
		}
		else {
			index = AnnotationProcessingHelper.getIndex( Index.YES, Analyze.NO, Norms.NO );
			store = spatialAnn.store();
			fieldName = prefix + ReflectionHelper.getAttributeName( member, spatialAnn.name() );
			boost = AnnotationProcessingHelper.getBoost( member, spatialAnn );
			termVector = Field.TermVector.NO;
			//FIXME what should we do about null tokens in Spatial?
			indexNullAs = null;
			nullToken = indexNullAs;
			tmpAnalyzer = null;
		}
		dynamicBoostStrategy = AnnotationProcessingHelper.getDynamicBoost( member );
		precisionStep = AnnotationProcessingHelper.getPrecisionStep( numericFieldAnn );
		FieldBridge bridge = BridgeFactory.guessType( fieldAnn, numericFieldAnn, member, reflectionManager );
		if ( indexNullAs != null && bridge instanceof TwoWayFieldBridge ) {
			bridge = new NullEncodingTwoWayFieldBridge( (TwoWayFieldBridge) bridge, indexNullAs );
		}
		fieldBridge = bridge;

		// Field > property > entity analyzer
		if ( tmpAnalyzer == null ) {
			tmpAnalyzer = AnnotationProcessingHelper.getAnalyzer(
					member.getAnnotation( org.hibernate.search.annotations.Analyzer.class ),
					context
			);
		}
		analyzer = tmpAnalyzer;
	}

	public String getFieldName() {
		return fieldName;
	}

	public Field.Index getIndex() {
		return index;
	}

	public Analyzer getAnalyzer() {
		return analyzer;
	}

	public void appendToPropertiesMetadata(AbstractDocumentBuilder.PropertiesMetadata propertiesMetadata) {
		sanityCheckFieldConfiguration( propertiesMetadata );

		propertiesMetadata.fieldGetters.add( fieldGetter );
		propertiesMetadata.fieldGetterNames.add( fieldGetter.getName() );
		propertiesMetadata.fieldNames.add( fieldName );
		propertiesMetadata.fieldNameToPositionMap.put( fieldGetter.getName(), propertiesMetadata.fieldNames.size() );
		propertiesMetadata.fieldStore.add( store );
		propertiesMetadata.fieldIndex.add( index );
		propertiesMetadata.fieldBoosts.add( boost );
		propertiesMetadata.dynamicFieldBoosts.add( dynamicBoostStrategy );
		propertiesMetadata.fieldTermVectors.add( termVector );
		propertiesMetadata.precisionSteps.add( precisionStep );
		propertiesMetadata.fieldNullTokens.add( nullToken );
		propertiesMetadata.fieldBridges.add( fieldBridge );
	}

	private void sanityCheckFieldConfiguration(AbstractDocumentBuilder.PropertiesMetadata propertiesMetadata) {
		int indexOfFieldWithSameName = propertiesMetadata.fieldNames.lastIndexOf( fieldName );
		if ( indexOfFieldWithSameName != -1 ) {
			if ( !propertiesMetadata.fieldIndex.get( indexOfFieldWithSameName ).equals( index ) ) {
				log.inconsistentFieldConfiguration( fieldName );
			}
		}
	}
}


