using System;
using System.Threading;
using System.Threading.Tasks;
using Swan.Logging;
namespace Swan.Threading {
  /// 
  /// Schedule an action to be periodically executed on the thread pool.
  /// 
  public sealed class PeriodicTask : IDisposable {
    /// 
    /// The minimum interval between action invocations.
    /// The value of this field is equal to 100 milliseconds.
    /// 
    public static readonly TimeSpan MinInterval = TimeSpan.FromMilliseconds(100);
    private readonly Func _action;
    private readonly CancellationTokenSource _cancellationTokenSource;
    private TimeSpan _interval;
    /// 
    /// Initializes a new instance of the  class.
    /// 
    /// The interval between invocations of .
    /// The callback to invoke periodically.
    /// A  that can be used to cancel operations.
    public PeriodicTask(TimeSpan interval, Func action, CancellationToken cancellationToken = default) {
      this._action = action ?? throw new ArgumentNullException(nameof(action));
      this._cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
      this._interval = ValidateInterval(interval);
      _ = Task.Run(this.ActionLoop);
    }
    /// 
    /// Finalizes an instance of the  class.
    /// 
    ~PeriodicTask() {
      this.Dispose(false);
    }
    /// 
    /// Gets or sets the interval between periodic action invocations.
    /// Changes to this property take effect after next action invocation.
    /// 
    /// 
    public TimeSpan Interval {
      get => this._interval;
      set => this._interval = ValidateInterval(value);
    }
    /// 
    public void Dispose() {
      this.Dispose(true);
      GC.SuppressFinalize(this);
    }
    private void Dispose(Boolean disposing) {
      if(disposing) {
        this._cancellationTokenSource.Cancel();
        this._cancellationTokenSource.Dispose();
      }
    }
    private static TimeSpan ValidateInterval(TimeSpan value)
        => value < MinInterval ? MinInterval : value;
    private async Task ActionLoop() {
      for(; ; )
      {
        try {
          await Task.Delay(this.Interval, this._cancellationTokenSource.Token).ConfigureAwait(false);
          await this._action(this._cancellationTokenSource.Token).ConfigureAwait(false);
        } catch(OperationCanceledException) when(this._cancellationTokenSource.IsCancellationRequested) {
          break;
        } catch(TaskCanceledException) when(this._cancellationTokenSource.IsCancellationRequested) {
          break;
        } catch(Exception ex) {
          ex.Log(nameof(PeriodicTask));
        }
      }
    }
  }
}