using System;
using System.Collections;
using System.Data;

namespace Orciid.Core
{
	/// <summary>
	/// Transaction Log Event Entry
	/// </summary>
	/// <remarks>
	/// Instances of this class hold individual event log entries returned by 
	/// <see cref="TransactionLog"/> methods.  All properties are read-only.
	/// </remarks>
	public class TransactionLogEvent
	{
		internal int id;
		internal int userid;
		internal DateTime eventtime;
		internal string title;
		internal string data;

		internal TransactionLogEvent()
		{
		}

		/// <summary>
		/// Event Identifier
		/// </summary>
		/// <remarks>
		/// This property value is not part of to the actual event data.
		/// </remarks>
		/// <value>
		/// The internal identifier of the event.
		/// </value>
		public int ID
		{
			get
			{
				return id;
			}
		}

		/// <summary>
		/// User Identifier
		/// </summary>
		/// <remarks>
		/// In many cases the user identifier is not available and this property
		/// will return <c>0</c>.
		/// </remarks>
		/// <value>
		/// The internal identifier of the user account relevant to the event.
		/// </value>
		public int UserID
		{
			get
			{
				return userid;
			}
		}

		/// <summary>
		/// Event Time
		/// </summary>
		/// <remarks>
		/// The precision of the stored event time depends on the database, although
		/// all databases should provide the date and time to the second.
		/// </remarks>
		/// <value>
		/// The time the event was logged.
		/// </value>
		public DateTime EventTime
		{
			get
			{
				return eventtime;
			}
		}

		/// <summary>
		/// Event title
		/// </summary>
		/// <remarks>
		/// The event title should not contain any actual event data, but only provide
		/// for event classification and grouping.
		/// </remarks>
		/// <value>
		/// The title of the event.
		/// </value>
		public string Title
		{
			get
			{
				return title;
			}
		}

		/// <summary>
		/// Event data
		/// </summary>
		/// <remarks>
		/// This property holds the detailed event information.
		/// </remarks>
		/// <value>
		/// The data representing the event.
		/// </value>
		public string Data
		{
			get
			{
				return data;
			}
		}
	}

	/// <summary>
	/// Transaction log interface
	/// </summary>
	/// <remarks>
	/// This interface defines all properties and methods that must be implemented
	/// by a transaction log class.
	/// </remarks>
	public interface ITransactionLog
	{
		/// <summary>
		/// Adds a message to the log
		/// </summary>
		/// <remarks>
		/// This method adds a method to the log, leaving the data field blank.
		/// </remarks>
		/// <param name="title">The message to add to the log.</param>
		void Add(string title);
		
		/// <summary>
		/// Adds a message to the log
		/// </summary>
		/// <remarks>
		/// This method adds a method to the log
		/// </remarks>
		/// <param name="title">The message to add to the log.</param>
		/// <param name="data">Additional data to add to the log.  This object must
		/// have a meaningful <c>ToString()</c> method.</param>
		void Add(string title, object data);
		
		/// <summary>
		/// Adds a message to the log 
		/// </summary>
		/// <remarks>
		/// This method adds a method to the log 
		/// </remarks>
		/// <param name="title">The message to add to the log.</param>
		/// <param name="ex">The exception to log.</param>
		void AddException(string title, Exception ex);
		
		/// <summary>
		/// Adds a message to the log 
		/// </summary>
		/// <remarks>
		/// This method adds a method to the log 
		/// </remarks>
		/// <param name="title">The message to add to the log.</param>
		/// <param name="data">Additional data to log.</param>
		/// <param name="ex">The exception to log.</param>
		void AddException(string title, string data, Exception ex);
		
		/// <summary>
		/// Retrieve titles of logged events
		/// </summary>
		/// <remarks>
		/// This method returns the distinct titles of all logged events, sorted alphabetically.
		/// </remarks>
		/// <returns>An array of event titles</returns>
		string[] GetTitles();
		
		/// <summary>
		/// Retrieve logged events
		/// </summary>
		/// <remarks>
		/// This method returns an array of <see cref="TransactionLogEvent"/>s  matching
		/// the specified criteria.  Use <see cref="GetTitles"/> to get a list of valid
		/// event titles.
		/// </remarks>
		/// <param name="title">The title of the events</param>
		/// <param name="from">The minimum date of the matching events</param>
		/// <param name="until">The maximum date of the matching events</param>
		/// <returns>An array of events. The array may be empty if no matching
		/// events are found.</returns>
		TransactionLogEvent[] GetEvents(string title, DateTime from, DateTime until);
		
