using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;

namespace System.Reactive.Concurrency
{
	public sealed class EventLoopScheduler : LocalScheduler, GInterface7, IDisposable
	{
		public EventLoopScheduler()
			: this((ThreadStart a) => new Thread(a)
			{
				Name = "Event Loop " + Interlocked.Increment(ref EventLoopScheduler._counter).ToString(),
				IsBackground = true
			})
		{
		}

		public EventLoopScheduler(Func<ThreadStart, Thread> threadFactory)
		{
			if (threadFactory == null)
			{
				throw new ArgumentNullException("threadFactory");
			}
			this._threadFactory = threadFactory;
			this._stopwatch = ConcurrencyAbstractionLayer.Current.StartStopwatch();
			this._gate = new object();
			this._evt = new SemaphoreSlim(0);
			this._queue = new SchedulerQueue<TimeSpan>();
			this._readyList = new Queue<ScheduledItem<TimeSpan>>();
			this.ExitIfEmpty = false;
		}

		internal bool ExitIfEmpty { get; set; }

		public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			TimeSpan timeSpan = this._stopwatch.Elapsed + dueTime;
			ScheduledItem<TimeSpan, TState> scheduledItem = new ScheduledItem<TimeSpan, TState>(this, state, action, timeSpan);
			object gate = this._gate;
			lock (gate)
			{
				if (this._disposed)
				{
					throw new ObjectDisposedException("EventLoopScheduler");
				}
				if (dueTime <= TimeSpan.Zero)
				{
					this._readyList.Enqueue(scheduledItem);
					this._evt.Release();
				}
				else
				{
					this._queue.Enqueue(scheduledItem);
					this._evt.Release();
				}
				this.EnsureThread();
			}
			return scheduledItem;
		}

		public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
		{
			if (period < TimeSpan.Zero)
			{
				throw new ArgumentOutOfRangeException("period");
			}
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			return new EventLoopScheduler.PeriodicallyScheduledWorkItem<TState>(this, state, period, action);
		}

		public override IStopwatch StartStopwatch()
		{
			return new StopwatchImpl();
		}

		public void Dispose()
		{
			object gate = this._gate;
			lock (gate)
			{
				if (!this._disposed)
				{
					this._disposed = true;
					Disposable.TryDispose(ref this._nextTimer);
					this._evt.Release();
				}
			}
		}

		private void EnsureThread()
		{
			if (this._thread == null)
			{
				this._thread = this._threadFactory(new ThreadStart(this.Run));
				this._thread.Start();
			}
		}

		private void Run()
		{
			for (;;)
			{
				this._evt.Wait();
				ScheduledItem<TimeSpan>[] array = null;
				object obj = this._gate;
				lock (obj)
				{
					while (this._evt.CurrentCount > 0)
					{
						this._evt.Wait();
					}
					if (this._disposed)
					{
						this._evt.Dispose();
						break;
					}
					while (this._queue.Count > 0 && this._queue.Peek().DueTime <= this._stopwatch.Elapsed)
					{
						ScheduledItem<TimeSpan> scheduledItem = this._queue.Dequeue();
						this._readyList.Enqueue(scheduledItem);
					}
					if (this._queue.Count > 0)
					{
						ScheduledItem<TimeSpan> scheduledItem2 = this._queue.Peek();
						if (scheduledItem2 != this._nextItem)
						{
							this._nextItem = scheduledItem2;
							TimeSpan timeSpan = scheduledItem2.DueTime - this._stopwatch.Elapsed;
							Disposable.TrySetSerial(ref this._nextTimer, ConcurrencyAbstractionLayer.Current.StartTimer(new Action<object>(this.Tick), scheduledItem2, timeSpan));
						}
					}
					if (this._readyList.Count > 0)
					{
						array = this._readyList.ToArray();
						this._readyList.Clear();
					}
				}
				ScheduledItem<TimeSpan>[] array2;
				int num;
				if (array != null)
				{
					array2 = array;
					num = 0;
					goto IL_0144;
				}
				IL_018C:
				if (this.ExitIfEmpty)
				{
					obj = this._gate;
					lock (obj)
					{
						if (this._readyList.Count == 0 && this._queue.Count == 0)
						{
							this._thread = null;
							break;
						}
						continue;
					}
					goto IL_01DA;
				}
				continue;
				IL_0144:
				if (num >= array2.Length)
				{
					goto IL_018C;
				}
				ScheduledItem<TimeSpan> scheduledItem3 = array2[num];
				if (!scheduledItem3.IsCanceled)
				{
					try
					{
						scheduledItem3.Invoke();
						goto IL_01DA;
					}
					catch (ObjectDisposedException ex) when ("EventLoopScheduler".Equals(ex.ObjectName))
					{
						goto IL_01DA;
					}
					goto IL_018C;
				}
				IL_01DA:
				num++;
				goto IL_0144;
			}
		}

