using System;
using System.Reactive.Disposables;

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

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

		protected override void Run(CombineLatest<TFirst, TSecond, TResult>._ 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._resultSelector = resultSelector;
			}

			public void Run(IObservable<TFirst> first, IObservable<TSecond> second)
			{
				this._gate = new object();
				CombineLatest<TFirst, TSecond, TResult>._.FirstObserver firstObserver = new CombineLatest<TFirst, TSecond, TResult>._.FirstObserver(this);
				CombineLatest<TFirst, TSecond, TResult>._.SecondObserver secondObserver = new CombineLatest<TFirst, TSecond, TResult>._.SecondObserver(this);
				firstObserver.Other = secondObserver;
				secondObserver.Other = firstObserver;
				Disposable.SetSingle(ref this._firstDisposable, first.SubscribeSafe(firstObserver));
				Disposable.SetSingle(ref this._secondDisposable, second.SubscribeSafe(secondObserver));
			}

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

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

			private object _gate;

			private IDisposable _firstDisposable;

			private IDisposable _secondDisposable;

			private sealed class FirstObserver : IObserver<TFirst>
			{
				public FirstObserver(CombineLatest<TFirst, TSecond, TResult>._ parent)
				{
					this._parent = parent;
				}

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

				public bool HasValue { get; private set; }

				public TFirst Value { get; private set; }

				public bool Done { get; private set; }

				public void OnNext(TFirst value)
				{
					object gate = this._parent._gate;
					lock (gate)
					{
						this.HasValue = true;
						this.Value = value;
						if (this._other.HasValue)
						{
							TResult tresult;
							try
							{
								tresult = this._parent._resultSelector(value, this._other.Value);
							}
							catch (Exception ex)
							{
								this._parent.ForwardOnError(ex);
								return;
							}
							this._parent.ForwardOnNext(tresult);
						}
						else if (this._other.Done)
						{
							this._parent.ForwardOnCompleted();
						}
					}
				}

				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);
						}
					}
				}

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

				private CombineLatest<TFirst, TSecond, TResult>._.SecondObserver _other;
			}

			private sealed class SecondObserver : IObserver<TSecond>
			{
				public SecondObserver(CombineLatest<TFirst, TSecond, TResult>._ parent)
				{
					this._parent = parent;
				}

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

				public bool HasValue { get; private set; }

				public TSecond Value { get; private set; }

				public bool Done { get; private set; }

				public void OnNext(TSecond value)
				{
					object gate = this._parent._gate;
					lock (gate)
					{
						this.HasValue = true;
						this.Value = value;
						if (this._other.HasValue)
						{
							TResult tresult;
							try
							{
								tresult = this._parent._resultSelector(this._other.Value, value);
							}
							catch (Exception ex)
							{
								this._parent.ForwardOnError(ex);
								return;
							}
							this._parent.ForwardOnNext(tresult);
						}
						else if (this._other.Done)
						{
							this._parent.ForwardOnCompleted();
						}
					}
				}

				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);
						}
					}
				}

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

				private CombineLatest<TFirst, TSecond, TResult>._.FirstObserver _other;
			}
		}
	}
}
