using System;
using System.Runtime.CompilerServices;

namespace System.Reactive.Concurrency
{
	internal abstract class SchedulerWrapper : IScheduler, IServiceProvider
	{
		protected SchedulerWrapper(IScheduler scheduler)
		{
			this._scheduler = scheduler;
			this._cache = new ConditionalWeakTable<IScheduler, IScheduler>();
		}

		protected SchedulerWrapper(IScheduler scheduler, ConditionalWeakTable<IScheduler, IScheduler> cache)
		{
			this._scheduler = scheduler;
			this._cache = cache;
		}

		public DateTimeOffset Now
		{
			get
			{
				return this._scheduler.Now;
			}
		}

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

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

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

		protected virtual Func<IScheduler, TState, IDisposable> Wrap<TState>(Func<IScheduler, TState, IDisposable> action)
		{
			return (IScheduler self, TState state) => action(this.GetRecursiveWrapper(self), state);
		}

		protected IScheduler GetRecursiveWrapper(IScheduler scheduler)
		{
			return this._cache.GetValue(scheduler, (IScheduler s) => this.Clone(s, this._cache));
		}

		protected abstract SchedulerWrapper Clone(IScheduler scheduler, ConditionalWeakTable<IScheduler, IScheduler> cache);

		public object GetService(Type serviceType)
		{
			IServiceProvider serviceProvider = this._scheduler as IServiceProvider;
			if (serviceProvider == null)
			{
				return null;
			}
			object obj;
			if (this.TryGetService(serviceProvider, serviceType, out obj))
			{
				return obj;
			}
			return serviceProvider.GetService(serviceType);
		}

		protected abstract bool TryGetService(IServiceProvider provider, Type serviceType, out object service);

		protected readonly IScheduler _scheduler;

		private readonly ConditionalWeakTable<IScheduler, IScheduler> _cache;
	}
}
