using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

/*
Name                    Description                                             Exceptions
====================================================================================================================
Avoid async void        Prefer async Task methods over async void methods       Event handlers
Async all the way       Don’t mix blocking and async code                       Console main method
Configure context       Use ConfigureAwait(false) when you can                  Methods that require con­text

To Do This                                  Instead of This                     Use This
====================================================================================================================
Retrieve the result of a background task	Task.Wait or Task.Result	        await
Wait for any task to complete	            Task.WaitAny	                    await Task.WhenAny
Retrieve the results of multiple tasks	    Task.WaitAll	                    await Task.WhenAll
Wait a period of time	                    Thread.Sleep	                    await Task.Delay
*/

// Sample demostration on asynchronous calls
// https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f
// https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

namespace Asynchronous
{
    class MyButton
    {
        public bool Enabled { get; set; }
    }

    class AsyncMethods
    {
        // sample only text output
        //private string url = "https://tibmanus.keybase.pub/hello.txt";
        private string url = "https://os.mbed.com/media/uploads/alonsoflxestevez/hello2.txt.txt";
        MyButton button1 = new MyButton();

        public async Task DelayAsync()  // NEW asynchronous method
        {
            // Code here runs in the original context.

            await Task.Delay(100);     // Do asynchronous work.

            // Code here runs in the original context.

            await Task.FromResult(1);
            await Task.Delay(0).ConfigureAwait(continueOnCapturedContext: false);

            // Code here runs in the original context.

            await Task.Delay(100).ConfigureAwait(continueOnCapturedContext: false);

            // Code here runs without the original context (in this case, on the thread pool).
        }

        // OK to return void ONLY if [asynchronous event handlers]
        private async void button1_Click(object sender, EventArgs e)
        {
            button1.Enabled = false;
            try
            {            
                await DelayAsync();         // Can't use ConfigureAwait here. Inside, YES!
            }
            finally
            {
                button1.Enabled = true;     // Because we need the context here.
            }
        }

        // AVOID Task.Run at ALL cost in ASP.NET applications
        private async void button2_Click(object sender, EventArgs e)
        {
            // Conclusion: do not use Task.Run in the implementation of the method; instead, 
            // use Task.Run to call the method.
            await Task.Run(() => DoExpensiveCalculation(null));
        }

        public async Task<string> DoCurlAsync()
        {
            // Execution is synchronous here
            using (var client = new HttpClient())
            {
                // Execution of GetFirstCharactersCountAsync() is yielded to the caller here
                // GetStringAsync returns a Task<string>, which is *awaited*
                var page = await client.GetStringAsync(url);

                // Execution resumes when the client.GetStringAsync task completes,
                // becoming synchronous again.
                return page;
            }
        }

        // use Async Await for I/O bound job. a example code
        public async Task<string> DoCurlAsyncConfigureAwait()
        {
            using (var httpClient = new HttpClient())
            using (var httpResponse = await httpClient.GetAsync(url).ConfigureAwait(false))
            {
                return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
            }
        }

        // use Task.Run for CPU bound job. a example
        public async Task<int> CalculateResult(object data)
        {
            // This queues up the work on the threadpool.
            var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));

            // Note that at this point, you can do some other work concurrently,
            // as CalculateResult() is still executing!

            // Execution of CalculateResult is yielded here!
            var result = await expensiveResultTask;

            return result;
        }

        private int DoExpensiveCalculation(object data) { return 1; }

        public void DoTaskList()
        {
            var list = new ConcurrentBag<string>();
            string[] dirNames = { ".", ".." };
            List<Task> tasks = new List<Task>();

            foreach (var dirName in dirNames)
            {
                Task t = Task.Run(() => {
                    foreach (var path in Directory.GetFiles(dirName))
                        list.Add(path);
                });
                tasks.Add(t);
            }

            // BAD way, use [await Task.WhenAll], and make method [async]
            Task.WaitAll(tasks.ToArray());

            foreach (Task t in tasks)
            {
                Console.WriteLine("Task {0} Status: {1}", t.Id, t.Status);
            }
            Console.WriteLine("Number of files read: {0}", list.Count);
        }
    }

    class Program
    {
        private static AsyncMethods am = new AsyncMethods();

        // lamba function pointer declaration
        public delegate int LambdaFunc(int x, int y);

        static public void Delay()      // OLD synchronous method
        {
            Thread.Sleep(100);          // Do synchronous work.
        }

        static async Task<string> AsyncWrapper()
        {
            return await am.DoCurlAsync();                          // can only be used in async method
        }

        static async Task<string> AsyncWrapperConfigureAwait()
        {
            return await am.DoCurlAsyncConfigureAwait().ConfigureAwait(false);    // prefered!!
        }

        static void Main(string[] args)
        {
            // unconventional, use if no return value       // DEADLOCK possible in GUI or ASP.NET
            am.DoCurlAsync().Wait();                        // BAD: ignore return
            am.DoCurlAsync().Wait(200);                     // BAD: imeout the async funtion within time

            // get result immediately
            var v1 = am.DoCurlAsync().Result;               // NOT good in UI, such as button click
            Console.WriteLine($"v1: {v1}");                 // WILL DEADLOCK

            var v2 = am.DoCurlAsyncConfigureAwait().Result; // OK, code within configuresawait
            Console.WriteLine($"v2: {v2}");

            var v3 = Program.AsyncWrapper().Result;         // NOT good in UI, Deadlock
            Console.WriteLine($"v3: {v3}");

            var v4 = Program.AsyncWrapperConfigureAwait().Result;   // PREFERRED!
            Console.WriteLine($"v4: {v4}");

            // Task related
            var v5 = Task.Run(() => { return "Task.Run"; });
            Console.WriteLine($"v5: {v5.Result}");

            var v6 = Program.AsyncWrapper();
            v6.Wait();      // BAD IDEA!
            Console.WriteLine($"v6: {v6.Result}");

            var v7 = Program.AsyncWrapperConfigureAwait();
            v7.Wait();      // better
            Console.WriteLine($"v7: {v7.Result}");

            // Task LIST
            am.DoTaskList();        // BAD way, use [await Task.WhenAll]

            // WAYS to start as Task
            // most direct way
            Task.Factory.StartNew(() => { Console.WriteLine("Hello Task library!"); });

            // using action!
            Task t1 = new Task(new Action(Program.Delay));
            t1.Start();

            // using delegate
            Task t2 = new Task(delegate { Program.Delay(); });
            t2.Start();

            // lambda
            Task task = new Task(() => Program.Delay());
            task.Start();

            // Using Task.Run in .NET4.5
            Task.Run(() => Program.Delay());        // need await/async

            // Using Task.FromResult from lambda
            LambdaFunc GetSum = (x, y) => { return x + y; };
            LambdaFunc GetMin = (x, y) => { return x - y; };
            LambdaFunc GetMut = (x, y) => { return x * y; };
            LambdaFunc GetDiv = (x, y) => { return x * y; };

            var v8 = Task.FromResult<int>(GetSum(2, 6));       // need await/async
            v8.Wait();
            Console.WriteLine($"v8: {v8.Result}");
        }
    }
}