/*
 * 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: DatabaseLogs.java 1702 2005-03-26 21:54:38Z gbevin $
 */
package com.uwyn.drone.modules.logmanagement;

import com.uwyn.drone.modules.logmanagement.exceptions.*;

import com.uwyn.drone.DroneConfig;
import com.uwyn.drone.core.Bot;
import com.uwyn.drone.core.Channel;
import com.uwyn.drone.modules.exceptions.LogManagerException;
import com.uwyn.drone.modules.logmanagement.LogManager;
import com.uwyn.drone.protocol.ServerMessage;
import com.uwyn.rife.database.Datasource;
import com.uwyn.rife.database.DbPreparedStatement;
import com.uwyn.rife.database.DbPreparedStatementHandler;
import com.uwyn.rife.database.DbQueryManager;
import com.uwyn.rife.database.exceptions.DatabaseException;
import com.uwyn.rife.database.queries.CreateTable;
import com.uwyn.rife.database.queries.DropTable;
import com.uwyn.rife.database.queries.Insert;
import com.uwyn.rife.database.queries.Select;
import com.uwyn.rife.search.dam.indexqueuequerymanagers.IndexQueueFactory;
import com.uwyn.rife.search.dam.indexqueuequerymanagers.exceptions.IndexQueryManagerInstallationException;
import com.uwyn.rife.search.dam.indexqueuequerymanagers.exceptions.IndexQueryManagerRemovalException;
import com.uwyn.rife.tools.StringUtils;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;
import java.util.logging.Logger;

public abstract class DatabaseLogs extends DbQueryManager implements LogManager
{
	private static SimpleDateFormat	FULL_FORMAT = null;
	private static SimpleDateFormat	YEARDATE_FORMAT = null;
	private static SimpleDateFormat	DATETIME_FORMAT = null;
	private static SimpleDateFormat	DATE_FORMAT = null;
	private static SimpleDateFormat	TIME_FORMAT = null;

	static
	{
		FULL_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm");
		YEARDATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd");
		DATETIME_FORMAT = new SimpleDateFormat("MM/dd HH:mm");
		DATE_FORMAT = new SimpleDateFormat("MM/dd");
		TIME_FORMAT = new SimpleDateFormat("HH:mm");

		FULL_FORMAT.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
		YEARDATE_FORMAT.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
		DATETIME_FORMAT.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
		DATE_FORMAT.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
		TIME_FORMAT.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
	}

    protected DatabaseLogs(Datasource datasource)
    {
        super(datasource);
    }

	public abstract boolean install()
	throws LogManagerException;

	public abstract boolean remove()
	throws LogManagerException;

	protected boolean _install(CreateTable createTableLog, String createGetLogMessagesIndex)
	throws LogManagerException
	{
		assert createTableLog != null;

		try
		{
			executeUpdate(createTableLog);
			executeUpdate(createGetLogMessagesIndex);
		}
		catch (DatabaseException e)
		{
			throw new InstallErrorException(e);
		}

		try
		{
			IndexQueueFactory.getInstance(getDatasource()).create();
		}
		catch (IndexQueryManagerInstallationException e)
		{
			Logger
				.getLogger("com.uwyn.drone.tools")
				.warning("Tried to install IndexQueueDbStructure but it failed; " +
					"probably already installed.");
		}

		return true;
	}

	protected void _addLog(Insert addLog, final Bot bot, final Channel channel, final ServerMessage serverMessage)
	throws LogManagerException
	{
		assert addLog != null;

		if (null == bot)			throw new IllegalArgumentException("bot can't be null.");
		if (null == channel)		throw new IllegalArgumentException("channel can't be null.");
		if (null == serverMessage)	throw new IllegalArgumentException("serverMessage can't be null.");

		try
		{
			if (0 == executeUpdate(addLog, new DbPreparedStatementHandler() {
					public void setParameters(DbPreparedStatement statement)
					{
						statement
							.setString("botname", bot.getName())
							.setString("channel", channel.getName())
							.setString("servername", channel.getServer().getServerName())
							.setString("nickname", serverMessage.getPrefix().getNickName())
							.setString("username", serverMessage.getPrefix().getUser())
							.setString("hostname", serverMessage.getPrefix().getHost())
							.setString("message", serverMessage.getTrailing())
							.setString("raw", serverMessage.getRaw());
					}
				}))
			{
				throw new AddLogErrorException(bot, channel, serverMessage);
			}
		}
		catch (DatabaseException e)
		{
			throw new AddLogErrorException(bot, channel, serverMessage, e);
		}
	}

