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

namespace System.Reactive.Linq.ObservableImpl
{
	internal static class Zip<TFirst, TSecond, TResult>
	{
		internal sealed class Observable : Producer<TResult, Zip<TFirst, TSecond, TResult>.Observable._>
		{
			public Observable(IObservable<TFirst> first, IObservable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
			{
				this._first = first;
				this._second = second;
				this._resultSelector = resultSelector;
			}

			protected override Zip<TFirst, TSecond, TResult>.Observable._ CreateSink(IObserver<TResult> observer)
			{
				return new Zip<TFirst, TSecond, TResult>.Observable._(this._resultSelector, observer);
			}

			protected override void Run(Zip<TFirst, TSecond, TResult>.Observable._ sink)
			{
				sink.Run(this._first, this._second);
			}

			private readonly IObservable<TFirst> _first;

			private readonly IObservable<TSecond> _second;

			private readonly Func<TFirst, TSecond, TResult> _resultSelector;

			internal sealed class _ : IdentitySink<TResult>
			{
				public _(Func<TFirst, TSecond, TResult> resultSelector, IObserver<TResult> observer)
					: base(observer)
				{
					this._gate = new object();
					this._firstObserver = new Zip<TFirst, TSecond, TResult>.Observable._.FirstObserver(this);
					this._secondObserver = new Zip<TFirst, TSecond, TResult>.Observable._.SecondObserver(this);
					this._firstObserver.Other = this._secondObserver;
					this._secondObserver.Other = this._firstObserver;
					this._resultSelector = resultSelector;
				}

				public void Run(IObservable<TFirst> first, IObservable<TSecond> second)
				{
					Disposable.SetSingle(ref this._firstDisposable, first.SubscribeSafe(this._firstObserver));
					Disposable.SetSingle(ref this._secondDisposable, second.SubscribeSafe(this._secondObserver));
				}

				protected override void Dispose(bool disposing)
				{
					if (disposing)
					{
						Disposable.TryDispose(ref this._firstDisposable);
						Disposable.TryDispose(ref this._secondDisposable);
						object gate = this._gate;
						lock (gate)
						{
							this._firstObserver.Dispose();
							this._secondObserver.Dispose();
						}
					}
					base.Dispose(disposing);
				}

				private readonly Func<TFirst, TSecond, TResult> _resultSelector;

				private readonly object _gate;

				private readonly Zip<TFirst, TSecond, TResult>.Observable._.FirstObserver _firstObserver;

				private IDisposable _firstDisposable;

				private readonly Zip<TFirst, TSecond, TResult>.Observable._.SecondObserver _secondObserver;

				private IDisposable _secondDisposable;

				private sealed class FirstObserver : IObserver<TFirst>, IDisposable
				{
					public FirstObserver(Zip<TFirst, TSecond, TResult>.Observable._ parent)
					{
						this._parent = parent;
						this._queue = new Queue<TFirst>();
					}

					public Zip<TFirst, TSecond, TResult>.Observable._.SecondObserver Other
					{
						set
						{
							this._other = value;
						}
					}

					public Queue<TFirst> Queue
					{
						get
						{
							return this._queue;
						}
					}

					public bool Done { get; private set; }

					public void OnNext(TFirst value)
					{
						object gate = this._parent._gate;
						lock (gate)
						{
							if (this._other.Queue.Count > 0)
							{
								TSecond tsecond = this._other.Queue.Dequeue();
								TResult tresult;
								try
								{
									tresult = this._parent._resultSelector(value, tsecond);
								}
								catch (Exception ex)
								{
									this._parent.ForwardOnError(ex);
									return;
								}
								this._parent.ForwardOnNext(tresult);
							}
							else if (this._other.Done)
							{
								this._parent.ForwardOnCompleted();
							}
							else
							{
								this._queue.Enqueue(value);
							}
						}
					}

					public void OnError(Exception error)
					{
						object gate = this._parent._gate;
						lock (gate)
						{
							this._parent.ForwardOnError(error);
						}
					}

					public void OnCompleted()
					{
						object gate = this._parent._gate;
						lock (gate)
						{
							this.Done = true;
							if (this._other.Done)
							{
								this._parent.ForwardOnCompleted();
							}
							else
							{
								Disposable.TryDispose(ref this._parent._firstDisposable);
							}
						}
					}

					public void Dispose()
					{
						this._queue.Clear();
					}

					private readonly Zip<TFirst, TSecond, TResult>.Observable._ _parent;

					private Zip<TFirst, TSecond, TResult>.Observable._.SecondObserver _other;

					private readonly Queue<TFirst> _queue;
				}

				private sealed class SecondObserver : IObserver<TSecond>, IDisposable
				{
					public SecondObserver(Zip<TFirst, TSecond, TResult>.Observable._ parent)
					{
						this._parent = parent;
						this._queue = new Queue<TSecond>();
					}

					public Zip<TFirst, TSecond, TResult>.Observable._.FirstObserver Other
					{
						set
						{
							this._other = value;
						}
					}

					public Queue<TSecond> Queue
					{
						get
						{
							return this._queue;
						}
					}

					public bool Done { get; private set; }

					public void OnNext(TSecond value)
					{
						object gate = this._parent._gate;
						lock (gate)
						{
							if (this._other.Queue.Count > 0)
							{
								TFirst tfirst = this._other.Queue.Dequeue();
								TResult tresult;
								try
								{
									tresult = this._parent._resultSelector(tfirst, value);
								}
								catch (Exception ex)
								{
									this._parent.ForwardOnError(ex);
									return;
								}
								this._parent.ForwardOnNext(tresult);
							}
							else if (this._other.Done)
							{
								this._parent.ForwardOnCompleted();
							}
							else
							{
								this._queue.Enqueue(value);
							}
						}
					}

					public void OnError(Exception error)
					{
						object gate = this._parent._gate;
						lock (gate)
						{
							this._parent.ForwardOnError(error);
						}
					}

					public void OnCompleted()
					{
						object gate = this._parent._gate;
						lock (gate)
						{
							this.Done = true;
							if (this._other.Done)
							{
								this._parent.ForwardOnCompleted();
							}
							else
							{
								Disposable.TryDispose(ref this._parent._secondDisposable);
							}
						}
					}

					public void Dispose()
					{
						this._queue.Clear();
					}

					private readonly Zip<TFirst, TSecond, TResult>.Observable._ _parent;

					private Zip<TFirst, TSecond, TResult>.Observable._.FirstObserver _other;

					private readonly Queue<TSecond> _queue;
				}
			}
		}

