using System;
using System.Threading;
using System.Threading.Tasks;

// ReSharper disable UnusedMember.Global - public API

namespace IPCamLib
{
    /// <summary>
    /// Receives frames from an ICamera as well as status updates.
    /// </summary>
    public interface IRetryableCameraObserver : ICameraObserver
    {
        /// <summary>
        /// Called when a new Fetch attempt has started.
        /// </summary>
        Task OnFetch();

        /// <summary>
        /// Called when the stream was disconnected.
        /// </summary>
        /// <param name="exception">Contains the exception, if any occured. Null for graceful disconnects and timeouts.</param>
        /// <param name="retryDelay">The delay until the next Fetch is attempted.</param>
        Task OnDisconnected(Exception exception, TimeSpan retryDelay);
    }
    
    
    /// <summary>
    /// Implements retry logic for an ICamera instance.
    /// </summary>
    public class RetryableCamera
    {
        private readonly ICamera camera;


        /// <summary>
        /// Creates a new instance of a RetryableCamera.
        /// </summary>
        /// <param name="camera">The camera instance to fetch from.</param>
        public RetryableCamera(ICamera camera)
        {
            this.camera = camera;
        }


        /// <summary>
        /// Starts receiving frames and will retry when disconnected.
        /// </summary>
        /// <param name="observer">The observer implementation to receive frames and status updates.</param>
        /// <param name="cancellationToken">A CancellationToken which will stop the camera stream when cancelled.</param>
        public async Task Fetch(IRetryableCameraObserver observer, CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                // TODO incremental back-off?
                var retryDelay = TimeSpan.FromSeconds(5);
                var exception = false;
                
                try
                {
                    await observer.OnFetch();
                    await camera.Fetch(observer, cancellationToken);
                }
                catch (TaskCanceledException)
                {
                    // Empty by design
                }
                catch (Exception e)
                {
                    await observer.OnDisconnected(e, retryDelay);
                    exception = true;
                }
                
                
                if (!exception && !cancellationToken.IsCancellationRequested)
                    await observer.OnDisconnected(null, retryDelay);

                await Task.Delay(retryDelay, cancellationToken);
            }
        }
    }
}
