using System;
using System.Reactive.Concurrency;
using System.Reflection;
using System.Threading;

namespace System.Reactive.Linq.ObservableImpl
{
	internal sealed class FromEventPattern
	{
		public sealed class Impl<T, U> : ClassicEventProducer<T, EventPattern<U>>
		{
			public Impl(Action<T> addHandler, Action<T> removeHandler, IScheduler scheduler)
				: base(addHandler, removeHandler, scheduler)
			{
			}

			public Impl(Func<EventHandler<U>, T> conversion, Action<T> addHandler, Action<T> removeHandler, IScheduler scheduler)
				: base(addHandler, removeHandler, scheduler)
			{
				this._conversion = conversion;
			}

			protected override T GetHandler(Action<EventPattern<U>> onNext)
			{
				T t;
				if (this._conversion == null)
				{
					t = ReflectionUtils.CreateDelegate<T>(new Action<object, U>(delegate(object sender, U eventArgs)
					{
						onNext(new EventPattern<U>(sender, eventArgs));
					}), typeof(Action<object, U>).GetMethod("Invoke"));
				}
				else
				{
					t = this._conversion(delegate(object sender, U eventArgs)
					{
						onNext(new EventPattern<U>(sender, eventArgs));
					});
				}
				return t;
			}

			private readonly Func<EventHandler<U>, T> _conversion;
		}

		public sealed class Impl<T, TSender, U> : ClassicEventProducer<T, EventPattern<TSender, U>>
		{
			public Impl(Action<T> addHandler, Action<T> removeHandler, IScheduler scheduler)
				: base(addHandler, removeHandler, scheduler)
			{
			}

			protected override T GetHandler(Action<EventPattern<TSender, U>> onNext)
			{
				return ReflectionUtils.CreateDelegate<T>(new Action<TSender, U>(delegate(TSender sender, U eventArgs)
				{
					onNext(new EventPattern<TSender, U>(sender, eventArgs));
				}), typeof(Action<TSender, U>).GetMethod("Invoke"));
			}
		}

		public sealed class Handler<TSender, T, TResult> : EventProducer<Delegate, TResult>
		{
			public Handler(object target, Type delegateType, MethodInfo addMethod, MethodInfo removeMethod, Func<TSender, T, TResult> getResult, bool isWinRT, IScheduler scheduler)
				: base(scheduler)
			{
				this._isWinRT = isWinRT;
				this._target = target;
				this._delegateType = delegateType;
				this._addMethod = addMethod;
				this._removeMethod = removeMethod;
				this._getResult = getResult;
			}

			protected override Delegate GetHandler(Action<TResult> onNext)
			{
				Action<TSender, T> action = delegate(TSender sender, T eventArgs)
				{
					onNext(this._getResult(sender, eventArgs));
				};
				return ReflectionUtils.CreateDelegate(this._delegateType, action, typeof(Action<TSender, T>).GetMethod("Invoke"));
			}

			protected override IDisposable AddHandler(Delegate handler)
			{
				Action action = null;
				try
				{
					if (this._isWinRT)
					{
						action = this.AddHandlerCoreWinRT(handler);
					}
					else
					{
						action = this.AddHandlerCore(handler);
					}
				}
				catch (TargetInvocationException ex)
				{
					throw ex.InnerException;
				}
				return new FromEventPattern.Handler<TSender, T, TResult>.RemoveHandlerDisposable(action);
			}

			private Action AddHandlerCore(Delegate handler)
			{
				this._addMethod.Invoke(this._target, new object[] { handler });
				return delegate
				{
					this._removeMethod.Invoke(this._target, new object[] { handler });
				};
			}

			private Action AddHandlerCoreWinRT(Delegate handler)
			{
				object token = this._addMethod.Invoke(this._target, new object[] { handler });
				return delegate
				{
					this._removeMethod.Invoke(this._target, new object[] { token });
				};
			}

			private readonly object _target;

			private readonly Type _delegateType;

			private readonly MethodInfo _addMethod;

			private readonly MethodInfo _removeMethod;

			private readonly Func<TSender, T, TResult> _getResult;

			private readonly bool _isWinRT;

			private sealed class RemoveHandlerDisposable : IDisposable
			{
				public RemoveHandlerDisposable(Action removeHandler)
				{
					Volatile.Write<Action>(ref this._removeHandler, removeHandler);
				}

				public void Dispose()
				{
					try
					{
						Action action = Interlocked.Exchange<Action>(ref this._removeHandler, null);
						if (action != null)
						{
							action();
						}
					}
					catch (TargetInvocationException ex)
					{
						throw ex.InnerException;
					}
				}

				private Action _removeHandler;
			}
		}
	}
}
