using CPF.Mac.Foundation;
using CPF.Mac.ObjCRuntime;
using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace CPF.Mac.CoreFoundation
{
	public class DispatchQueue : DispatchObject
	{
		internal delegate void dispatch_callback_t(IntPtr context);

		private static IntPtr main_q;

		private static object lockobj = new object();

		internal static readonly dispatch_callback_t static_dispatch = static_dispatcher_to_managed;

		public string Label
		{
			get
			{
				if (handle == IntPtr.Zero)
				{
					throw new ObjectDisposedException("DispatchQueue");
				}
				return Marshal.PtrToStringAnsi(dispatch_queue_get_label(handle));
			}
		}

		public IntPtr Context
		{
			get
			{
				Check();
				return dispatch_get_context(handle);
			}
			set
			{
				Check();
				dispatch_set_context(handle, value);
			}
		}

		[Obsolete("Deprecated in iOS 6.0")]
		public static DispatchQueue CurrentQueue => new DispatchQueue(dispatch_get_current_queue(), owns: false);

		public static DispatchQueue DefaultGlobalQueue => new DispatchQueue(dispatch_get_global_queue((IntPtr)0, IntPtr.Zero), owns: false);

		public static DispatchQueue MainQueue
		{
			get
			{
				lock (lockobj)
				{
					if (main_q == IntPtr.Zero)
					{
						main_q = Dlfcn.dlsym((IntPtr)(-2), "_dispatch_main_q");
						if (main_q == IntPtr.Zero)
						{
							IntPtr handle = Dlfcn.dlopen("/usr/lib/libSystem.dylib", 0);
							main_q = Dlfcn.GetIndirect(handle, "_dispatch_main_q");
							Dlfcn.dlclose(handle);
						}
					}
				}
				if (main_q == IntPtr.Zero)
				{
					return PInvokeDispatchGetMainQueue();
				}
				return new DispatchQueue(main_q, owns: false);
			}
		}

		[Preserve(Conditional = true)]
		internal DispatchQueue(IntPtr handle, bool owns)
			: base(handle, owns)
		{
		}

		public DispatchQueue(IntPtr handle)
			: base(handle, owns: false)
		{
		}

		public DispatchQueue(string label)
		{
			handle = dispatch_queue_create(label, IntPtr.Zero);
			if (handle == IntPtr.Zero)
			{
				throw new Exception("Error creating dispatch queue");
			}
		}

		[DllImport("libc")]
		private static extern void dispatch_suspend(IntPtr o);

		public void Suspend()
		{
			Check();
			dispatch_suspend(handle);
		}

		[DllImport("libc")]
		private static extern void dispatch_resume(IntPtr o);

		public void Resume()
		{
			Check();
			dispatch_resume(handle);
		}

		[DllImport("libc")]
		private static extern IntPtr dispatch_get_context(IntPtr o);

		[DllImport("libc")]
		private static extern void dispatch_set_context(IntPtr o, IntPtr ctx);

		public static DispatchQueue GetGlobalQueue(DispatchQueuePriority priority)
		{
			return new DispatchQueue(dispatch_get_global_queue((IntPtr)(int)priority, IntPtr.Zero), owns: false);
		}

		private static DispatchQueue PInvokeDispatchGetMainQueue()
		{
			return new DispatchQueue(dispatch_get_main_queue(), owns: false);
		}

		[MonoPInvokeCallback(typeof(dispatch_callback_t))]
		private static void static_dispatcher_to_managed(IntPtr context)
		{
			GCHandle gCHandle = GCHandle.FromIntPtr(context);
			Tuple<NSAction, DispatchQueue> tuple = gCHandle.Target as Tuple<NSAction, DispatchQueue>;
			if (tuple != null)
			{
				SynchronizationContext current = SynchronizationContext.Current;
				if (current == null)
				{
					SynchronizationContext.SetSynchronizationContext(new DispatchQueueSynchronizationContext(tuple.Item2));
				}
				try
				{
					tuple.Item1();
				}
				catch
				{
					gCHandle.Free();
					throw;
				}
				finally
				{
					if (current == null)
					{
						SynchronizationContext.SetSynchronizationContext(null);
					}
				}
			}
			gCHandle.Free();
		}

		public void DispatchAsync(NSAction action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			dispatch_async_f(handle, (IntPtr)GCHandle.Alloc(Tuple.Create(action, this)), static_dispatch);
		}

		public void DispatchSync(NSAction action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			dispatch_sync_f(handle, (IntPtr)GCHandle.Alloc(Tuple.Create(action, this)), static_dispatch);
		}

		[DllImport("libc")]
		private static extern IntPtr dispatch_queue_create(string label, IntPtr attr);

		[DllImport("libc")]
		private static extern void dispatch_async_f(IntPtr queue, IntPtr context, dispatch_callback_t dispatch);

		[DllImport("libc")]
		private static extern void dispatch_sync_f(IntPtr queue, IntPtr context, dispatch_callback_t dispatch);

		[DllImport("libc")]
		private static extern IntPtr dispatch_get_current_queue();

		[DllImport("libc")]
		private static extern IntPtr dispatch_get_global_queue(IntPtr priority, IntPtr flags);

		[DllImport("libc")]
		private static extern IntPtr dispatch_get_main_queue();

		[DllImport("libc")]
		private static extern IntPtr dispatch_queue_get_label(IntPtr queue);

		[DllImport("libc")]
		private static extern IntPtr dispatch_main();

		public static void MainIteration()
		{
			dispatch_main();
		}
	}
}
