using System;
using System.Reactive.Disposables;

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

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

		protected override void Run(WithLatestFrom<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();
				this._latestGate = new object();
				WithLatestFrom<TFirst, TSecond, TResult>._.FirstObserver firstObserver = new WithLatestFrom<TFirst, TSecond, TResult>._.FirstObserver(this);
				WithLatestFrom<TFirst, TSecond, TResult>._.SecondObserver secondObserver = new WithLatestFrom<TFirst, TSecond, TResult>._.SecondObserver(this);
				Disposable.SetSingle(ref this._secondDisposable, second.SubscribeSafe(secondObserver));
				base.SetUpstream(first.SubscribeSafe(firstObserver));
			}

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

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

			private object _gate;

			private volatile bool _hasLatest;

			private TSecond _latest;

			private object _latestGate;

			private IDisposable _secondDisposable;

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

				public void OnCompleted()
				{
					object gate = this._parent._gate;
					lock (gate)
					{
						this._parent.ForwardOnCompleted();
					}
				}

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

				public void OnNext(TFirst value)
				{
					if (this._parent._hasLatest)
					{
						object obj = this._parent._latestGate;
						TSecond latest;
						lock (obj)
						{
							latest = this._parent._latest;
						}
						TResult tresult;
						try
						{
							tresult = this._parent._resultSelector(value, latest);
						}
						catch (Exception ex)
						{
							obj = this._parent._gate;
							lock (obj)
							{
								this._parent.ForwardOnError(ex);
							}
							return;
						}
						obj = this._parent._gate;
						lock (obj)
						{
							this._parent.ForwardOnNext(tresult);
						}
					}
				}

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

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

				public void OnCompleted()
				{
					Disposable.TryDispose(ref this._parent._secondDisposable);
				}

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

				public void OnNext(TSecond value)
				{
					object latestGate = this._parent._latestGate;
					lock (latestGate)
					{
						this._parent._latest = value;
					}
					if (!this._parent._hasLatest)
					{
						this._parent._hasLatest = true;
					}
				}

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