/*
 * Copyright 2002-2005 Uwyn bvba/sprl <info[remove] at uwyn dot com>
 * Distributed under the terms of the GNU Lesser General Public
 * License, v2.1 or later
 *
 * $Id: Search.java 1587 2005-03-13 09:52:23Z gbevin $
 */
package com.uwyn.drone.webui.elements.pub;

import java.util.*;
import org.apache.lucene.search.*;

import com.uwyn.drone.DroneConfig;
import com.uwyn.drone.core.Bot;
import com.uwyn.drone.core.BotsRunner;
import com.uwyn.drone.core.Channel;
import com.uwyn.drone.tools.SearchTool;
import com.uwyn.rife.config.Config;
import com.uwyn.rife.engine.Element;
import com.uwyn.rife.site.ConstrainedProperty;
import com.uwyn.rife.site.PagedNavigation;
import com.uwyn.rife.site.Validation;
import com.uwyn.rife.site.ValidationError;
import com.uwyn.rife.template.Template;
import com.uwyn.rife.tools.ExceptionUtils;
import com.uwyn.rife.tools.SortListComparables;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.DateField;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.MultiReader;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;

public class Search extends Element
{
	private static final int 				LIMIT = 100;
	private static final int 				SPAN = 5;

	public static final SimpleDateFormat 	INPUT_DATE_FORMAT_SHORT = new SimpleDateFormat("dd/MM/yyyy");
	public static final SimpleDateFormat 	LUCENE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");

	private Template 	mTemplate = null;
	private HashMap		mNickColors = new HashMap();
	private int 		mColorCounter = 0;
	private String 		mCurrentDate = null;
	private String 		mCurrentBotname = null;
	private String 		mCurrentServername = null;
	private String 		mCurrentChannelname = null;

	static
	{
		INPUT_DATE_FORMAT_SHORT.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
		LUCENE_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
	}

	public void initialize()
	{
		mTemplate = getHtmlTemplate("drone.pub.search");
	}

	public void processElement()
	{
		SearchBean bean =  (SearchBean)getNamedInputBean("SearchBean");
		if (!bean.isValidSearch())
		{
			bean = (SearchBean)getNamedInputBean("OffsetBean");
		}

		if (bean.isValidSearch())
		{
			doSearch(bean, getInputInt("offset", 0));

			return;
		}

		print(mTemplate);
	}

