using System;
using System.Collections.Generic;
using System.Globalization;

namespace System.Reactive.Concurrency
{
	public abstract class VirtualTimeSchedulerBase<T, U> : IScheduler, IServiceProvider, GInterface8 where T : IComparable<T>
	{
		protected VirtualTimeSchedulerBase()
			: this(default(T), Comparer<T>.Default)
		{
		}

		protected VirtualTimeSchedulerBase(T initialClock, IComparer<T> comparer)
		{
			this.Clock = initialClock;
			if (comparer == null)
			{
				throw new ArgumentNullException("comparer");
			}
			this.Comparer = comparer;
		}

		protected abstract T Add(T absolute, U relative);

		protected abstract DateTimeOffset ToDateTimeOffset(T absolute);

		protected abstract U ToRelative(TimeSpan timeSpan);

		public bool IsEnabled { get; private set; }

		protected IComparer<T> Comparer { get; }

		public abstract IDisposable ScheduleAbsolute<TState>(TState state, T dueTime, Func<IScheduler, TState, IDisposable> action);

		public IDisposable ScheduleRelative<TState>(TState state, U dueTime, Func<IScheduler, TState, IDisposable> action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			T t = this.Add(this.Clock, dueTime);
			return this.ScheduleAbsolute<TState>(state, t, action);
		}

		public IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			return this.ScheduleAbsolute<TState>(state, this.Clock, action);
		}

		public IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			return this.ScheduleRelative<TState>(state, this.ToRelative(dueTime), action);
		}

		public IDisposable Schedule<TState>(TState state, DateTimeOffset dueTime, Func<IScheduler, TState, IDisposable> action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			return this.ScheduleRelative<TState>(state, this.ToRelative(dueTime - this.Now), action);
		}

		public void Start()
		{
			if (!this.IsEnabled)
			{
				this.IsEnabled = true;
				do
				{
					IScheduledItem<T> next = this.GetNext();
					if (next != null)
					{
						if (this.Comparer.Compare(next.DueTime, this.Clock) > 0)
						{
							this.Clock = next.DueTime;
						}
						next.Invoke();
					}
					else
					{
						this.IsEnabled = false;
					}
				}
				while (this.IsEnabled);
			}
		}

		public void Stop()
		{
			this.IsEnabled = false;
		}

		public void AdvanceTo(T time)
		{
			int num = this.Comparer.Compare(time, this.Clock);
			if (num < 0)
			{
				throw new ArgumentOutOfRangeException("time");
			}
			if (num == 0)
			{
				return;
			}
			if (!this.IsEnabled)
			{
				this.IsEnabled = true;
				do
				{
					IScheduledItem<T> next = this.GetNext();
					if (next != null && this.Comparer.Compare(next.DueTime, time) <= 0)
					{
						if (this.Comparer.Compare(next.DueTime, this.Clock) > 0)
						{
							this.Clock = next.DueTime;
						}
						next.Invoke();
					}
					else
					{
						this.IsEnabled = false;
					}
				}
				while (this.IsEnabled);
				this.Clock = time;
				return;
			}
			throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.CANT_ADVANCE_WHILE_RUNNING, "AdvanceTo"));
		}

		public void AdvanceBy(U time)
		{
			T t = this.Add(this.Clock, time);
			int num = this.Comparer.Compare(t, this.Clock);
			if (num < 0)
			{
				throw new ArgumentOutOfRangeException("time");
			}
			if (num == 0)
			{
				return;
			}
			if (this.IsEnabled)
			{
				throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.CANT_ADVANCE_WHILE_RUNNING, "AdvanceBy"));
			}
			this.AdvanceTo(t);
		}

		public void Sleep(U time)
		{
			T t = this.Add(this.Clock, time);
			if (this.Comparer.Compare(t, this.Clock) < 0)
			{
				throw new ArgumentOutOfRangeException("time");
			}
			this.Clock = t;
		}

		public T Clock { get; protected set; }

		public DateTimeOffset Now
		{
			get
			{
				return this.ToDateTimeOffset(this.Clock);
			}
		}

		protected abstract IScheduledItem<T> GetNext();

		object IServiceProvider.GetService(Type serviceType)
		{
			return this.GetService(serviceType);
		}

		protected virtual object GetService(Type serviceType)
		{
			if (serviceType == typeof(GInterface8))
			{
				return this;
			}
			return null;
		}

		public IStopwatch StartStopwatch()
		{
			DateTimeOffset start = this.ToDateTimeOffset(this.Clock);
			return new VirtualTimeSchedulerBase<T, U>.VirtualTimeStopwatch(() => this.ToDateTimeOffset(this.Clock) - start);
		}

		private sealed class VirtualTimeStopwatch : IStopwatch
		{
			public VirtualTimeStopwatch(Func<TimeSpan> getElapsed)
			{
				this._getElapsed = getElapsed;
			}

			public TimeSpan Elapsed
			{
				get
				{
					return this._getElapsed();
				}
			}

			private readonly Func<TimeSpan> _getElapsed;
		}
	}
}
