using System;
using System.Collections;
using System.Threading;
using System.Diagnostics;
using System.Security.Principal;
using Orciid.Core.Util;

namespace Orciid.Core
{
	/// <summary>
	/// Background task delegate
	/// </summary>
	/// <remarks>
	/// Delegate for a method that will be called during different stages of a background
	/// task's execution
	/// </remarks>
	public delegate bool BackgroundTaskDelegate(BackgroundTask task, object info);

	/// <summary>
	/// Background task
	/// </summary>
	/// <remarks>
	/// This class provides functionality to run methods in the background immediately
	/// or after a given timespan
	/// </remarks>
	public class BackgroundTask:
		IComparable
	{
		private static ArrayList tasks = new ArrayList();
		private static Hashtable activegroups = new Hashtable();

		private static bool application_running = true;
		
		private int priority = 0;
		private string title = "No name";
		private string group = null;
		private DateTime startafter;
		private bool running = false;

		/// <summary>
		/// Start event
		/// </summary>
		/// <remarks>
		/// This event gets called before a task is started.  If the delegate returns <c>false</c>,
		/// the task is not run.
		/// </remarks>
		public event BackgroundTaskDelegate OnStart;

		/// <summary>
		/// Run event
		/// </summary>
		/// <remarks>
		/// This event gets called when a task is starting to run.  This is the event
		/// that needs the work method assigned to it.
		/// </remarks>
		public event BackgroundTaskDelegate OnRun;

		/// <summary>
		/// Error event
		/// </summary>
		/// <remarks>
		/// This event gets called when the execution of a task resulted in an exception.
		/// </remarks>
		public event BackgroundTaskDelegate OnError;

		/// <summary>
		/// Finish event
		/// </summary>
		/// <remarks>
		/// This event gets called after a task finished running.
		/// </remarks>
		public event BackgroundTaskDelegate OnFinish;

		private const int NUM_TASKS = 3;

		/// <summary>
		/// Data associated with task
		/// </summary>
		/// <remarks>
		/// This hashtable can hold any data that may be required for the task execution.
		/// When a task is created, add data to this hashtable.  The data is then passed
		/// to the method which is called when the task starts to run.
		/// </remarks>
		public Hashtable Data = new Hashtable();

		private static WindowsIdentity winident = null;

		/// <summary>
		/// Initialize background tasks
		/// </summary>
		/// <remarks>
		/// Starts a predefined number of threads to handle upcoming background tasks.
		/// </remarks>
		[Initialize]
		public static void Initialize()
		{
			application_running = true;
			winident = WindowsIdentity.GetCurrent(); // quick-fix for Windows impersonation problem
			for (int i = 0; i < NUM_TASKS; i++)
				new Thread(new ThreadStart(Run)).Start();
		}

		/// <summary>
		/// Shut down background tasks
		/// </summary>
		/// <remarks>
		/// To be called when the application is being shut down, this will end the threads
		/// handling background tasks.
		/// </remarks>
		public static void Shutdown()
		{
			application_running = false;
		}

		private static void Run()
		{
			Trace.WriteLine("BackgroundTask thread started", "BT");
			if (winident != null)
				winident.Impersonate();
			while (application_running)
			{
				BackgroundTask t = null;
				lock (typeof(BackgroundTask))
				{
					int i = tasks.Count - 1;
					while (i >= 0)
					{
						t = (BackgroundTask)tasks[i];
						if (!t.running &&
							t.Wait > 0 &&
							(t.group == null || !activegroups.ContainsKey(t.group)))
							break;
						i--;
					}
					if (i < 0)
						t = null;
					else
					{
						t.running = true;
						if (t.group != null)
							activegroups.Add(t.group, null);
					}
				}
				if (t == null)
					Thread.Sleep(1000);
				else
				{
					Trace.WriteLine(String.Format("Starting task {0}", t.DebugTitle), "BT");
					// explicitly clear out cache before starting task
					CachableObject.ResetThreadCache();
					// activate anonymous user in case last task activated another user
					AnonymousUser.User.Activate(null);
					bool canceltask = false;
					try
					{
						if (t.OnStart != null)
							canceltask = t.OnStart(t, null);
					}
					catch 
					{
					}
					if (!canceltask)
					{
						try
						{
							if (t.OnRun != null)
								t.OnRun(t, null);
						}
						catch (Exception ex)
						{
							try
							{
								if (t.OnError != null)
									t.OnError(t, ex);
							}
							catch
							{
							}
						}
						try
						{
							if (t.OnFinish != null)
								t.OnFinish(t, null);
						}
						catch
						{
						}
						Trace.WriteLine(String.Format("Finished task {0}", t.DebugTitle), "BT");
					}
					else
						Trace.WriteLine(String.Format("Cancelled task {0}", t.DebugTitle), "BT");
					// explicitly clear out cache after finishing task
					CachableObject.ResetThreadCache();
					lock (typeof(BackgroundTask))
					{
						t.running = false;
						tasks.Remove(t);
						tasks.Sort();
						if (t.group != null)
							activegroups.Remove(t.group);
					}
				}
			}
		}

		/// <summary>
		/// Look up task
		/// </summary>
		/// <remarks>
		/// This method looks up a task by its title.  If multiple tasks have the same
		/// title, this method returns the first matching task.
		/// </remarks>
		/// <param name="title">The title of the task</param>
		/// <returns>The first task with a matching title, or <c>null</c> if no matching
		/// title was found.</returns>
		public static BackgroundTask FindTask(string title)
		{
			lock (typeof(BackgroundTask))
			{
				foreach (BackgroundTask t in tasks)
					if (t.title == title)
						return t;
			}
			return null;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Creates a new background task that can start immediately
		/// </remarks>
		public BackgroundTask()
		{
			startafter = DateTime.Now;
			OnError += new BackgroundTaskDelegate(BackgroundTask_OnError);
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Creates a new background task with a given title and priority.  Tasks
		/// with higher priorities will run first.
		/// </remarks>
		/// <param name="t">The title of the task</param>
		/// <param name="p">The priority of the task</param>
		public BackgroundTask(string t, int p):
			this()
		{
			title = t;
			priority = p;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Creates a new background task with a given title, priority and group association.
		/// Tasks with higher priorities will run first.
		/// Only one task of the same group will run at any given point in time.
		/// </remarks>
		/// <param name="t">The title of the task</param>
		/// <param name="p">The priority of the task</param>
		/// <param name="g">The name of the group of the task</param>
		public BackgroundTask(string t, int p, string g):
			this(t, p)
		{
			group = g;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Creates a new background task with a given title, priority, group association
		/// and start time.
		/// Tasks with higher priorities will run first.
		/// Only one task of the same group will run at any given point in time.
		/// The task will not start before the given start time.
		/// </remarks>
		/// <param name="t">The title of the task</param>
		/// <param name="p">The priority of the task</param>
		/// <param name="g">The name of the group of the task</param>
		/// <param name="a">The start time of the task</param>
		public BackgroundTask(string t, int p, string g, DateTime a):
			this(t, p, g)
		{
			startafter = a;
		}

		/// <summary>
		/// Queue background task
		/// </summary>
		/// <remarks>
		/// This method adds a background task to the list of queued tasks.  Once a task
		/// is in the queue, it will run at the soonest possible time, depending on its 
		/// priority, group association, and start time.
		/// </remarks>
		public void Queue()
		{
			lock (typeof(BackgroundTask))
			{
				tasks.Add(this);
				tasks.Sort();
			}
		}

		/// <summary>
		/// Dequeue background task
		/// </summary>
		/// <remarks>
		/// Removes a background task from the list of queued tasks.  Only tasks
		/// that have not started running yet can be removed.
		/// </remarks>
		public void Dequeue()
		{
			lock (typeof(BackgroundTask))
			{
				if (running)
					throw new CoreException("Cannot dequeue running background task");
				tasks.Remove(this);
				tasks.Sort();
			}
		}

		/// <summary>
		/// Compare two tasks
		/// </summary>
		/// <remarks>
		/// Utility method to keep queue of tasks sorted.  Tasks with higher priority or
		/// a sooner start time will be sorted higher.
		/// </remarks>
		/// <param name="obj">Another background task to compare to</param>
		/// <returns><see cref="IComparable"/></returns>
		public int CompareTo(object obj) 
		{
			if (obj is BackgroundTask) 
			{
				BackgroundTask other = (BackgroundTask)obj;
				int c = priority.CompareTo(other.priority);
				if (c == 0)
					return -startafter.CompareTo(other.startafter);
				else
					return c;
			}
			throw new ArgumentException("object is not a BackgroundTask");    
		}

		/// <summary>
		/// Task priority
		/// </summary>
		/// <remarks>
		/// Tasks with higher priority will run sooner.
		/// </remarks>
		/// <value>
		/// The priority of the background task.  
		/// </value>
		public int Priority
		{
			get
			{
				return priority;
			}
		}

		/// <summary>
		/// Task title
		/// </summary>
		/// <remarks>
		/// The title of the background task.
		/// </remarks>
		/// <value>
		/// The title of the background task.
		/// </value>
		public string Title
		{
			get
			{
				return title;
			}
		}

		/// <summary>
		/// Task start time
		/// </summary>
		/// <remarks>
		/// A task will never run before its assigned start time.
		/// </remarks>
		/// <value>
		/// The start time of the background task.
		/// </value>
		public DateTime StartAfter
		{
			get
			{
				return startafter;
			}
		}

		/// <summary>
		/// Task wait time
		/// </summary>
		/// <remarks>
		/// Returns the positive number of seconds the task has been waiting to start running,
		/// or the negative number of seconds for which the task cannot run yet because its
		/// start time is still in the future.
		/// </remarks>
		/// <value>
		/// The wait time for the background task
		/// </value>
		public double Wait
		{
			get
			{
				TimeSpan wait = DateTime.Now - startafter;
				return wait.TotalMilliseconds / 1000.0;
			}
		}

		/// <summary>
		/// Running flag
		/// </summary>
		/// <remarks>
		/// Indicates the status of the background task
		/// </remarks>
		/// <value>
		/// <c>true</c> if the task is currently running, <c>false</c> otherwise.
		/// </value>
		public bool Running
		{
			get
			{
				return running;
			}
		}

		/// <summary>
		/// Debug title
		/// </summary>
		/// <remarks>
		/// Returns the title of the task, its status (RUNNING or WAITING),
		/// its group association (if any), its priority, and the wait time.
		/// </remarks>
		/// <value>
		/// The title of the task with additional information
		/// </value>
		public string DebugTitle
		{
			get
			{
				return String.Format("{4,10} [{1,-20}{2,3}{3,13:f3}{5,23}] {0}", 
					title,
					(group == null ? "" : group),
					priority,
					Wait,
					(running ? "RUNNING" : "WAITING"),
					startafter);
			}
		}

		/// <summary>
		/// Print queue contents to trace
		/// </summary>
		/// <remarks>
		/// This method prints the debug titles of all tasks in the queue to the trace console.
		/// </remarks>
		public static void TraceDumpQueue()
		{
			lock (typeof(BackgroundTask))
			{
				Trace.WriteLine(String.Format("Task queue ({0} entries):", tasks.Count), "BT");
				Trace.Indent();
				foreach (BackgroundTask t in tasks)
					Trace.WriteLine(t.DebugTitle, "BT");
				Trace.Unindent();
			}		
		}

		/// <summary>
		/// Queued tasks
		/// </summary>
		/// <remarks>
		/// Returns an array of debug titles of all tasks currently in the task queue.
		/// </remarks>
		/// <returns>An array of strings</returns>
		public static string[] TaskQueue()
		{
			ArrayList list = new ArrayList();
			lock (typeof(BackgroundTask))
				foreach (BackgroundTask t in tasks)
					list.Add(t.DebugTitle);
			return (string[])list.ToArray(typeof(string));
		}

		private bool BackgroundTask_OnError(BackgroundTask task, object info)
		{
			TransactionLog.Instance.AddException("Background Task failed",
				String.Format("Title: {0}\nPriority: {1}\nGroup: {2}", 
				task.title,
				task.priority,
				task.group), (Exception)info);
			return true;
		}
	}
}