	private void doSearch(SearchBean submission, int offset)
	{
		assert submission != null;

		BooleanQuery.setMaxClauseCount(Integer.MAX_VALUE);

		SearchTool 		search_tool = new SearchTool();
		BooleanQuery 	query = search_tool.getSearchQuery(submission);

		setNamedOutputBean("OffsetBean", submission);

		if (submission.isValidSearch() && query != null)
		{
			Hits hits = null;

			try
			{
				MultiReader 			reader = null;
				ParallelMultiSearcher 	searcher = null;

				List 					searchers = new ArrayList();
				List 					readers = new ArrayList();
				Collection 				bots = BotsRunner.getRepInstance().getBots();
				Iterator 				iter = bots.iterator();

				while (iter.hasNext())
				{
					Bot bot = (Bot)iter.next();

					Iterator channels_it = bot.getJoinedChannels().iterator();
					while (channels_it.hasNext())
					{
						Channel 		channel = ((Channel)channels_it.next());
						StringBuffer 	key = new StringBuffer();
						key
							.append(Config.getRepInstance().getString("LUCENE_DIR"))
							.append(File.separator)
							.append(bot.getName())
							.append("-")
							.append(bot.getServer().getServerName())
							.append("-")
							.append(channel.getName().substring(1));
						
						if (submission.getChannel() == null ||
							submission.getChannel().equals(""))
						{
							IndexReader 	r = IndexReader.open(key.toString());
							IndexSearcher 	s = new IndexSearcher(r);

							searchers.add(s);
							readers.add(r);
						}
						else
						{
							String submissionChannel = "#" + submission.getChannel().substring(submission.getChannel().indexOf("#")+1);
							if (channel.getName().equals(submissionChannel))
							{
								IndexReader 	r = IndexReader.open(key.toString());
								IndexSearcher 	s = new IndexSearcher(r);

								searchers.add(s);
								readers.add(r);
							}
						}
					}
				}

				IndexSearcher[] searcherArray = (IndexSearcher[])searchers.toArray(new IndexSearcher[0]);
				IndexReader[] 	readerArray = (IndexReader[])readers.toArray(new IndexReader[0]);

				searcher = new ParallelMultiSearcher(searcherArray);
				reader = new MultiReader(readerArray);

				SortField dateSort = new SortField("momentDateSort", SortField.AUTO, true);
				SortField timeSort = new SortField("momentTimeSort", SortField.AUTO, false);

				hits = searcher.search(query, new Sort(new SortField[] { dateSort, timeSort }));

				// only highlight in the message
				Query 					keywordQuery = search_tool.getKeywordQuery();
				Highlighter 			highlighter = null;
				if (keywordQuery != null)
				{
					Query 				rewritten_query =  keywordQuery.rewrite(reader);
					SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<span class=\"highlighted\">", "</span>");
					
					highlighter = new Highlighter(formatter, new QueryScorer(rewritten_query));
				}

				for (int i = offset; i < LIMIT + offset; i++)
				{
					if (i >= hits.length())
					{
						// handle partial full pages
						break;
					}

					Document doc = hits.doc(i);

					// pull in our values
					String 	nickname = doc.getField("nickname").stringValue();
					String 	message = doc.getField("message").stringValue();
					String 	channelname = doc.getField("channel").stringValue();
					String 	botname = doc.getField("botname").stringValue();
					String 	servername = doc.getField("servername").stringValue();
					Date 	moment = DateField.stringToDate(doc.getField("moment").stringValue());
					String 	day = ChannelLog.OUTPUT_DATE_FORMAT.format(moment);
					String 	time = ChannelLog.OUTPUT_TIME_FORMAT.format(moment);
					String 	outputday = ChannelLog.DATE_FORMAT.format(moment);

					// stolen directly from ChannelLog
					String nickcolor = (String)mNickColors.get(nickname);
					if (null == nickcolor)
					{
						nickcolor = ChannelLog.NICK_COLORS[mColorCounter++%ChannelLog.NICK_COLORS.length];
						mNickColors.put(nickname, nickcolor);
					}
					mTemplate.setValue("bgcolor", nickcolor);

					if (!channelname.startsWith("#"))
					{
						channelname = "#" + channelname;
					}

					// output a header if we're no longer in our little
					// section of the world, i.e. date, server, channel, or
					// bot changed
					if (mCurrentDate == null || !mCurrentDate.equals(day) ||
						mCurrentServername == null || !mCurrentServername.equals(servername) ||
						mCurrentBotname == null || !mCurrentBotname.equals(botname) ||
						mCurrentChannelname == null || !mCurrentChannelname.equals(channelname))
					{
						mTemplate.setValue("day", day);
						mTemplate.setValue("botname", botname);
						mTemplate.setValue("servername", servername);
						mTemplate.setValue("channelname", channelname);

						try
						{
							SearchBean bean = new SearchBean();
							bean.setKeyword(submission.getKeyword());
							bean.setChannel(botname + " - " + channelname);
							bean.setDate(INPUT_DATE_FORMAT_SHORT.format(ChannelLog.DATE_FORMAT.parse(outputday)));

							setNamedOutputBean("SearchBean", bean);
						}
						catch (ParseException e)
						{
							Logger
								.getLogger("com.uwyn.drone.webui.elements.pub")
								.severe(ExceptionUtils.getExceptionStackTrace(e));
						}

						setOutput("day", outputday);
						setOutput("channelname", channelname);
						setOutput("botname", botname);
						setExitQuery(mTemplate, "show_channel_log", null, null);

						mTemplate.setBlock("day_header", "day_header");

						mCurrentDate = day;
						mCurrentServername = servername;
						mCurrentBotname = botname;
						mCurrentChannelname = channelname;
					}
					else
					{
						mTemplate.setValue("day_header", "");
					}

					// output the message
					mTemplate.setValue("time", time);

					// translate the \u0001ACTION command which corresponds to
					// /me so that the user's nickname is used instead
					StringBuffer message_buf = new StringBuffer();
					if (message.startsWith(ChannelLog.IRC_ACTION))
					{
						message_buf
							.append(nickname)
							.append(message.substring(ChannelLog.IRC_ACTION.length()));
					}
					else
					{
						message_buf.append(message);
					}

					String encoded_message = encodeHtml(message_buf.toString());

					if (highlighter != null)
					{
						TokenStream tokenStream = new StandardAnalyzer().tokenStream("message", new StringReader(encoded_message));
						String highlighted = highlighter.getBestFragments(tokenStream, encoded_message, 25, "...");

						if (!highlighted.equals(""))
						{
							encoded_message = highlighted;
						}
					}
					
					encoded_message = ChannelLog.convertUrl(encoded_message, ChannelLog.URL_HIGHLIGHT);

					Matcher email_matcher = ChannelLog.EMAIL_HIGHLIGHT.matcher(encoded_message);
					encoded_message = email_matcher.replaceAll("<a href=\"mailto:$1\">$1</a>");

					mTemplate.setValue("nickname", nickname);
					mTemplate.setValue("message", encoded_message);

					mTemplate.appendBlock("messages", "message");
				}

				searcher.close();
				reader.close();
			}
			catch (IOException e)
			{
				Logger
					.getLogger("com.uwyn.drone.webui.elements.pub")
					.severe(ExceptionUtils.getExceptionStackTrace(e));
			}

			// finalize the results
			mTemplate.appendBlock("results", "results");

			if (hits != null &&
				hits.length() > LIMIT)
			{
				PagedNavigation.generateNavigation(this, mTemplate, hits.length(), LIMIT, offset, SPAN);

				mTemplate.setBlock("ranged_table", "ranged_table");
			}
		}

