using System;
using System.Reactive.Concurrency;
using System.Reactive.Linq;

namespace System.Reactive.Subjects
{
	public static class Subject
	{
		public static ISubject<TSource, TResult> Create<TSource, TResult>(IObserver<TSource> observer, IObservable<TResult> observable)
		{
			if (observer == null)
			{
				throw new ArgumentNullException("observer");
			}
			if (observable == null)
			{
				throw new ArgumentNullException("observable");
			}
			return new Subject.AnonymousSubject<TSource, TResult>(observer, observable);
		}

		public static ISubject<T> Create<T>(IObserver<T> observer, IObservable<T> observable)
		{
			if (observer == null)
			{
				throw new ArgumentNullException("observer");
			}
			if (observable == null)
			{
				throw new ArgumentNullException("observable");
			}
			return new Subject.AnonymousSubject<T>(observer, observable);
		}

		public static ISubject<TSource, TResult> Synchronize<TSource, TResult>(ISubject<TSource, TResult> subject)
		{
			if (subject == null)
			{
				throw new ArgumentNullException("subject");
			}
			return new Subject.AnonymousSubject<TSource, TResult>(Observer.Synchronize<TSource>(subject), subject);
		}

		public static ISubject<TSource> Synchronize<TSource>(ISubject<TSource> subject)
		{
			if (subject == null)
			{
				throw new ArgumentNullException("subject");
			}
			return new Subject.AnonymousSubject<TSource>(Observer.Synchronize<TSource>(subject), subject);
		}

		public static ISubject<TSource, TResult> Synchronize<TSource, TResult>(ISubject<TSource, TResult> subject, IScheduler scheduler)
		{
			if (subject == null)
			{
				throw new ArgumentNullException("subject");
			}
			if (scheduler == null)
			{
				throw new ArgumentNullException("scheduler");
			}
			return new Subject.AnonymousSubject<TSource, TResult>(Observer.Synchronize<TSource>(subject), subject.ObserveOn(scheduler));
		}

		public static ISubject<TSource> Synchronize<TSource>(ISubject<TSource> subject, IScheduler scheduler)
		{
			if (subject == null)
			{
				throw new ArgumentNullException("subject");
			}
			if (scheduler == null)
			{
				throw new ArgumentNullException("scheduler");
			}
			return new Subject.AnonymousSubject<TSource>(Observer.Synchronize<TSource>(subject), subject.ObserveOn(scheduler));
		}

		private class AnonymousSubject<T, U> : ISubject<T, U>, IObserver<T>, IObservable<U>
		{
			public AnonymousSubject(IObserver<T> observer, IObservable<U> observable)
			{
				this._observer = observer;
				this._observable = observable;
			}

			public void OnCompleted()
			{
				this._observer.OnCompleted();
			}

			public void OnError(Exception error)
			{
				if (error == null)
				{
					throw new ArgumentNullException("error");
				}
				this._observer.OnError(error);
			}

			public void OnNext(T value)
			{
				this._observer.OnNext(value);
			}

			public IDisposable Subscribe(IObserver<U> observer)
			{
				if (observer == null)
				{
					throw new ArgumentNullException("observer");
				}
				return this._observable.Subscribe(observer);
			}

			private readonly IObserver<T> _observer;

			private readonly IObservable<U> _observable;
		}

		private sealed class AnonymousSubject<T> : Subject.AnonymousSubject<T, T>, ISubject<T>, ISubject<T, T>, IObserver<T>, IObservable<T>
		{
			public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
				: base(observer, observable)
			{
			}
		}
	}
}
