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

using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;

namespace System.Reactive.Linq
{
    using ObservableImpl;

    internal partial class QueryLanguage
    {
        #region - Create -

        public virtual IObservable<TSource> Create<TSource>(Func<IObserver<TSource>, IDisposable> subscribe)
        {
            return new CreateWithDisposableObservable<TSource>(subscribe);
        }

        private sealed class CreateWithDisposableObservable<TSource> : ObservableBase<TSource>
        {
            private readonly Func<IObserver<TSource>, IDisposable> _subscribe;

            public CreateWithDisposableObservable(Func<IObserver<TSource>, IDisposable> subscribe)
            {
                _subscribe = subscribe;
            }

            protected override IDisposable SubscribeCore(IObserver<TSource> observer)
            {
                return _subscribe(observer) ?? Disposable.Empty;
            }
        }

        public virtual IObservable<TSource> Create<TSource>(Func<IObserver<TSource>, Action> subscribe)
        {
            return new CreateWithActionDisposable<TSource>(subscribe);
        }

        private sealed class CreateWithActionDisposable<TSource> : ObservableBase<TSource>
        {
            private readonly Func<IObserver<TSource>, Action> _subscribe;

            public CreateWithActionDisposable(Func<IObserver<TSource>, Action> subscribe)
            {
                _subscribe = subscribe;
            }

            protected override IDisposable SubscribeCore(IObserver<TSource> observer)
            {
                var a = _subscribe(observer);
                return a != null ? Disposable.Create(a) : Disposable.Empty;
            }
        }

        #endregion

        #region - CreateAsync -

        public virtual IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, CancellationToken, Task> subscribeAsync)
        {
            return new CreateWithTaskTokenObservable<TResult>(subscribeAsync);
        }

        private sealed class CreateWithTaskTokenObservable<TResult> : ObservableBase<TResult>
        {
            private sealed class Subscription : IDisposable
            {
                private sealed class TaskCompletionObserver : IObserver<Unit>
                {
                    private readonly IObserver<TResult> _observer;

                    public TaskCompletionObserver(IObserver<TResult> observer)
                    {
                        _observer = observer;
                    }

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

                    public void OnError(Exception error)
                    {
                        _observer.OnError(error);
                    }

                    public void OnNext(Unit value)
                    {
                        // deliberately ignored
                    }
                }

                private readonly IDisposable _subscription;
                private readonly CancellationTokenSource _cts = new();

                public Subscription(Func<IObserver<TResult>, CancellationToken, Task> subscribeAsync, IObserver<TResult> observer)
                {
                    _subscription = subscribeAsync(observer, _cts.Token)
                        .Subscribe(new TaskCompletionObserver(observer));
                }

                public void Dispose()
                {
                    _cts.Cancel();
                    _subscription.Dispose();
                }
            }

            private readonly Func<IObserver<TResult>, CancellationToken, Task> _subscribeAsync;

            public CreateWithTaskTokenObservable(Func<IObserver<TResult>, CancellationToken, Task> subscribeAsync)
            {
                _subscribeAsync = subscribeAsync;
            }

            protected override IDisposable SubscribeCore(IObserver<TResult> observer)
            {
                return new Subscription(_subscribeAsync, observer);
            }
        }

