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

namespace System.Reactive
{
	internal abstract class TailRecursiveSink<TSource> : IdentitySink<TSource>
	{
		protected TailRecursiveSink(IObserver<TSource> observer)
			: base(observer)
		{
		}

		public void Run(IEnumerable<IObservable<TSource>> sources)
		{
			IEnumerator<IObservable<TSource>> enumerator;
			if (!this.TryGetEnumerator(sources, out enumerator))
			{
				return;
			}
			this._stack.Push(enumerator);
			this.Drain();
		}

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				this.DisposeAll();
			}
			base.Dispose(disposing);
		}

		private void Drain()
		{
			if (Interlocked.Increment(ref this._trampoline) != 1)
			{
				return;
			}
			do
			{
				if (Volatile.Read(ref this._isDisposed))
				{
					while (this._stack.Count != 0)
					{
						this._stack.Pop().Dispose();
					}
					Disposable.TryDispose(ref this._currentSubscription);
				}
				else if (this._stack.Count != 0)
				{
					IEnumerator<IObservable<TSource>> enumerator = this._stack.Peek();
					IObservable<TSource> observable = null;
					IObservable<TSource> observable2 = null;
					try
					{
						if (enumerator.MoveNext())
						{
							observable = enumerator.Current;
						}
					}
					catch (Exception ex)
					{
						enumerator.Dispose();
						base.ForwardOnError(ex);
						Volatile.Write(ref this._isDisposed, true);
						continue;
					}
					try
					{
						observable2 = Helpers.Unpack<TSource>(observable);
					}
					catch (Exception ex2)
					{
						observable2 = null;
						if (!this.Fail(ex2))
						{
							Volatile.Write(ref this._isDisposed, true);
						}
						continue;
					}
					if (observable2 == null)
					{
						this._stack.Pop();
						enumerator.Dispose();
						continue;
					}
					IEnumerable<IObservable<TSource>> enumerable = this.Extract(observable2);
					if (enumerable != null)
					{
						IEnumerator<IObservable<TSource>> enumerator2;
						if (this.TryGetEnumerator(enumerable, out enumerator2))
						{
							this._stack.Push(enumerator2);
							continue;
						}
						Volatile.Write(ref this._isDisposed, true);
						continue;
					}
					else
					{
						IDisposable ready = ReadyToken.Ready;
						if (Disposable.TrySetSingle(ref this._currentSubscription, ready) != TrySetSingleResult.Success)
						{
							continue;
						}
						IDisposable disposable = observable2.SubscribeSafe(this);
						IDisposable disposable2 = Interlocked.CompareExchange<IDisposable>(ref this._currentSubscription, disposable, ready);
						if (disposable2 != ready)
						{
							disposable.Dispose();
							if (disposable2 == BooleanDisposable.True)
							{
								continue;
							}
						}
					}
				}
				else
				{
					Volatile.Write(ref this._isDisposed, true);
					this.Done();
				}
			}
			while (Interlocked.Decrement(ref this._trampoline) != 0);
		}

		private void DisposeAll()
		{
			Volatile.Write(ref this._isDisposed, true);
			this.Drain();
		}

		protected void Recurse()
		{
			if (Disposable.TrySetSerial(ref this._currentSubscription, null))
			{
				this.Drain();
			}
		}

		protected abstract IEnumerable<IObservable<TSource>> Extract(IObservable<TSource> source);

		private bool TryGetEnumerator(IEnumerable<IObservable<TSource>> sources, out IEnumerator<IObservable<TSource>> result)
		{
			bool flag;
			try
			{
				result = sources.GetEnumerator();
				flag = true;
			}
			catch (Exception ex)
			{
				base.ForwardOnError(ex);
				result = null;
				flag = false;
			}
			return flag;
		}

		protected virtual void Done()
		{
			base.ForwardOnCompleted();
		}

		protected virtual bool Fail(Exception error)
		{
			base.ForwardOnError(error);
			return false;
		}

		private bool _isDisposed;

		private int _trampoline;

		private IDisposable _currentSubscription;

		private readonly Stack<IEnumerator<IObservable<TSource>>> _stack = new Stack<IEnumerator<IObservable<TSource>>>();
	}
}