		generateForm(mTemplate, submission);

		print(mTemplate);
	}

	public void doSearch()
	{
		//reset offset
		setOutput("offset", 0);

		clearNamedOutputBean("OffsetBean");

		doSearch((SearchBean)getSubmissionBean(SearchBean.class), 0);
	}

	public static class SearchBean extends Validation
	{
		private String 	mKeyword = null;
		private String 	mDate = null;
		private String 	mBeginDate = null;
		private String 	mEndDate = null;
		private String 	mUser = null;
		private String 	mChannel = null;

		protected void activateValidation()
		{
			Collection bots = BotsRunner.getRepInstance().getBots();
			Iterator iter = bots.iterator();

			List channels = new ArrayList();

			while (iter.hasNext())
			{
				Bot bot = (Bot)iter.next();

				Iterator channelsIter = bot.getJoinedChannels().iterator();
				while (channelsIter.hasNext())
				{
					String name = bot.getName() + " - " + ((Channel)channelsIter.next()).getName();
					channels.add(name);
				}
			}
			
			new SortListComparables().sort(channels);

			addConstraint(new ConstrainedProperty("channel")
							  .inList((String[])channels.toArray(new String[0])));
			addConstraint(new ConstrainedProperty("keyword").notNull(true).notEmpty(true));
		}

		public boolean isValidSearch()
		{
			if (mKeyword == null ||
				(mKeyword != null && mKeyword.equals("")))
			{
				addValidationError(new ValidationError.INVALID("keyword"));
				
				return false;
			}
			
			return true;
		}

		public void 	setChannel(String channel) 		{ mChannel = channel; }
		public String 	getChannel() 					{ return mChannel; }
		public void 	setKeyword(String keyword) 		{ mKeyword = keyword; }
		public String 	getKeyword() 					{ return mKeyword; }
		public void 	setDate(String date) 			{ mDate = date; }
		public String 	getDate() 						{ return mDate; }
		public void 	setBeginDate(String beginDate) 	{ mBeginDate = beginDate; }
		public String 	getBeginDate() 					{ return mBeginDate; }
		public void 	setEndDate(String endDate) 		{ mEndDate = endDate; }
		public String 	getEndDate() 					{ return mEndDate; }
		public void 	setUser(String user) 			{ mUser = user; }
		public String 	getUser() 						{ return mUser; }
	}
}

