/* ===================================================================
 * UnitLucenePlugin.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: UnitLucenePlugin.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.UnitCriteria;
import magoffin.matt.ieat.dao.UnitDao;
import magoffin.matt.ieat.dao.UnitDao.UnitIndexCallbackData;
import magoffin.matt.ieat.domain.Unit;
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.BooleanQuery;
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 UnitLucenePlugin extends AbstractLucenePlugin implements LucenePlugin {

	private UnitDao unitDao = null;
	private final Logger log = Logger.getLogger(UnitLucenePlugin.class);

	/**
	 * Default constructor.
	 */
	public UnitLucenePlugin() {
		super();
		setIndexType(IndexType.UNIT.toString());
	}

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

	public void index(Object objectId, IndexWriter writer) {
		Unit r = unitDao.get((Integer)objectId);
		indexUnit(r, writer);
	}

	public void indexObject(Object object, IndexWriter writer) {
		indexUnit((Unit)object, writer);
	}
	
	private void doReindexAll(final IndexWriter writer, final UnitIndexResults indexResults) {
		unitDao.index(
				new UnitDao.UnitIndexCallback() {
					public void handle(UnitIndexCallbackData data) {
						Unit unit = getDomainObjectFactory().getUnitInstance();
						unit.setValue(data.getName());
						unit.setUnitId(data.getUnitId());
						unit.setAbbreviation(data.getAbbreviation());
						indexUnit(unit,writer);
						indexResults.count++;
					}
				}
		);
		indexResults.finished = true;
	}
	
	public IndexResults reindex() {
		final UnitIndexResults results = new UnitIndexResults();
		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;
					}
				}
			}, "UnitsReindex").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 UnitCriteria ) {
			q = getQuery((UnitCriteria)criteria);
		}
		if ( q == null ) {
			throw new RuntimeException("Unknown criteria");
		}
		return q;
	}

	private Query getQuery(UnitCriteria criteria) {
		BooleanQuery bq = (BooleanQuery) getQueryForSimilarlyNamed(
				criteria.getUnit().getValue(), 
				criteria.useApproximateSearch());
		getLucene().addTokenizedFuzzyQuery(bq,
				criteria.getUnit().getAbbreviation(),
				IndexField.UNIT_ABBREVIATION.getFieldName(), 
				getIndexType());
		return bq;
	}

	/* (non-Javadoc)
	 * @see magoffin.matt.lucene.LucenePlugin#build(org.apache.lucene.document.Document)
	 */
	public SearchMatch build(Document doc) {
		Unit match = getDomainObjectFactory().getUnitInstance();
		match.setUnitId(Integer.valueOf(doc.get(IndexField.ITEM_ID.getFieldName())));
		match.setValue(doc.get(IndexField.ITEM_NAME.getFieldName()));
		match.setAbbreviation(doc.get(IndexField.UNIT_ABBREVIATION.getFieldName()));

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

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

		if ( unit.getValue() != null ) {
			doc.add(new Field(IndexField.ITEM_NAME.getFieldName(), unit.getValue(), 
					Field.Store.YES, Field.Index.TOKENIZED));
		}
		
		if ( unit.getAbbreviation() != null ) {
			doc.add(new Field(IndexField.UNIT_ABBREVIATION.getFieldName(), 
					unit.getAbbreviation(), Field.Store.YES, Field.Index.TOKENIZED));
		}
		
		try {
			writer.addDocument(doc);
			LuceneServiceUtils.publishIndexEvent(
					new IndexEvent(unit,EventType.UPDATE, getIndexType()), 
					getIndexEventListeners());
		} catch ( IOException e ) {
			throw new RuntimeException("IOException adding unit to index",e);
		}
		
		return errors;
	}
	
	private final class UnitIndexResults 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 UnitDao getUnitDao() {
		return unitDao;
	}
	
	/**
	 * @param tidbitDao the tidbitDao to set
	 */
	public void setUnitDao(UnitDao tidbitDao) {
		this.unitDao = tidbitDao;
	}

}
