using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Services
{
    /// 
    /// When working with cloud services and Docker containers, it's very important to always catch
    /// TimeoutException, and retry the operation.  
    /// RetryWithExponentialBackoff makes it easy to implement such pattern. 
    /// Usage:
    ///     var retry = new RetryWithExponentialBackoff();
    ///     await retry.RunAsync(async ()=>
    ///     {
    ///        // work with HttpClient
    ///     });
    /// 
    public sealed class RetryWithExponentialBackoff
    {
        private readonly int maxRetries, delayMilliseconds, maxDelayMilliseconds;
        public RetryWithExponentialBackoff(int maxRetries = 10, int delayMilliseconds = 200, int maxDelayMilliseconds = 2000)
        {
            this.maxRetries = maxRetries;
            this.delayMilliseconds = delayMilliseconds;
            this.maxDelayMilliseconds = maxDelayMilliseconds;
        }
        public async Task RunAsync(Func func)
        {
            ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries, this.delayMilliseconds, this.maxDelayMilliseconds);
        retry:
            try
            {
                await func();
            }
            catch (Exception ex) when (ex is TimeoutException || ex is System.Net.Http.HttpRequestException)
            {
                Debug.WriteLine("Exception raised is: " + ex.GetType().ToString() + " -- Message: " + ex.Message + " -- Inner Message: " + ex.InnerException.Message);
                await backoff.Delay();
                goto retry;
            }
        }
    }
    /// 
    /// Usage:
    /// ExponentialBackoff backoff = new ExponentialBackoff(3, 10, 100);
    /// retry:
    /// try {
    ///        // ...
    /// }
    /// catch (Exception ex) {
    ///    await backoff.Delay(cancellationToken);
    ///    goto retry;
    /// }
    /// 
    public struct ExponentialBackoff
    {
        private readonly int m_maxRetries, m_delayMilliseconds, m_maxDelayMilliseconds;
        private int m_retries, m_pow;
        public ExponentialBackoff(int maxRetries, int delayMilliseconds, int maxDelayMilliseconds)
        {
            m_maxRetries = maxRetries;
            m_delayMilliseconds = delayMilliseconds;
            m_maxDelayMilliseconds = maxDelayMilliseconds;
            m_retries = 0;
            m_pow = 1;
        }
        public Task Delay()
        {
            if (m_retries == m_maxRetries)
            {
                throw new TimeoutException("Max retry attempts exceeded.");
            }
            ++m_retries;
            if (m_retries < 31)
            {
                m_pow = m_pow << 1; // m_pow = Pow(2, m_retries - 1)
            }
            int delay = Math.Min(m_delayMilliseconds * (m_pow - 1) / 2, m_maxDelayMilliseconds);
            return Task.Delay(delay);
        }
    }
}