		internal sealed class Enumerable : Producer<TResult, Zip<TFirst, TSecond, TResult>.Enumerable._>
		{
			public Enumerable(IObservable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
			{
				this._first = first;
				this._second = second;
				this._resultSelector = resultSelector;
			}

			protected override Zip<TFirst, TSecond, TResult>.Enumerable._ CreateSink(IObserver<TResult> observer)
			{
				return new Zip<TFirst, TSecond, TResult>.Enumerable._(this._resultSelector, observer);
			}

			protected override void Run(Zip<TFirst, TSecond, TResult>.Enumerable._ sink)
			{
				sink.Run(this._first, this._second);
			}

			private readonly IObservable<TFirst> _first;

			private readonly IEnumerable<TSecond> _second;

			private readonly Func<TFirst, TSecond, TResult> _resultSelector;

			internal sealed class _ : Sink<TFirst, TResult>
			{
				public _(Func<TFirst, TSecond, TResult> resultSelector, IObserver<TResult> observer)
					: base(observer)
				{
					this._resultSelector = resultSelector;
				}

				private static IEnumerator<TSecond> MakeDisposedEnumerator()
				{
					yield break;
				}

				public void Run(IObservable<TFirst> first, IEnumerable<TSecond> second)
				{
					try
					{
						IEnumerator<TSecond> enumerator = second.GetEnumerator();
						if (Interlocked.CompareExchange<IEnumerator<TSecond>>(ref this._rightEnumerator, enumerator, null) != null)
						{
							enumerator.Dispose();
							return;
						}
					}
					catch (Exception ex)
					{
						base.ForwardOnError(ex);
						return;
					}
					this.Run(first);
				}

				protected override void Dispose(bool disposing)
				{
					if (disposing && Interlocked.Increment(ref this._enumerationInProgress) == 1)
					{
						IEnumerator<TSecond> enumerator = Interlocked.Exchange<IEnumerator<TSecond>>(ref this._rightEnumerator, Zip<TFirst, TSecond, TResult>.Enumerable._.DisposedEnumerator);
						if (enumerator != null)
						{
							enumerator.Dispose();
						}
					}
					base.Dispose(disposing);
				}

				public override void OnNext(TFirst value)
				{
					IEnumerator<TSecond> enumerator = Volatile.Read<IEnumerator<TSecond>>(ref this._rightEnumerator);
					if (enumerator == Zip<TFirst, TSecond, TResult>.Enumerable._.DisposedEnumerator)
					{
						return;
					}
					if (Interlocked.Increment(ref this._enumerationInProgress) != 1)
					{
						return;
					}
					TSecond tsecond = default(TSecond);
					bool flag = false;
					bool flag2;
					try
					{
						try
						{
							if (flag2 = enumerator.MoveNext())
							{
								tsecond = enumerator.Current;
							}
						}
						finally
						{
							if (Interlocked.Decrement(ref this._enumerationInProgress) != 0)
							{
								IEnumerator<TSecond> enumerator2 = Interlocked.Exchange<IEnumerator<TSecond>>(ref this._rightEnumerator, Zip<TFirst, TSecond, TResult>.Enumerable._.DisposedEnumerator);
								if (enumerator2 != null)
								{
									enumerator2.Dispose();
								}
								flag = true;
							}
						}
					}
					catch (Exception ex)
					{
						base.ForwardOnError(ex);
						return;
					}
					if (flag)
					{
						return;
					}
					if (flag2)
					{
						TResult tresult;
						try
						{
							tresult = this._resultSelector(value, tsecond);
						}
						catch (Exception ex2)
						{
							base.ForwardOnError(ex2);
							return;
						}
						base.ForwardOnNext(tresult);
						return;
					}
					base.ForwardOnCompleted();
				}

				private readonly Func<TFirst, TSecond, TResult> _resultSelector;

				private int _enumerationInProgress;

				private IEnumerator<TSecond> _rightEnumerator;

				private static readonly IEnumerator<TSecond> DisposedEnumerator = Zip<TFirst, TSecond, TResult>.Enumerable._.MakeDisposedEnumerator();
			}
		}
	}
}
