// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information. 

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

namespace System.Reactive.Linq
{
#if !NO_PERF
    using ObservableImpl;
#endif

    internal partial class QueryLanguage
    {
        #region + Subscribe +

        public virtual IDisposable Subscribe<TSource>(IEnumerable<TSource> source, IObserver<TSource> observer)
        {
            return Subscribe_<TSource>(source, observer, SchedulerDefaults.Iteration);
        }

        public virtual IDisposable Subscribe<TSource>(IEnumerable<TSource> source, IObserver<TSource> observer, IScheduler scheduler)
        {
            return Subscribe_<TSource>(source, observer, scheduler);
        }

        private static IDisposable Subscribe_<TSource>(IEnumerable<TSource> source, IObserver<TSource> observer, IScheduler scheduler)
        {
#if !NO_PERF
            //
            // [OK] Use of unsafe Subscribe: we're calling into a known producer implementation.
            //
            return new ToObservable<TSource>(source, scheduler).Subscribe/*Unsafe*/(observer);
#else
            var e = source.GetEnumerator();
            var flag = new BooleanDisposable();

            scheduler.Schedule(self =>
            {
                var hasNext = false;
                var ex = default(Exception);
                var current = default(TSource);

                if (flag.IsDisposed)
                {
                    e.Dispose();
                    return;
                }

                try
                {
                    hasNext = e.MoveNext();
                    if (hasNext)
                        current = e.Current;
                }
                catch (Exception exception)
                {
                    ex = exception;
                }

                if (!hasNext || ex != null)
                {
                    e.Dispose();
                }

                if (ex != null)
                {
                    observer.OnError(ex);
                    return;
                }

                if (!hasNext)
                {
                    observer.OnCompleted();
                    return;
                }

                observer.OnNext(current);
                self();
            });

            return flag;
#endif
        }

        #endregion

        #region + ToEnumerable +

        public virtual IEnumerable<TSource> ToEnumerable<TSource>(IObservable<TSource> source)
        {
            return new AnonymousEnumerable<TSource>(() => source.GetEnumerator());
        }

        #endregion

        #region ToEvent

        public virtual IEventSource<Unit> ToEvent(IObservable<Unit> source)
        {
            return new EventSource<Unit>(source, (h, _) => h(Unit.Default));
        }

        public virtual IEventSource<TSource> ToEvent<TSource>(IObservable<TSource> source)
        {
            return new EventSource<TSource>(source, (h, value) => h(value));
        }

        #endregion

        #region ToEventPattern

        public virtual IEventPatternSource<TEventArgs> ToEventPattern<TEventArgs>(IObservable<EventPattern<TEventArgs>> source)
#if !NO_EVENTARGS_CONSTRAINT
            where TEventArgs : EventArgs
#endif
        {
            return new EventPatternSource<TEventArgs>(
#if !NO_VARIANCE
                source,
#else
                source.Select(x => (EventPattern<object, TEventArgs>)x),
#endif
                (h, evt) => h(evt.Sender, evt.EventArgs)
            );
        }

        #endregion

        #region + ToObservable +

        public virtual IObservable<TSource> ToObservable<TSource>(IEnumerable<TSource> source)
        {
#if !NO_PERF
            return new ToObservable<TSource>(source, SchedulerDefaults.Iteration);
#else
            return ToObservable_(source, SchedulerDefaults.Iteration);
#endif
        }

        public virtual IObservable<TSource> ToObservable<TSource>(IEnumerable<TSource> source, IScheduler scheduler)
        {
#if !NO_PERF
            return new ToObservable<TSource>(source, scheduler);
#else
            return ToObservable_(source, scheduler);
#endif
        }

#if NO_PERF
        private static IObservable<TSource> ToObservable_<TSource>(IEnumerable<TSource> source, IScheduler scheduler)
        {
            return new AnonymousObservable<TSource>(observer => source.Subscribe(observer, scheduler));
        }
#endif

        #endregion
    }
}