        public virtual IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, Task> subscribeAsync)
        {
            return Create<TResult>((observer, token) => subscribeAsync(observer));
        }

        public virtual IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, CancellationToken, Task<IDisposable>> subscribeAsync)
        {
            return new CreateWithTaskDisposable<TResult>(subscribeAsync);
        }

        private sealed class CreateWithTaskDisposable<TResult> : ObservableBase<TResult>
        {
            private sealed class Subscription : IDisposable
            {
                private sealed class TaskDisposeCompletionObserver : IObserver<IDisposable>, IDisposable
                {
                    private readonly IObserver<TResult> _observer;
                    private SingleAssignmentDisposableValue _disposable;

                    public TaskDisposeCompletionObserver(IObserver<TResult> observer)
                    {
                        _observer = observer;
                    }

                    public void Dispose()
                    {
                        _disposable.Dispose();
                    }

                    public void OnCompleted()
                    {
                    }

                    public void OnError(Exception error)
                    {
                        _observer.OnError(error);
                    }

                    public void OnNext(IDisposable value)
                    {
                        _disposable.Disposable = value;
                    }
                }

                private readonly TaskDisposeCompletionObserver _observer;
                private readonly CancellationTokenSource _cts = new();

                public Subscription(Func<IObserver<TResult>, CancellationToken, Task<IDisposable>> subscribeAsync, IObserver<TResult> observer)
                {
                    //
                    // We don't cancel the subscription below *ever* and want to make sure the returned resource gets disposed eventually.
                    // Notice because we're using the AnonymousObservable<T> type, we get auto-detach behavior for free.
                    //
                    subscribeAsync(observer, _cts.Token)
                        .Subscribe(_observer = new TaskDisposeCompletionObserver(observer));
                }

                public void Dispose()
                {
                    _cts.Cancel();
                    _observer.Dispose();
                }
            }

            private readonly Func<IObserver<TResult>, CancellationToken, Task<IDisposable>> _subscribeAsync;

            public CreateWithTaskDisposable(Func<IObserver<TResult>, CancellationToken, Task<IDisposable>> subscribeAsync)
            {
                _subscribeAsync = subscribeAsync;
            }

            protected override IDisposable SubscribeCore(IObserver<TResult> observer)
            {
                return new Subscription(_subscribeAsync, observer);
            }
        }

        public virtual IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, Task<IDisposable>> subscribeAsync)
        {
            return Create<TResult>((observer, token) => subscribeAsync(observer));
        }

        public virtual IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, CancellationToken, Task<Action>> subscribeAsync)
        {
            return new CreateWithTaskActionObservable<TResult>(subscribeAsync);
        }

        private sealed class CreateWithTaskActionObservable<TResult> : ObservableBase<TResult>
        {
            private sealed class Subscription : IDisposable
            {
                private sealed class TaskDisposeCompletionObserver : IObserver<Action>, IDisposable
                {
                    private readonly IObserver<TResult> _observer;
                    private Action? _disposable;

                    public TaskDisposeCompletionObserver(IObserver<TResult> observer)
                    {
                        _observer = observer;
                    }

                    public void Dispose()
                    {
                        Interlocked.Exchange(ref _disposable, Stubs.Nop)?.Invoke();
                    }

                    public void OnCompleted()
                    {
                    }

                    public void OnError(Exception error)
                    {
                        _observer.OnError(error);
                    }

                    public void OnNext(Action value)
                    {
                        if (Interlocked.CompareExchange(ref _disposable, value, null) != null)
                        {
                            value?.Invoke();
                        }
                    }
                }

                private readonly TaskDisposeCompletionObserver _observer;
                private readonly CancellationTokenSource _cts = new();

                public Subscription(Func<IObserver<TResult>, CancellationToken, Task<Action>> subscribeAsync, IObserver<TResult> observer)
                {
                    //
                    // We don't cancel the subscription below *ever* and want to make sure the returned resource gets disposed eventually.
                    // Notice because we're using the AnonymousObservable<T> type, we get auto-detach behavior for free.
                    //
                    subscribeAsync(observer, _cts.Token)
                        .Subscribe(_observer = new TaskDisposeCompletionObserver(observer));
                }

                public void Dispose()
                {
                    _cts.Cancel();
                    _observer.Dispose();
                }
            }

            private readonly Func<IObserver<TResult>, CancellationToken, Task<Action>> _subscribeAsync;

            public CreateWithTaskActionObservable(Func<IObserver<TResult>, CancellationToken, Task<Action>> subscribeAsync)
            {
                _subscribeAsync = subscribeAsync;
            }

            protected override IDisposable SubscribeCore(IObserver<TResult> observer)
            {
                return new Subscription(_subscribeAsync, observer);
            }
        }

        public virtual IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, Task<Action>> subscribeAsync)
        {
            return Create<TResult>((observer, token) => subscribeAsync(observer));
        }

        #endregion

        #region + Defer +

        public virtual IObservable<TValue> Defer<TValue>(Func<IObservable<TValue>> observableFactory)
        {
            return new Defer<TValue>(observableFactory);
        }

        #endregion

        #region + DeferAsync +

        public virtual IObservable<TValue> Defer<TValue>(Func<Task<IObservable<TValue>>> observableFactoryAsync, bool ignoreExceptionsAfterUnsubscribe)
        {
            return Defer(() => StartAsync(observableFactoryAsync, new TaskObservationOptions.Value(null, ignoreExceptionsAfterUnsubscribe)).Merge());
        }

        public virtual IObservable<TValue> Defer<TValue>(Func<CancellationToken, Task<IObservable<TValue>>> observableFactoryAsync, bool ignoreExceptionsAfterUnsubscribe)
        {
            return Defer(() => StartAsync(observableFactoryAsync, new TaskObservationOptions.Value(null, ignoreExceptionsAfterUnsubscribe)).Merge());
        }

        #endregion

        #region + Empty +

        public virtual IObservable<TResult> Empty<TResult>()
        {
            return EmptyDirect<TResult>.Instance;
        }

        public virtual IObservable<TResult> Empty<TResult>(IScheduler scheduler)
        {
            return new Empty<TResult>(scheduler);
        }

        #endregion

        #region + Generate +

        public virtual IObservable<TResult> Generate<TState, TResult>(TState initialState, Func<TState, bool> condition, Func<TState, TState> iterate, Func<TState, TResult> resultSelector)
        {
            return new Generate<TState, TResult>.NoTime(initialState, condition, iterate, resultSelector, SchedulerDefaults.Iteration);
        }

        public virtual IObservable<TResult> Generate<TState, TResult>(TState initialState, Func<TState, bool> condition, Func<TState, TState> iterate, Func<TState, TResult> resultSelector, IScheduler scheduler)
        {
            return new Generate<TState, TResult>.NoTime(initialState, condition, iterate, resultSelector, scheduler);
        }

        #endregion

        #region + Never +

        public virtual IObservable<TResult> Never<TResult>()
        {
            return ObservableImpl.Never<TResult>.Default;
        }

        #endregion

        #region + Range +

        public virtual IObservable<int> Range(int start, int count)
        {
            return Range_(start, count, SchedulerDefaults.Iteration);
        }

        public virtual IObservable<int> Range(int start, int count, IScheduler scheduler)
        {
            return Range_(start, count, scheduler);
        }

        private static IObservable<int> Range_(int start, int count, IScheduler scheduler)
        {
            var longRunning = scheduler.AsLongRunning();
            if (longRunning != null)
            {
                return new RangeLongRunning(start, count, longRunning);
            }
            return new RangeRecursive(start, count, scheduler);
        }

        #endregion

        #region + Repeat +

        public virtual IObservable<TResult> Repeat<TResult>(TResult value)
        {
            return Repeat_(value, SchedulerDefaults.Iteration);
        }

        public virtual IObservable<TResult> Repeat<TResult>(TResult value, IScheduler scheduler)
        {
            return Repeat_(value, scheduler);
        }

        private static IObservable<TResult> Repeat_<TResult>(TResult value, IScheduler scheduler)
        {
            var longRunning = scheduler.AsLongRunning();
            if (longRunning != null)
            {
                return new Repeat<TResult>.ForeverLongRunning(value, longRunning);
            }
            return new Repeat<TResult>.ForeverRecursive(value, scheduler);
        }

        public virtual IObservable<TResult> Repeat<TResult>(TResult value, int repeatCount)
        {
            return Repeat_(value, repeatCount, SchedulerDefaults.Iteration);
        }

        public virtual IObservable<TResult> Repeat<TResult>(TResult value, int repeatCount, IScheduler scheduler)
        {
            return Repeat_(value, repeatCount, scheduler);
        }

        private static IObservable<TResult> Repeat_<TResult>(TResult value, int repeatCount, IScheduler scheduler)
        {
            var longRunning = scheduler.AsLongRunning();
            if (longRunning != null)
            {
                return new Repeat<TResult>.CountLongRunning(value, repeatCount, longRunning);
            }
            return new Repeat<TResult>.CountRecursive(value, repeatCount, scheduler);
        }

        #endregion

        #region + Return +

        public virtual IObservable<TResult> Return<TResult>(TResult value)
        {
            // ConstantTimeOperations is a mutable field so we'd have to
            // check if it points to the immediate scheduler instance
            // which is done in the other Return overload anyway
            return Return(value, SchedulerDefaults.ConstantTimeOperations);
        }

        public virtual IObservable<TResult> Return<TResult>(TResult value, IScheduler scheduler)
        {
            if (scheduler == ImmediateScheduler.Instance)
            {
                return new ReturnImmediate<TResult>(value);
            }
            return new Return<TResult>(value, scheduler);
        }

        #endregion

        #region + Throw +

        public virtual IObservable<TResult> Throw<TResult>(Exception exception)
        {
            // ConstantTimeOperations is a mutable field so we'd have to
            // check if it points to the immediate scheduler instance
            // which is done in the other Return overload anyway
            return Throw<TResult>(exception, SchedulerDefaults.ConstantTimeOperations);
        }

        public virtual IObservable<TResult> Throw<TResult>(Exception exception, IScheduler scheduler)
        {
            if (scheduler == ImmediateScheduler.Instance)
            {
                return new ThrowImmediate<TResult>(exception);
            }
            return new Throw<TResult>(exception, scheduler);
        }

        #endregion

        #region + Using +

        public virtual IObservable<TSource> Using<TSource, TResource>(Func<TResource> resourceFactory, Func<TResource, IObservable<TSource>> observableFactory) where TResource : IDisposable
        {
            return new Using<TSource, TResource>(resourceFactory, observableFactory);
        }

        #endregion

        #region - UsingAsync -

        public virtual IObservable<TSource> Using<TSource, TResource>(Func<CancellationToken, Task<TResource>> resourceFactoryAsync, Func<TResource, CancellationToken, Task<IObservable<TSource>>> observableFactoryAsync) where TResource : IDisposable
        {
            return Observable.FromAsync(resourceFactoryAsync)
                .SelectMany(resource =>
                    Observable.Using(
                        () => resource,
                        resource_ => Observable.FromAsync(ct => observableFactoryAsync(resource_, ct)).Merge()
                    )
                );
        }

        #endregion
    }
}
