/* ===================================================================
 * UserLucenePlugin.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: UserLucenePlugin.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.UserCriteria;
import magoffin.matt.ieat.dao.UserDao;
import magoffin.matt.ieat.dao.UserDao.UserIndexCallbackData;
import magoffin.matt.ieat.domain.UiUserMatch;
import magoffin.matt.ieat.domain.User;
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.index.Term;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.BooleanClause.Occur;

/**
 * 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 UserLucenePlugin extends AbstractLucenePlugin implements LucenePlugin {

	private UserDao userDao = null;
	private final Logger log = Logger.getLogger(UserLucenePlugin.class);

	/**
	 * Default constructor.
	 */
	public UserLucenePlugin() {
		super();
		setIndexType(IndexType.USER.toString());
	}

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

	public void index(Object objectId, IndexWriter writer) {
		User r = userDao.get((Integer)objectId);
		indexUser(r, writer);
	}

	public void indexObject(Object object, IndexWriter writer) {
		indexUser((User)object, writer);
	}
	
	private void doReindexAll(final IndexWriter writer, final UserIndexResults indexResults) {
		userDao.index(
				new UserDao.UserIndexCallback() {
					public void handle(UserIndexCallbackData data) {
						User user = getDomainObjectFactory().getUserInstance();
						user.setName(data.getName());
						user.setUserId(data.getUserId());
						user.setLogin(data.getLogin());
						user.setEmail(data.getEmail());
						user.setAccessLevel(data.getAccessLevel());
						user.setCreatedDate(data.getCreatedDate());
						user.setLastLoginDate(data.getLastLoginDate());
						indexUser(user,writer);
						indexResults.count++;
					}
				}
		);
		indexResults.finished = true;
	}
	
	public IndexResults reindex() {
		final UserIndexResults results = new UserIndexResults();
		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;
					}
				}
			}, "UsersReindex").start();
		}
		return results;
	}

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

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

	private Query getQuery(UserCriteria userCriteria) {
		int mode = userCriteria.getSearchMode();
		Query result = null;
		switch ( mode ) {
			case UserCriteria.MODE_USERS_BY_ID:
				BooleanQuery rootQuery = new BooleanQuery();
				Integer[] userIds = userCriteria.getUserIds();
				for ( int i = 0; i < userIds.length; i++ ) {
					Query userIdQuery = new TermQuery(new Term(
							IndexField.ITEM_ID.getFieldName(),
							String.valueOf(userIds[i])));
					rootQuery.add(userIdQuery, Occur.SHOULD);
				}
				result = rootQuery;
				break;
				
			case UserCriteria.MODE_USER_BY_LOGIN:
				result = new TermQuery(new Term(
						IndexField.USER_LOGIN.getFieldName(),
						userCriteria.getQuery()));
				break;
			
			default:
				throw new RuntimeException("Unknown user search mode: " +mode);
		}
		return result;
	}

	/* (non-Javadoc)
	 * @see magoffin.matt.lucene.LucenePlugin#build(org.apache.lucene.document.Document)
	 */
	public SearchMatch build(Document doc) {
		UiUserMatch match = getDomainObjectFactory().getUserMatchInstance();
		match.setUserId(Integer.valueOf(doc.get(IndexField.ITEM_ID.getFieldName())));
		match.setName(doc.get(IndexField.ITEM_NAME.getFieldName()));
		match.setEmail(doc.get(IndexField.USER_EMAIL.getFieldName()));
		match.setLogin(doc.get(IndexField.USER_LOGIN.getFieldName()));
		match.setPassword("");
		
		if ( SearchMatch.class.isAssignableFrom(match.getClass()) ) {
			return (SearchMatch)match;
		}
		SearchMatch searchMatch = (SearchMatch)DelegatingInvocationHandler.wrapObject(
				match, SearchMatch.class);
		return searchMatch;
	}

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

		if ( user.getName() != null ) {
			doc.add(new Field(IndexField.ITEM_NAME.getFieldName(), user.getName(), 
					Field.Store.YES, Field.Index.TOKENIZED));

			// note we tokenize the following index key assuming a tokenizer that 
			// outputs  a single Term is configured since sorting only works on 
			// fields with a single term
			doc.add(new Field(IndexField.ITEM_INDEX_KEY.getFieldName(), user.getName(),
					Field.Store.NO, Field.Index.TOKENIZED));
		}
		
		if ( user.getAccessLevel() != null ) {
			doc.add(new Field(IndexField.USER_ACCESS_LEVEL.getFieldName(), 
					user.getAccessLevel().toString(),
					Field.Store.YES, Field.Index.UN_TOKENIZED));
		}
		
		if ( user.getCreatedDate() != null ) {
			doc.add(new Field(IndexField.CREATED_DATE.getFieldName(), 
					user.getCreatedDate().getTime().toString(),
					Field.Store.YES, Field.Index.UN_TOKENIZED));
		}
		
		if ( user.getLastLoginDate() != null ) {
			doc.add(new Field(IndexField.USER_LAST_LOGIN_DATE.getFieldName(), 
					user.getLastLoginDate().getTime().toString(),
					Field.Store.YES, Field.Index.UN_TOKENIZED));
		}
		
		if ( user.getLogin() != null ) {
			doc.add(new Field(IndexField.USER_LOGIN.getFieldName(), user.getLogin(),
					Field.Store.YES, Field.Index.TOKENIZED));
		}
		
		if ( user.getEmail() != null ) {
			doc.add(new Field(IndexField.USER_EMAIL.getFieldName(), user.getEmail(),
					Field.Store.YES, Field.Index.TOKENIZED));
		}
		
		try {
			writer.addDocument(doc);
			LuceneServiceUtils.publishIndexEvent(
					new IndexEvent(user,EventType.UPDATE, getIndexType()), 
					getIndexEventListeners());
		} catch ( IOException e ) {
			throw new RuntimeException("IOException adding user to index",e);
		}
		
		return errors;
	}
	
	private final class UserIndexResults 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 UserDao getUserDao() {
		return userDao;
	}
	
	/**
	 * @param tidbitDao the tidbitDao to set
	 */
	public void setUserDao(UserDao tidbitDao) {
		this.userDao = tidbitDao;
	}

}
