/* ===================================================================
 * IngredientLucenePlugin.java
 * 
 * Created May 25, 2006 9:18:03 PM
 * 
 * Copyright (c) 2006 Matt Magoffin.
 * 
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License as 
 * published by the Free Software Foundation; either version 2 of 
 * the License, or (at your option) any later version.
 * 
 * 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 
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 
 * 02111-1307 USA
 * ===================================================================
 * $Id: IngredientLucenePlugin.java 24 2009-05-03 23:17:13Z msqr $
 * ===================================================================
 */

package magoffin.matt.ieat.biz.impl.lucene;

import java.io.IOException;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import magoffin.matt.ieat.biz.RecipeSearchBiz.IngredientCriteria;
import magoffin.matt.ieat.dao.IngredientDao;
import magoffin.matt.ieat.dao.IngredientDao.IngredientIndexCallbackData;
import magoffin.matt.ieat.domain.Ingredient;
import magoffin.matt.ieat.domain.UiIngredientMatch;
import magoffin.matt.lucene.IndexEvent;
import magoffin.matt.lucene.IndexResults;
import magoffin.matt.lucene.LucenePlugin;
import magoffin.matt.lucene.LuceneServiceUtils;
import magoffin.matt.lucene.SearchCriteria;
import magoffin.matt.lucene.SearchMatch;
import magoffin.matt.lucene.SearchResults;
import magoffin.matt.lucene.IndexEvent.EventType;
import magoffin.matt.lucene.LuceneService.IndexWriterOp;
import magoffin.matt.util.DelegatingInvocationHandler;

import org.apache.log4j.Logger;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.search.Query;

/**
 * Lucene search plugin implementation for User objects.
 * 
 * @author matt.magoffin
 * @version $Revision: 24 $ $Date: 2009-05-04 11:17:13 +1200 (Mon, 04 May 2009) $
 */
public class IngredientLucenePlugin extends AbstractLucenePlugin implements LucenePlugin {

	private IngredientDao ingredientDao = null;
	private final Logger log = Logger.getLogger(IngredientLucenePlugin.class);

	/**
	 * Default constructor.
	 */
	public IngredientLucenePlugin() {
		super();
		setIndexType(IndexType.INGREDIENT.toString());
	}

	/* (non-Javadoc)
	 * @see magoffin.matt.lucene.LucenePlugin#getIdForObject(java.lang.Object)
	 */
	public Object getIdForObject(Object object) {
		if ( object instanceof Ingredient ) {
			return ((Ingredient)object).getIngredientId();
		}
		return null;
	}

	public void index(Object objectId, IndexWriter writer) {
		Ingredient r = ingredientDao.get((Integer)objectId);
		indexIngredient(r, writer);
	}

	public void indexObject(Object object, IndexWriter writer) {
		indexIngredient((Ingredient)object, writer);
	}
	
	private void doReindexAll(final IndexWriter writer, final IngredientIndexResults indexResults) {
		ingredientDao.index(
				new IngredientDao.IngredientIndexCallback() {
					public void handle(IngredientIndexCallbackData data) {
						Ingredient ingredient = getDomainObjectFactory().getIngredientInstance();
						ingredient.setName(data.getName());
						ingredient.setIngredientId(data.getIngredientId());
						indexIngredient(ingredient,writer);
						indexResults.count++;
					}
				}
		);
		indexResults.finished = true;
	}
	
	public IndexResults reindex() {
		final IngredientIndexResults results = new IngredientIndexResults();
		if ( isSingleThreaded() ) {
			try {
				getLucene().doIndexWriterOp(getIndexType(), true, false, true, new IndexWriterOp() {
					public void doWriterOp(String type, IndexWriter writer) {
						doReindexAll(writer, results);
					}
				});
			} finally {
				results.finished = true;
			}
		} else {
			new Thread(new Runnable() {
				public void run() {
					try {
						getLucene().doIndexWriterOp(getIndexType(), true, false, true, new IndexWriterOp() {
							public void doWriterOp(String type, IndexWriter writer) {
								doReindexAll(writer, results);
							}
						});
					} finally {
						results.finished = true;
					}
				}
			}, "IngredientsReindex").start();
		}
		return results;
	}