		private void Tick(object state)
		{
			object gate = this._gate;
			lock (gate)
			{
				if (!this._disposed)
				{
					ScheduledItem<TimeSpan> scheduledItem = (ScheduledItem<TimeSpan>)state;
					if (scheduledItem == this._nextItem)
					{
						this._nextItem = null;
					}
					if (this._queue.Remove(scheduledItem))
					{
						this._readyList.Enqueue(scheduledItem);
					}
					this._evt.Release();
				}
			}
		}

		private static int _counter;

		private readonly Func<ThreadStart, Thread> _threadFactory;

		private readonly IStopwatch _stopwatch;

		private Thread _thread;

		private readonly object _gate;

		private readonly SemaphoreSlim _evt;

		private readonly SchedulerQueue<TimeSpan> _queue;

		private readonly Queue<ScheduledItem<TimeSpan>> _readyList;

		private ScheduledItem<TimeSpan> _nextItem;

		private IDisposable _nextTimer;

		private bool _disposed;

		private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable
		{
			public PeriodicallyScheduledWorkItem(EventLoopScheduler scheduler, TState state, TimeSpan period, Func<TState, TState> action)
			{
				this._state = state;
				this._period = period;
				this._action = action;
				this._scheduler = scheduler;
				this._next = scheduler._stopwatch.Elapsed + period;
				Disposable.TrySetSingle(ref this._task, scheduler.Schedule<EventLoopScheduler.PeriodicallyScheduledWorkItem<TState>>(this, this._next - scheduler._stopwatch.Elapsed, (IScheduler _, EventLoopScheduler.PeriodicallyScheduledWorkItem<TState> s) => s.Tick(_)));
			}

			private IDisposable Tick(IScheduler self)
			{
				this._next += this._period;
				Disposable.TrySetMultiple(ref this._task, self.Schedule<EventLoopScheduler.PeriodicallyScheduledWorkItem<TState>>(this, this._next - this._scheduler._stopwatch.Elapsed, (IScheduler _, EventLoopScheduler.PeriodicallyScheduledWorkItem<TState> s) => s.Tick(_)));
				this._gate.Wait<EventLoopScheduler.PeriodicallyScheduledWorkItem<TState>>(this, delegate(EventLoopScheduler.PeriodicallyScheduledWorkItem<TState> closureWorkItem)
				{
					closureWorkItem._state = closureWorkItem._action(closureWorkItem._state);
				});
				return Disposable.Empty;
			}

			public void Dispose()
			{
				Disposable.TryDispose(ref this._task);
				this._gate.Dispose();
			}

			private readonly TimeSpan _period;

			private readonly Func<TState, TState> _action;

			private readonly EventLoopScheduler _scheduler;

			private readonly AsyncLock _gate = new AsyncLock();

			private TState _state;

			private TimeSpan _next;

			private IDisposable _task;
		}
	}
}
