// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace BasicWebSite.Controllers
{
    public class AsyncActionsController : Controller
    {
        const int SimulateDelayMilliseconds = 20;

        public override void OnActionExecuted(ActionExecutedContext context)
        {
            // So that tests can observe we're following the proper flow after an exception, surface a
            // message saying what the exception was.
            if (context.Exception != null)
            {
                context.Result = Content($"Action exception message: {context.Exception.Message}");
                context.ExceptionHandled = true;
            }
        }

        public async void AsyncVoidAction()
        {
            await Task.Delay(SimulateDelayMilliseconds);
        }

        public Task TaskAction()
        {
            return Task.Delay(SimulateDelayMilliseconds);
        }

        public async Task TaskExceptionAction()
        {
            await Task.Delay(SimulateDelayMilliseconds);
            throw new CustomException();
        }

        public async Task<Message> TaskOfObjectAction(string message)
        {
            await Task.Delay(SimulateDelayMilliseconds);
            return new Message { Text = message };
        }

        public async Task<Message> TaskOfObjectExceptionAction(string message)
        {
            await Task.Delay(SimulateDelayMilliseconds);
            throw new CustomException();
        }

        public async Task<IActionResult> TaskOfIActionResultAction(string message)
        {
            await Task.Delay(SimulateDelayMilliseconds);
            return Content(message);
        }

        public async Task<IActionResult> TaskOfIActionResultExceptionAction(string message)
        {
            await Task.Delay(SimulateDelayMilliseconds);
            throw new CustomException();
        }

        public async Task<ContentResult> TaskOfContentResultAction(string message)
        {
            await Task.Delay(SimulateDelayMilliseconds);
            return Content(message);
        }

        public async Task<ContentResult> TaskOfContentResultExceptionAction(string message)
        {
            await Task.Delay(SimulateDelayMilliseconds);
            throw new CustomException();
        }

        public ValueTask<Message> PreCompletedValueTaskOfObjectAction(string message)
        {
            return new ValueTask<Message>(new Message { Text = message });
        }

        public ValueTask<Message> PreCompletedValueTaskOfObjectExceptionAction(string message)
        {
            throw new CustomException();
        }

        public ValueTask<IActionResult> PreCompletedValueTaskOfIActionResultAction(string message)
        {
            return new ValueTask<IActionResult>(Content(message));
        }

        public ValueTask<IActionResult> PreCompletedValueTaskOfIActionResultExceptionAction(string message)
        {
            throw new CustomException();
        }

        public ValueTask<ContentResult> PreCompletedValueTaskOfContentResultAction(string message)
        {
            return new ValueTask<ContentResult>(Content(message));
        }

        public ValueTask<ContentResult> PreCompletedValueTaskOfContentResultExceptionAction(string message)
        {
            throw new CustomException();
        }

        public CustomAwaitable CustomAwaitableVoidAction()
        {
            return new CustomAwaitable(SimulateDelayMilliseconds);
        }

        public CustomAwaitable CustomAwaitableVoidExceptionAction()
        {
            throw new CustomException();
        }

        public CustomAwaitable<Message> CustomAwaitableOfObjectAction(string message)
        {
            return new CustomAwaitable<Message>(
                SimulateDelayMilliseconds,
                new Message { Text = message });
        }

        public CustomAwaitable<Message> CustomAwaitableOfObjectExceptionAction(string message)
        {
            throw new CustomException();
        }

        public CustomAwaitable<IActionResult> CustomAwaitableOfIActionResultAction(string message)
        {
            return new CustomAwaitable<IActionResult>(SimulateDelayMilliseconds, Content(message));
        }

        public CustomAwaitable<IActionResult> CustomAwaitableOfIActionResultExceptionAction(string message)
        {
            throw new CustomException();
        }

        public CustomAwaitable<ContentResult> CustomAwaitableOfContentResultAction(string message)
        {
            return new CustomAwaitable<ContentResult>(SimulateDelayMilliseconds, Content(message));
        }

        public CustomAwaitable<ContentResult> CustomAwaitableOfContentResultExceptionAction(string message)
        {
            throw new CustomException();
        }

        public class Message
        {
            public string Text { get; set; }
        }

        public class CustomAwaitable
        {
            protected readonly int _simulateDelayMilliseconds;

            public CustomAwaitable(int simulateDelayMilliseconds)
            {
                _simulateDelayMilliseconds = simulateDelayMilliseconds;
            }

            public CustomAwaiter GetAwaiter()
            {
                return new CustomAwaiter(_simulateDelayMilliseconds);
            }
        }

        public class CustomAwaitable<T> : CustomAwaitable
        {
            private readonly T _result;

            public CustomAwaitable(int simulateDelayMilliseconds, T result)
                : base(simulateDelayMilliseconds)
            {
                _result = result;
            }

            public new CustomAwaiter<T> GetAwaiter()
            {
                return new CustomAwaiter<T>(_simulateDelayMilliseconds, _result);
            }
        }

        public class CustomAwaiter : INotifyCompletion
        {
            private IList<Action> _continuations = new List<Action>();

            public CustomAwaiter(int simulateDelayMilliseconds)
            {
                Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(simulateDelayMilliseconds);
                    lock(_continuations)
                    {
                        IsCompleted = true;

                        foreach (var continuation in _continuations)
                        {
                            continuation();
                        }

                        _continuations.Clear();
                    }
                });
            }

            public bool IsCompleted { get; private set; }

            public void OnCompleted(Action continuation)
            {
                lock (_continuations)
                {
                    if (IsCompleted)
                    {
                        continuation();
                    }
                    else
                    {
                        _continuations.Add(continuation);
                    }
                }
            }

            public void GetResult()
            {
            }
        }

        public class CustomAwaiter<T> : CustomAwaiter
        {
            private readonly T _result;

            public CustomAwaiter(int simulateDelayMilliseconds, T result)
                : base(simulateDelayMilliseconds)
            {
                _result = result;
            }

            public new T GetResult() => _result;
        }

        public class CustomException : Exception
        {
            public CustomException() : base("This is a custom exception.")
            {
            }
        }
    }
}
