using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ZeroLevel.Services.Async
{
    /// <summary>
    /// Helper methods for cancellation tokens.
    /// </summary>
    public static class CancellationTokenHelpers
    {
        /// <summary>
        /// Initializes the static members.
        /// </summary>
        static CancellationTokenHelpers()
        {
            Canceled = new CancellationToken(true);
        }

        /// <summary>
        /// Gets <see cref="CancellationToken.None"/>, a cancellation token that is never canceled.
        /// </summary>
        public static CancellationToken None { get { return CancellationToken.None; } }

        /// <summary>
        /// Gets a cancellation token that is already canceled.
        /// </summary>
        public static CancellationToken Canceled { get; private set; }

        /// <summary>
        /// Creates a cancellation token that is canceled after the due time.
        /// </summary>
        /// <param name="dueTime">The due time after which to cancel the token.</param>
        /// <returns>A cancellation token that is canceled after the due time.</returns>
        public static NormalizedCancellationToken Timeout(TimeSpan dueTime)
        {
            var cts = new CancellationTokenSource();
            cts.CancelAfter(dueTime);
            return new NormalizedCancellationToken(cts);
        }

        /// <summary>
        /// Creates a cancellation token that is canceled after the due time.
        /// </summary>
        /// <param name="dueTime">The due time after which to cancel the token.</param>
        /// <returns>A cancellation token that is canceled after the due time.</returns>
        public static NormalizedCancellationToken Timeout(int dueTime)
        {
            var cts = new CancellationTokenSource();
            cts.CancelAfter(dueTime);
            return new NormalizedCancellationToken(cts);
        }

        /// <summary>
        /// Reduces a set of cancellation tokens by removing any cancellation tokens that cannot be canceled. If any tokens are already canceled, the returned token will be canceled.
        /// </summary>
        /// <param name="cancellationTokens">The cancellation tokens to reduce.</param>
        public static NormalizedCancellationToken Normalize(params CancellationToken[] cancellationTokens)
        {
            return Normalize((IEnumerable<CancellationToken>)cancellationTokens);
        }

        /// <summary>
        /// Reduces a set of cancellation tokens by removing any cancellation tokens that cannot be canceled. If any tokens are already canceled, the returned token will be canceled.
        /// </summary>
        /// <param name="cancellationTokens">The cancellation tokens to reduce.</param>
        public static NormalizedCancellationToken Normalize(IEnumerable<CancellationToken> cancellationTokens)
        {
            var tokens = cancellationTokens.Where(t => t.CanBeCanceled).ToArray();
            if (tokens.Length == 0)
                return new NormalizedCancellationToken();
            if (tokens.Length == 1)
                return new NormalizedCancellationToken(tokens[0]);
            var alreadyCanceled = tokens.FirstOrDefault(t => t.IsCancellationRequested);
            if (alreadyCanceled.IsCancellationRequested)
                return new NormalizedCancellationToken(alreadyCanceled);
            return new NormalizedCancellationToken(CancellationTokenSource.CreateLinkedTokenSource(tokens));
        }

        /// <summary>
        /// Creates a cancellation token that is canceled when the provided <see cref="Task"/> completes.
        /// </summary>
        /// <param name="source">The task to observe.</param>
        /// <param name="continuationOptions">The options to use for the task continuation.</param>
        public static NormalizedCancellationToken FromTask(Task source, TaskContinuationOptions continuationOptions)
        {
            var cts = new CancellationTokenSource();
            source.ContinueWith(_ => cts.Cancel(), CancellationToken.None, continuationOptions, TaskScheduler.Default);
            return new NormalizedCancellationToken(cts);
        }

        /// <summary>
        /// Creates a cancellation token that is canceled when the provided <see cref="Task"/> completes.
        /// </summary>
        /// <param name="source">The task to observe.</param>
        public static NormalizedCancellationToken FromTask(Task source)
        {
            return FromTask(source, TaskContinuationOptions.None);
        }
    }
}