using System;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Threading;

namespace System.Reactive.Linq.ObservableImpl
{
	internal sealed class RetryWhen<T, U> : IObservable<T>
	{
		internal RetryWhen(IObservable<T> source, Func<IObservable<Exception>, IObservable<U>> handler)
		{
			this._source = source;
			this._handler = handler;
		}

		public IDisposable Subscribe(IObserver<T> observer)
		{
			if (observer == null)
			{
				throw new ArgumentNullException("observer");
			}
			Subject<Exception> subject = new Subject<Exception>();
			IObservable<U> observable = null;
			IDisposable empty;
			try
			{
				observable = this._handler(subject);
				if (observable == null)
				{
					throw new NullReferenceException("The handler returned a null IObservable");
				}
				goto IL_0049;
			}
			catch (Exception ex)
			{
				observer.OnError(ex);
				empty = Disposable.Empty;
			}
			return empty;
			IL_0049:
			RetryWhen<T, U>.MainObserver mainObserver = new RetryWhen<T, U>.MainObserver(observer, this._source, new RedoSerializedObserver<Exception>(subject));
			IDisposable disposable = observable.SubscribeSafe(mainObserver.HandlerConsumer);
			Disposable.SetSingle(ref mainObserver.HandlerUpstream, disposable);
			mainObserver.HandlerNext();
			return mainObserver;
		}

		private readonly IObservable<T> _source;

		private readonly Func<IObservable<Exception>, IObservable<U>> _handler;

		private sealed class MainObserver : Sink<T>, IObserver<T>
		{
			internal MainObserver(IObserver<T> downstream, IObservable<T> source, IObserver<Exception> errorSignal)
				: base(downstream)
			{
				this._source = source;
				this._errorSignal = errorSignal;
				this.HandlerConsumer = new RetryWhen<T, U>.MainObserver.HandlerObserver(this);
			}

			protected override void Dispose(bool disposing)
			{
				if (disposing)
				{
					Disposable.TryDispose(ref this._upstream);
					Disposable.TryDispose(ref this.HandlerUpstream);
				}
				base.Dispose(disposing);
			}

			public void OnCompleted()
			{
				HalfSerializer.ForwardOnCompleted<T>(this, ref this._halfSerializer, ref this._error);
			}

			public void OnError(Exception error)
			{
				if (Disposable.TrySetSerial(ref this._upstream, null))
				{
					this._errorSignal.OnNext(error);
				}
			}

			public void OnNext(T value)
			{
				HalfSerializer.ForwardOnNext<T>(this, value, ref this._halfSerializer, ref this._error);
			}

			private void HandlerError(Exception error)
			{
				HalfSerializer.ForwardOnError<T>(this, error, ref this._halfSerializer, ref this._error);
			}

			private void HandlerComplete()
			{
				HalfSerializer.ForwardOnCompleted<T>(this, ref this._halfSerializer, ref this._error);
			}

			internal void HandlerNext()
			{
				if (Interlocked.Increment(ref this._trampoline) == 1)
				{
					for (;;)
					{
						SingleAssignmentDisposable singleAssignmentDisposable = new SingleAssignmentDisposable();
						if (Disposable.TrySetSingle(ref this._upstream, singleAssignmentDisposable) != TrySetSingleResult.Success)
						{
							break;
						}
						singleAssignmentDisposable.Disposable = this._source.SubscribeSafe(this);
						if (Interlocked.Decrement(ref this._trampoline) == 0)
						{
							return;
						}
					}
					return;
				}
			}

			private readonly IObserver<Exception> _errorSignal;

			internal readonly RetryWhen<T, U>.MainObserver.HandlerObserver HandlerConsumer;

			private readonly IObservable<T> _source;

			private IDisposable _upstream;

			internal IDisposable HandlerUpstream;

			private int _trampoline;

			private int _halfSerializer;

			private Exception _error;

			internal sealed class HandlerObserver : IObserver<U>
			{
				internal HandlerObserver(RetryWhen<T, U>.MainObserver main)
				{
					this._main = main;
				}

				public void OnCompleted()
				{
					this._main.HandlerComplete();
				}

				public void OnError(Exception error)
				{
					this._main.HandlerError(error);
				}

				public void OnNext(U value)
				{
					this._main.HandlerNext();
				}

				private readonly RetryWhen<T, U>.MainObserver _main;
			}
		}
	}
}