	public SearchResults find(SearchCriteria criteria) {
		Query q = getLuceneQueryForSearchCriteria(criteria);
		return handleStandardSearch(q);
	}

	public Object getNativeQuery(SearchCriteria criteria) {
		return getLuceneQueryForSearchCriteria(criteria);
	}
	
	private Query getLuceneQueryForSearchCriteria(SearchCriteria criteria) {
		Query q = null;
		if ( criteria instanceof IngredientCriteria ) {
			q = getQuery((IngredientCriteria)criteria);
		}
		if ( q == null ) {
			throw new RuntimeException("Unknown criteria");
		}
		return q;
	}

	private Query getQuery(IngredientCriteria criteria) {
		return getQueryForSimilarlyNamed(criteria.getIngredientName(), 
				criteria.useApproximateSearch());
	}

	/* (non-Javadoc)
	 * @see magoffin.matt.lucene.LucenePlugin#build(org.apache.lucene.document.Document)
	 */
	public SearchMatch build(Document doc) {
		UiIngredientMatch match = getDomainObjectFactory().getIngredientMatchInstance();
		match.setIngredientId(Integer.valueOf(doc.get(IndexField.ITEM_ID.getFieldName())));
		match.setName(doc.get(IndexField.ITEM_NAME.getFieldName()));

		if ( SearchMatch.class.isAssignableFrom(match.getClass()) ) {
			return (SearchMatch)match;
		}
		SearchMatch searchMatch = (SearchMatch)DelegatingInvocationHandler.wrapObject(
				match, SearchMatch.class);
		return searchMatch;
	}

	private List<Object> indexIngredient(Ingredient ingredient, IndexWriter writer) {
		List<Object> errors = new LinkedList<Object>();
		
		if ( ingredient == null || ingredient.getIngredientId() == null ) {
			// don't bother trying to index null or empty ingredient
			String msg = "Null Ingredient passed to indexIngredient()... perhaps not available in transaction?";
			log.debug(msg);
			errors.add(msg);
			return errors;
		}
		
		if ( log.isDebugEnabled() ) {
			log.debug("Indexing Ingredient " +ingredient.getIngredientId()
					+" (" +ingredient.getName() +")");
		}
		
		Document doc = new Document();
		doc.add(new Field(IndexField.ITEM_ID.getFieldName(), ingredient.getIngredientId().toString(), 
				Field.Store.YES, Field.Index.UN_TOKENIZED));

		if ( ingredient.getName() != null ) {
			doc.add(new Field(IndexField.ITEM_NAME.getFieldName(), ingredient.getName(), 
					Field.Store.YES, Field.Index.TOKENIZED));
		}
		
		try {
			writer.addDocument(doc);
			LuceneServiceUtils.publishIndexEvent(
					new IndexEvent(ingredient,EventType.UPDATE, getIndexType()),
					getIndexEventListeners());
		} catch ( IOException e ) {
			throw new RuntimeException("IOException adding ingredient to index",e);
		}
		
		return errors;
	}
	
	private final class IngredientIndexResults implements IndexResults {

		private int count = 0;
		private Map<Integer, String> errors = new LinkedHashMap<Integer, String>();
		private boolean finished = false;

		public Map<? extends Serializable, String> getErrors() {
			return errors;
		}

		public int getNumIndexed() {
			return count - errors.size();
		}

		public int getNumProcessed() {
			return count;
		}

		public boolean isFinished() {
			return finished;
		}

	}

	/**
	 * @return the tidbitDao
	 */
	public IngredientDao getIngredientDao() {
		return ingredientDao;
	}
	
	/**
	 * @param tidbitDao the tidbitDao to set
	 */
	public void setIngredientDao(IngredientDao tidbitDao) {
		this.ingredientDao = tidbitDao;
	}

}
