// TODO:
//	DispatcherObject returned by BeginInvoke must allow:
//	   * Waiting until delegate is invoked.
//	   See: BeginInvoke documentation for details
//
//	Implement the "Invoke" methods, they are currently not working.
//
//	Add support for disabling the dispatcher and resuming it.
//	Add support for Waiting for new tasks to be pushed, so that we dont busy loop.
//	Add support for aborting an operation (emit the hook.operationaborted too) 
//
// Very confusing information about Shutdown: it states that shutdown is
// not over, until all events are unwinded, and also states that all events
// are aborted at that point.  See 'Dispatcher.InvokeShutdown' docs,
//
// Testing reveals that 
//	 -> InvokeShutdown() stops processing, even if events are available,
//		there is no "unwinding" of events, even of higher priority events,
//		they are just ignored.
//
// The documentation for the Dispatcher family is poorly written, complete
// sections are cut-and-pasted that add no value and the important pieces
// like (what is a frame) is not on the APIs, but scattered everywhere else
//
// -----------------------------------------------------------------------
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2006 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2016 Quamotion (http://quamotion.mobi)
//
// Authors:
//	Miguel de Icaza (miguel@novell.com)
//	Frederik Carlier (frederik.carlier@quamotion.mobi)
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Security;
using System.Threading;

namespace System.Windows.Threading {

	[Flags]
	internal enum Flags {
		ShutdownStarted = 1,
		Shutdown = 2,
		Disabled = 4
	}
	
	public sealed class Dispatcher {
		static Dictionary<Thread, Dispatcher> dispatchers = new Dictionary<Thread, Dispatcher> ();
		static object olock = new object ();
		static DispatcherFrame main_execution_frame = new DispatcherFrame ();
		
		const int TOP_PRIO = (int)DispatcherPriority.Send;
		Thread base_thread;
		PokableQueue [] priority_queues = new PokableQueue [TOP_PRIO+1];

		Flags flags;
		int queue_bits;

		//
		// Used to notify the dispatcher thread that new data is available
		//
		EventWaitHandle wait;

		//
		// The hooks for this Dispatcher
		//
		DispatcherHooks hooks;

		//
		// The current DispatcherFrame active in a given Dispatcher, we use this to
		// keep a linked list of all active frames, so we can "ExitAll" frames when
		// requested
		DispatcherFrame current_frame;
		
		Dispatcher (Thread t)
		{
			base_thread = t;
			for (int i = 1; i <= (int) DispatcherPriority.Send; i++)
				priority_queues [i] = new PokableQueue ();
			wait = new EventWaitHandle (false, EventResetMode.AutoReset);
			hooks = new DispatcherHooks (this);
		}

		[EditorBrowsable (EditorBrowsableState.Never)]
		public bool CheckAccess ()
		{
			return Thread.CurrentThread == base_thread;
		}

		[EditorBrowsable (EditorBrowsableState.Never)]
		public void VerifyAccess ()
		{
			if (Thread.CurrentThread != base_thread)
				throw new InvalidOperationException ("Invoked from a different thread");
		}

		public static void ValidatePriority (DispatcherPriority priority, string parameterName)
		{
			if (priority < DispatcherPriority.Inactive || priority > DispatcherPriority.Send)
				throw new InvalidEnumArgumentException (parameterName);
		}

		public DispatcherOperation BeginInvoke (Delegate method, params object[] args)
		{
			throw new NotImplementedException ();
		}
		
		public DispatcherOperation BeginInvoke (Delegate method, DispatcherPriority priority, params object[] args)
		{
			throw new NotImplementedException ();
		}

		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method)
		{
			if (priority < 0 || priority > DispatcherPriority.Send)
				throw new InvalidEnumArgumentException ("priority");
			if (priority == DispatcherPriority.Inactive)
				throw new ArgumentException ("priority can not be inactive", "priority");
			if (method == null)
				throw new ArgumentNullException ("method");

			DispatcherOperation op = new DispatcherOperation (this, priority, method);
			Queue (priority, op);

			return op;
		}

		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method, object arg)
		{
			if (priority < 0 || priority > DispatcherPriority.Send)
				throw new InvalidEnumArgumentException ("priority");
			if (priority == DispatcherPriority.Inactive)
				throw new ArgumentException ("priority can not be inactive", "priority");
			if (method == null)
				throw new ArgumentNullException ("method");

			DispatcherOperation op = new DispatcherOperation (this, priority, method, arg);
			
			Queue (priority, op);
			
			return op;
		}
		
		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method, object arg, params object [] args)
		{
			if (priority < 0 || priority > DispatcherPriority.Send)
				throw new InvalidEnumArgumentException ("priority");
			if (priority == DispatcherPriority.Inactive)
				throw new ArgumentException ("priority can not be inactive", "priority");
			if (method == null)
				throw new ArgumentNullException ("method");

			DispatcherOperation op = new DispatcherOperation (this, priority, method, arg, args);
			Queue (priority, op);

			return op;
		}

		public DispatcherOperation InvokeAsync (Action callback)
		{
			return this.BeginInvoke(callback);
		}

		public DispatcherOperation InvokeAsync (Action callback, DispatcherPriority priority)
		{
			return this.BeginInvoke(callback, priority);
		}

		public DispatcherOperation InvokeAsync (Action callback, DispatcherPriority priority, CancellationToken cancellationToken)
		{
			return this.BeginInvoke(callback, priority);
		}

		public object Invoke (Delegate method, params object[] args)
		{
			throw new NotImplementedException ();
		}

		public object Invoke (Delegate method, TimeSpan timeout, params object[] args)
		{
			throw new NotImplementedException ();
		}

		public object Invoke (Delegate method, TimeSpan timeout, DispatcherPriority priority, params object[] args)
		{
			throw new NotImplementedException ();
		}

		public object Invoke (Delegate method, DispatcherPriority priority, params object[] args)
		{
			throw new NotImplementedException ();
		}

		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		public object Invoke (DispatcherPriority priority, Delegate method)
		{
			if (priority < 0 || priority > DispatcherPriority.Send)
				throw new InvalidEnumArgumentException ("priority");
			if (priority == DispatcherPriority.Inactive)
				throw new ArgumentException ("priority can not be inactive", "priority");
			if (method == null)
				throw new ArgumentNullException ("method");

			DispatcherOperation op = new DispatcherOperation (this, priority, method);
			Queue (priority, op);
			PushFrame (new DispatcherFrame ());
			
			throw new NotImplementedException ();
		}

		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		public object Invoke (DispatcherPriority priority, Delegate method, object arg)
		{
			if (priority < 0 || priority > DispatcherPriority.Send)
				throw new InvalidEnumArgumentException ("priority");
			if (priority == DispatcherPriority.Inactive)
				throw new ArgumentException ("priority can not be inactive", "priority");
			if (method == null)
				throw new ArgumentNullException ("method");

			Queue (priority, new DispatcherOperation (this, priority, method, arg));
			throw new NotImplementedException ();
		}
		
		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		public object Invoke (DispatcherPriority priority, Delegate method, object arg, params object [] args)
		{
			if (priority < 0 || priority > DispatcherPriority.Send)
				throw new InvalidEnumArgumentException ("priority");
			if (priority == DispatcherPriority.Inactive)
				throw new ArgumentException ("priority can not be inactive", "priority");
			if (method == null)
				throw new ArgumentNullException ("method");

			Queue (priority, new DispatcherOperation (this, priority, method, arg, args));

			throw new NotImplementedException ();
		}

		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method)
		{
			throw new NotImplementedException ();
		}

		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg)
		{
			throw new NotImplementedException ();
		}

		[Browsable (false)]
		[EditorBrowsable (EditorBrowsableState.Never)]
		public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg, params object [] args)
		{
			throw new NotImplementedException ();
		}

		void Queue (DispatcherPriority priority, DispatcherOperation x)
		{
			int p = ((int) priority);
			PokableQueue q = priority_queues [p];
			
			lock (q){
				int flag = 1 << p;
				q.Enqueue (x);
				queue_bits |= flag;
			}
			hooks.EmitOperationPosted (x);

			if (Thread.CurrentThread != base_thread)
				wait.Set ();
		}

		internal void Reprioritize (DispatcherOperation op, DispatcherPriority oldpriority)
		{
			int oldp = (int) oldpriority;
			PokableQueue q = priority_queues [oldp];

			lock (q){
				q.Remove (op);
			}
			Queue (op.Priority, op);
			hooks.EmitOperationPriorityChanged (op);
		}
		
		public static Dispatcher CurrentDispatcher {
			get {
				lock (olock){
					Thread t = Thread.CurrentThread;
					Dispatcher dis = FromThread (t);

					if (dis != null)
						return dis;
				
					dis = new Dispatcher (t);
					dispatchers [t] = dis;
					return dis;
				}
			}
		}

		public static Dispatcher FromThread (Thread thread)
		{
			Dispatcher dis;
			
			if (dispatchers.TryGetValue (thread, out dis))
				return dis;

			return null;
		}

		public Thread Thread {
			get {
				return base_thread;
			}
		}

		[SecurityCritical]
		public static void Run ()
		{
			// Set Continue, because the previous run could clean
			// this flag by Dispatcher.ExitAllFrames.
			main_execution_frame.Continue = true;
			PushFrame (main_execution_frame);
		}
		
		[SecurityCritical]
		public static void PushFrame (DispatcherFrame frame)
		{
			if (frame == null)
				throw new ArgumentNullException ("frame");

			Dispatcher dis = CurrentDispatcher;

			if (dis.HasShutdownFinished)
				throw new InvalidOperationException ("The Dispatcher has shut down");
			if (frame.dispatcher != null)
				throw new InvalidOperationException ("Frame is already running on a different dispatcher");
			if ((dis.flags & Flags.Disabled) != 0)
				throw new InvalidOperationException ("Dispatcher processing has been disabled");

			frame.ParentFrame = dis.current_frame;
			dis.current_frame = frame;
			
			frame.dispatcher = dis;

			dis.RunFrame (frame);

			frame.dispatcher = null;
			dis.current_frame = frame.ParentFrame;
			frame.ParentFrame = null;
		}

		void PerformShutdown ()
		{
			EventHandler h;
			
			h = ShutdownStarted;
			if (h != null)
				h (this, new EventArgs ());
			
			flags |= Flags.Shutdown;
			
			h = ShutdownFinished;
			if (h != null)
				h (this, new EventArgs ());

			priority_queues = null;
			wait = null;
		}
		
		void RunFrame (DispatcherFrame frame)
		{
			while (frame.Continue) {
				while (queue_bits != 0){
					for (int i = TOP_PRIO; i > 0 && queue_bits != 0; i--){
						int current_bit = queue_bits & (1 << i);
						if (current_bit != 0){
							PokableQueue q = priority_queues [i];

							do {
								DispatcherOperation task;
								
								lock (q){
									// if we are done with this queue, leave.
									if (q.Count == 0){
										queue_bits &= ~current_bit;
										break;
									}
									task = (DispatcherOperation) q.Dequeue ();
								}
								
								task.Invoke ();

								//
								// call hooks.
								//
								if (task.Status == DispatcherOperationStatus.Aborted)
									hooks.EmitOperationAborted (task);
								else
									hooks.EmitOperationCompleted (task);

								if (!frame.Continue)
									return;
								
								if (HasShutdownStarted){
									PerformShutdown ();
									return;
								}

								//
								// If a higher-priority task comes in, go do that
								//
								if (current_bit < (queue_bits & ~current_bit))
								{
									i = TOP_PRIO + 1; // for-loop decreases by one
									break;
								}
							} while (true);
						}
					}
				}
				hooks.EmitInactive ();
				
				wait.WaitOne ();
				wait.Reset ();
			}
		}

		[EditorBrowsable (EditorBrowsableState.Advanced)]
		public DispatcherHooks Hooks {
			[SecurityCritical]
			get { throw new NotImplementedException (); }
		}

		public bool HasShutdownStarted {
			get {
				return (flags & Flags.ShutdownStarted) != 0;
			}
		}

		public bool HasShutdownFinished {
			get {
				return (flags & Flags.Shutdown) != 0;
			}
		}

		//
		// Do no work here, so that any events are thrown on the owning thread
		//
		[SecurityCritical]
		public void InvokeShutdown ()
		{
			flags |= Flags.ShutdownStarted;
		}

		[SecurityCritical]
		public void BeginInvokeShutdown (DispatcherPriority priority)
		{
			throw new NotImplementedException ();
		}

		[SecurityCritical]
		public static void ExitAllFrames ()
		{
			Dispatcher dis = CurrentDispatcher;
			
			for (DispatcherFrame frame = dis.current_frame; frame != null; frame = frame.ParentFrame){
				if (frame.exit_on_request)
					frame.Continue = false;
				else {
					//
					// Stop unwinding the frames at the first frame that is
					// long running
					break;
				}
			}
		}

		public DispatcherProcessingDisabled DisableProcessing ()
		{
			throw new NotImplementedException ();
		}

		public event EventHandler ShutdownStarted;
		public event EventHandler ShutdownFinished;
		public event DispatcherUnhandledExceptionEventHandler UnhandledException;
		public event DispatcherUnhandledExceptionFilterEventHandler UnhandledExceptionFilter;
	}

	internal class PokableQueue {
		const int initial_capacity = 32;

		int size, head, tail;
		object [] array;

		internal PokableQueue (int capacity) 
		{
			array = new object [capacity];
		}

		internal PokableQueue () : this (initial_capacity)
		{
		}

		public void Enqueue (object obj)
		{
			if (size == array.Length)
					Grow ();
			array[tail] = obj;
			tail = (tail+1) % array.Length;
			size++;
		}

		public object Dequeue ()
		{
			if (size < 1)
					throw new InvalidOperationException ();
			object result = array[head];
			array [head] = null;
			head = (head + 1) % array.Length;
			size--;
			return result;
		}

		void Grow () 
		{
			int newc = array.Length * 2;
			object[] new_contents = new object[newc];
			array.CopyTo (new_contents, 0);
			array = new_contents;
			head = 0;
			tail = head + size;
		}

		public int Count {
			get {
				return size;
			}
		}

		public void Remove (object obj)
		{
			for (int i = 0; i < size; i++){
				if (array [(head+i) % array.Length] == obj){
					for (int j = i; j < size-i; j++)
						array [(head +j) % array.Length] = array [(head+j+1) % array.Length];
					size--;
					if (size < 0)
						size = array.Length-1;
					tail--;
				}
			}
		}
	}
}