	private Timestamp parseInterval(String interval)
	{
		Timestamp result = null;

		Calendar result_cal = Calendar.getInstance(TimeZone.getTimeZone(DroneConfig.getTimezone()));
		Calendar now_cal = Calendar.getInstance(TimeZone.getTimeZone(DroneConfig.getTimezone()));
		try
		{
			Date search_begin_date = FULL_FORMAT.parse(interval);
			result_cal.setTime(search_begin_date);
			result = new Timestamp(result_cal.getTimeInMillis());
		}
		catch (ParseException e)
		{
			try
			{
				Date search_begin_date = YEARDATE_FORMAT.parse(interval);
				result_cal.setTime(search_begin_date);
				result_cal.set(Calendar.HOUR, now_cal.get(Calendar.HOUR));
				result_cal.set(Calendar.MINUTE, now_cal.get(Calendar.MINUTE));
				result = new Timestamp(result_cal.getTimeInMillis());
			}
			catch (ParseException e2)
			{
				try
				{
					Date search_begin_date = DATETIME_FORMAT.parse(interval);
					result_cal.setTime(search_begin_date);
					result_cal.set(Calendar.YEAR, now_cal.get(Calendar.YEAR));
					result = new Timestamp(result_cal.getTimeInMillis());
				}
				catch (ParseException e3)
				{
					try
					{
						Date search_begin_date = DATE_FORMAT.parse(interval);
						result_cal.setTime(search_begin_date);
						result_cal.set(Calendar.YEAR, now_cal.get(Calendar.YEAR));
						result_cal.set(Calendar.HOUR, now_cal.get(Calendar.HOUR));
						result_cal.set(Calendar.MINUTE, now_cal.get(Calendar.MINUTE));
						result = new Timestamp(result_cal.getTimeInMillis());
					}
					catch (ParseException e4)
					{
						try
						{
							Date search_begin_date = TIME_FORMAT.parse(interval);
							result_cal.setTime(search_begin_date);
							result_cal.set(Calendar.YEAR, now_cal.get(Calendar.YEAR));
							result_cal.set(Calendar.MONTH, now_cal.get(Calendar.MONTH));
							result_cal.set(Calendar.DATE, now_cal.get(Calendar.DATE));
							result = new Timestamp(result_cal.getTimeInMillis());
						}
						catch (ParseException e5)
						{
							return null;
						}
					}
				}
			}
		}

		return result;
	}

	protected boolean _searchLog(Select searchLog, LogResultProcessor processor, final Bot bot, final Channel channel, final String search)
	throws LogManagerException
	{
		assert searchLog != null;

		if (null == bot)			throw new IllegalArgumentException("bot can't be null.");
		if (null == channel)		throw new IllegalArgumentException("channel can't be null.");
		if (null == search)			throw new IllegalArgumentException("search can't be null");
		if (0 == search.length())	throw new IllegalArgumentException("search can't be empty");

		boolean result = false;
		try
		{
			String		search_message = null;
			String		search_nickname = null;
			Timestamp	search_begin = null;
			Timestamp	search_end = null;
			int			search_count = -1;

			Select		searchLogAdapted = searchLog.clone();
			ArrayList	search_parts = StringUtils.split(search, ";");
			Iterator	search_parts_it = search_parts.iterator();
			String		search_part = null;
			while (search_parts_it.hasNext())
			{
				search_part = (String)search_parts_it.next();

				if (search_part.startsWith("nick="))
				{
					int equality_index = search_part.indexOf("=");
					if (search_nickname != null ||
						-1 == equality_index)
					{
						throw new InvalidSearchSyntaxException(bot, channel, search);
					}

					search_nickname = search_part.substring(equality_index+1).toLowerCase();
					if ("in.co.daffodil.db.jdbc.DaffodilDBDriver".equals(getDatasource().getAliasedDriver()))
					{
						searchLogAdapted.whereParameterAnd("lcase(nickname)", "nickname" , "=");
					}
					else
					{
						searchLogAdapted.whereParameterAnd("lower(nickname)", "nickname" , "=");
					}
				}

				else if (search_part.startsWith("begin="))
				{
					int equality_index = search_part.indexOf("=");
					if (search_begin != null ||
						-1 == equality_index)
					{
						throw new InvalidSearchSyntaxException(bot, channel, search);
					}

					if (null == (search_begin = parseInterval(search_part.substring(equality_index+1))))
					{
						throw new InvalidSearchSyntaxException(bot, channel, search);
					}

					searchLogAdapted.whereParameterAnd("moment", "begin", ">=");
				}

				else if (search_part.startsWith("end="))
				{
					int equality_index = search_part.indexOf("=");
					if (search_end != null ||
						-1 == equality_index)
					{
						throw new InvalidSearchSyntaxException(bot, channel, search);
					}

					if (null == (search_end = parseInterval(search_part.substring(equality_index+1))))
					{
						throw new InvalidSearchSyntaxException(bot, channel, search);
					}

					searchLogAdapted.whereParameterAnd("moment", "end", "<");
				}

				else if (search_part.startsWith("#="))
				{
					int equality_index = search_part.indexOf("=");
					if (search_count != -1 ||
						-1 == equality_index)
					{
						throw new InvalidSearchSyntaxException(bot, channel, search);
					}

					try
					{
						search_count = Integer.parseInt(search_part.substring(equality_index+1));
						searchLogAdapted.limit(search_count);
					}
					catch (NumberFormatException e)
					{
						throw new InvalidSearchSyntaxException(bot, channel, search);
					}
				}

				else
				{
					if (search_message != null)
					{
						throw new InvalidSearchSyntaxException(bot, channel, search);
					}

					search_message = search_part.toLowerCase();
					if ("in.co.daffodil.db.jdbc.DaffodilDBDriver".equals(getDatasource().getAliasedDriver()))
					{
						searchLogAdapted.whereParameterAnd("lcase(message)", "message", "LIKE");
					}
					else
					{
						searchLogAdapted.whereParameterAnd("lower(message)", "message", "LIKE");
					}
				}
			}

			if (null == search_message &&
				null == search_nickname &&
				null == search_begin &&
				null == search_end &&
				-1 == search_count)
			{
				throw new InvalidSearchSyntaxException(bot, channel, search);
			}

			final String	param_search_message = search_message;
			final String	param_search_nickname = search_nickname;
			final Timestamp	param_search_begin = search_begin;
			final Timestamp	param_search_end = search_end;
			result = executeFetchAll(searchLogAdapted, processor, new DbPreparedStatementHandler() {
				public void setParameters(DbPreparedStatement statement)
				{
					statement
						.setString("botname", bot.getName())
						.setString("channel", channel.getName())
						.setString("servername", channel.getServer().getServerName());
					if (param_search_message != null)
					{
						statement.setString("message", "%"+param_search_message+"%");
					}
					if (param_search_nickname != null)
					{
						statement.setString("nickname", param_search_nickname);
					}
					if (param_search_begin != null)
					{
						statement.setTimestamp("begin", param_search_begin);
					}
					if (param_search_end != null)
					{
						statement.setTimestamp("end", param_search_end);
					}
				}});
		}
		catch (DatabaseException e)
		{
			throw new SearchLogErrorException(bot, channel, search, e);
		}

		return result;
	}