		/// <summary>
		/// Retrieve logged events
		/// </summary>
		/// <remarks>
		/// This method returns an array of <see cref="TransactionLogEvent"/>s  matching
		/// the specified criteria.
		/// </remarks>
		/// <param name="from">The minimum date of the matching events</param>
		/// <param name="until">The maximum date of the matching events</param>
		/// <returns>An array of events. The array may be empty if no matching
		/// events are found.</returns>
		TransactionLogEvent[] GetEvents(DateTime from, DateTime until);

        /// <summary>
        /// Remove transaction log events
        /// </summary>
        /// <remarks>
        /// This method permanently removes entries with a specific title and within a
        /// specified date range from the transaction log
        /// </remarks>
        /// <param name="title">Title of entries to delete</param>
        /// <param name="from">Start date</param>
        /// <param name="until">End date</param>
        /// <returns>The number of entries that were deleted</returns>
		int DeleteEvents(string title, DateTime from, DateTime until);

        /// <summary>
        /// Remove transaction log events
        /// </summary>
        /// <remarks>
        /// This method permanently removes entries within a
        /// specified date range from the transaction log
        /// </remarks>
        /// <param name="from">Start date</param>
        /// <param name="until">End date</param>
        /// <returns>The number of entries that were deleted</returns>
		int DeleteEvents(DateTime from, DateTime until);
	}

	/// <summary>
	/// Provides access to transaction log object
	/// </summary>
	/// <remarks>
	/// By default the transaction log object is a <see cref="DbTransactionLog"/> object.
	/// It can be overridden internally for testing purposes.
	/// </remarks>
	public class TransactionLog
	{
		private static ITransactionLog instance = new DbTransactionLog();

		/// <summary>
		/// Get instance of transaction log class
		/// </summary>
		/// <remarks>
		/// Returns the active instance of the transaction log class
		/// </remarks>
		public static ITransactionLog Instance
		{
			get
			{
				return instance;
			}
		}

		internal static ITransactionLog SetInstance(ITransactionLog i)
		{
			ITransactionLog old = instance;
			instance = i;
			return old;
		}
	}

	/// <summary>
	/// This class provides static methods used to add messages to a log in the database.
	/// </summary>
	/// <remarks>
	/// The transaction log can be used to keep a historical log of certain events, e.g.
	/// user logins, failed login attempts, unhandled exceptions, etc.  The titles of the logged 
	/// events should be kept consistent and in small numbers, the actual event data should be kept
	/// in the data object for each logged event.
	/// </remarks>
	public class DbTransactionLog:
		ITransactionLog
	{
		internal DbTransactionLog()
		{
		}

		/// <summary>
		/// Adds a message to the log in the database.
		/// </summary>
		/// <remarks>
		/// This method adds a method to the log in the database, leaving the data field blank.
		/// </remarks>
		/// <param name="title">The message to add to the database.</param>
		public void Add(string title)
		{
			Add(title, null);
		}

		/// <summary>
		/// Adds a message to the log in the database.
		/// </summary>
		/// <remarks>
		/// This method adds a method to the log in the database.
		/// </remarks>
		/// <param name="title">The message to add to the database.</param>
		/// <param name="data">Additional data to add to the database.  This object must
		/// have a meaningful <c>ToString()</c> method.</param>
		public void Add(string title, object data)
		{
			User user = User.CurrentUser();
			if (title == null)
				throw new ArgumentNullException("title", "Title cannot be null");
			if (title.Length > 50)
				title = title.Substring(0, 50);
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					@"INSERT INTO TransactionLog (UserID,EventTime,Title,Data)
					VALUES ({userid:n},{eventtime},{title},{data})");
				query.AddParam("userid", user != null ? user.ID : int.MinValue);
				query.AddParam("eventtime", DateTime.Now);
				query.AddParam("title", title);
				query.AddParam("data", data);
				conn.ExecQuery(query);
			}
		}

		/// <summary>
		/// Adds a message to the log in the database.
		/// </summary>
		/// <remarks>
		/// This method adds a method to the log in the database.
		/// </remarks>
		/// <param name="title">The message to add to the database.</param>
		/// <param name="ex">The exception to log.</param>
		public void AddException(string title, Exception ex)
		{
			AddException(title, "", ex);
		}