	protected boolean _getLogMessages(Select getLogMessages, LogResultProcessor processor, final Bot bot, final Channel channel, Calendar day)
	throws LogManagerException
	{
		assert getLogMessages != null;

		if (null == bot)		throw new IllegalArgumentException("bot can't be null.");
		if (null == channel)	throw new IllegalArgumentException("channel can't be null.");
		if (null == day)		throw new IllegalArgumentException("day can't be null.");

		Calendar begin = Calendar.getInstance(TimeZone.getTimeZone(DroneConfig.getTimezone()));
		Calendar end = Calendar.getInstance(TimeZone.getTimeZone(DroneConfig.getTimezone()));
		begin.set(day.get(Calendar.YEAR), day.get(Calendar.MONTH), day.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
		end.set(day.get(Calendar.YEAR), day.get(Calendar.MONTH), day.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
		end.add(Calendar.DAY_OF_MONTH, 1);

		boolean result = false;
		try
		{
			final Timestamp timestamp_begin = new Timestamp(begin.getTimeInMillis());
			final Timestamp timestamp_end = new Timestamp(end.getTimeInMillis());
			result = executeFetchAll(getLogMessages, processor, new DbPreparedStatementHandler() {
				public void setParameters(DbPreparedStatement statement)
				{
					statement
						.setString("botname", bot.getName())
						.setString("channel", channel.getName())
						.setString("servername", channel.getServer().getServerName())
						.setTimestamp("begin", timestamp_begin)
						.setTimestamp("end", timestamp_end)
						.executeQuery();
				}});
		}
		catch (DatabaseException e)
		{
			throw new GetLogMessagesErrorException(bot, channel, day, e);
		}

		return result;
	}

	protected boolean _remove(DropTable dropTableLog, String dropGetLogMessagesIndex)
	throws LogManagerException
	{
		assert dropTableLog != null;

		try
		{
			executeUpdate(dropGetLogMessagesIndex);
			executeUpdate(dropTableLog);
		}
		catch (DatabaseException e)
		{
			throw new RemoveErrorException(e);
		}

		try
		{
			IndexQueueFactory.getInstance(getDatasource()).remove();
		}
		catch (IndexQueryManagerRemovalException e)
		{
			throw new RemoveErrorException(new DatabaseException(e));
		}

		return true;
	}
}