		/// <summary>
		/// Adds a message to the log in the database.
		/// </summary>
		/// <remarks>
		/// This method adds a method to the log in the database.
		/// </remarks>
		/// <param name="title">The message to add to the database.</param>
		/// <param name="data">Additional data to log.</param>
		/// <param name="ex">The exception to log.</param>
		public void AddException(string title, string data, Exception ex)
		{
			Add(title, String.Format("Data: {0}\nException: {1}",
				data,
				ex.ToString()));
		}

		/// <summary>
		/// Retrieve titles of logged events
		/// </summary>
		/// <remarks>
		/// This method returns the distinct titles of all logged events, sorted alphabetically.
		/// </remarks>
		/// <returns>An array of event titles</returns>
		public string[] GetTitles()
		{
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					"SELECT DISTINCT Title FROM TransactionLog ORDER BY Title");
				DataTable table = conn.SelectQuery(query);
				string[] titles = new string[table.Rows.Count];
				for (int i = 0; i < table.Rows.Count; i++)
					titles[i] = conn.DataToString(table.Rows[i]["Title"]);
				return titles;
			}
		}

		/// <summary>
		/// Retrieve logged events
		/// </summary>
		/// <remarks>
		/// This method returns an array of <see cref="TransactionLogEvent"/>s  matching
		/// the specified criteria.  Use <see cref="GetTitles"/> to get a list of valid
		/// event titles.
		/// </remarks>
		/// <param name="title">The title of the events</param>
		/// <param name="from">The minimum date of the matching events</param>
		/// <param name="until">The maximum date of the matching events</param>
		/// <returns>An array of events. The array may be empty if no matching
		/// events are found.</returns>
		public TransactionLogEvent[] GetEvents(string title, DateTime from, DateTime until)
		{
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					@"SELECT ID,UserID,EventTime,Title,Data FROM TransactionLog WHERE " + 
					(title == null ? "" : "Title={title} AND ") + 
					@"EventTime>={from} AND EventTime<={until}
					ORDER BY EventTime DESC");
				query.AddParam("title", title);
				query.AddParam("from", from);
				query.AddParam("until", until);
				DataTable table = conn.SelectQuery(query);
				TransactionLogEvent[] events = new TransactionLogEvent[table.Rows.Count];
				for (int i = 0; i < table.Rows.Count; i++)
				{
					DataRow row = table.Rows[i];
					TransactionLogEvent e = new TransactionLogEvent();
					e.id = conn.DataToInt(row["ID"], 0);
					e.userid = conn.DataToInt(row["UserID"], 0);
					e.eventtime = conn.DataToDateTime(row["EventTime"]);
					e.title = conn.DataToString(row["Title"]);
					e.data = conn.DataToString(row["Data"]);
					events[i] = e;
				}
				return events;
			}
		}

        /// <summary>
        /// Remove transaction log events
        /// </summary>
        /// <remarks>
        /// This method permanently removes entries with a specific title and within a
        /// specified date range from the transaction log
        /// </remarks>
        /// <param name="from">Start date</param>
        /// <param name="until">End date</param>
        /// <returns>The number of entries that were deleted</returns>
		public int DeleteEvents(DateTime from, DateTime until)
		{
			return DeleteEvents(null, from, until);
		}

        /// <summary>
        /// Remove transaction log events
        /// </summary>
        /// <remarks>
        /// This method permanently removes entries within a
        /// specified date range from the transaction log
        /// </remarks>
        /// <param name="title">Title of entries to delete</param>
        /// <param name="from">Start date</param>
        /// <param name="until">End date</param>
        /// <returns>The number of entries that were deleted</returns>
        public int DeleteEvents(string title, DateTime from, DateTime until)
		{
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					@"DELETE FROM TransactionLog WHERE " +
					(title == null ? "" : "Title={title} AND ") + 
					@"EventTime>={from} AND EventTime<={until}");
				query.AddParam("title", title);
				query.AddParam("from", from);
				query.AddParam("until", until);
				return conn.ExecQuery(query);
			}
		}

		/// <summary>
		/// Retrieve logged events
		/// </summary>
		/// <remarks>
		/// This method returns an array of <see cref="TransactionLogEvent"/>s  matching
		/// the specified criteria.
		/// </remarks>
		/// <param name="from">The minimum date of the matching events</param>
		/// <param name="until">The maximum date of the matching events</param>
		/// <returns>An array of events. The array may be empty if no matching
		/// events are found.</returns>
		public TransactionLogEvent[] GetEvents(DateTime from, DateTime until)
		{
			return GetEvents(null, from, until);
		}
	}
}
