﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dawn;
using FclEx.Helpers;
using FclEx.Utils;
using MoreLinq;

namespace FclEx
{
    [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
    public static partial class EnumerableExtensions
    {
        public static bool IsValid<T>([NotNullWhen(true)] this IEnumerable<T>? source)
        {
            return !source.IsNullOrEmpty();
        }

        public static bool IsEmpty<T>(this IEnumerable<T> source)
        {
            return !source.Any();
        }

        public static bool IsNullOrEmpty<T>([NotNullWhen(false)] this IEnumerable<T>? source)
        {
            return source == null || !source.Any();
        }

        public static IEnumerable<T> Touch<T>(this IEnumerable<T>? source)
        {
            return source ?? Enumerable.Empty<T>();
        }

        public static string JoinWith<T>(this IEnumerable<T> strs, string separator)
        {
            return string.Join(separator, strs.Select(m => m?.ToString()));
        }

        public static IEnumerable<T> NotNull<T>(this IEnumerable<T> col)
        {
            return col.Where(m => m != null);
        }

        public static IEnumerable<T> WhereIf<T>(this IEnumerable<T> source, Func<T, bool> predicate, bool condition)
        {
            return condition ? source.Where(predicate) : source;
        }

        public static IEnumerable<T> WhereIf<T>(this IEnumerable<T> source, Func<T, int, bool> predicate, bool condition)
        {
            return condition ? source.Where(predicate) : source;
        }

        public static IEnumerable<TResult> SelectMany<TSource, TResult>(this IEnumerable<TSource> source,
            Func<TSource, TSource, TResult> resultSelector)
        {
            return source.SelectMany(m => source, resultSelector);
        }

        public static IEnumerable<T> Except<T>(this IEnumerable<T> source, T item, IEqualityComparer<T>? comparer = null)
        {
            comparer ??= EqualityComparer<T>.Default;
            return source.Where(m => !comparer.Equals(m, item));
        }

        public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> enumerable, IComparer<T>? comparer = null)
        {
            return new SortedSet<T>(enumerable, comparer ?? Comparer<T>.Default);
        }

        [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
        public static TProp[] ToArrayByIndex<T, TProp>(this IEnumerable<T> enumerable, Func<T, int> indexSelector, Func<T, TProp> valueSelector)
        {
            Guard.Argument(enumerable, nameof(enumerable)).NotNull();
            Guard.Argument(indexSelector, nameof(indexSelector)).NotNull();
            Guard.Argument(valueSelector, nameof(valueSelector)).NotNull();

            if (!enumerable.Any())
                return Array.Empty<TProp>();

            var max = enumerable.Max(indexSelector);
            var list = new TProp[max + 1];
            foreach (var item in enumerable)
            {
                var index = indexSelector(item);
                list[index] = valueSelector(item);
            }
            return list;
        }

        public static IEnumerable<KeyValuePair<T1, T2>> AsKeyValue<T1, T2>(this IEnumerable<ValueTuple<T1, T2>> enumerable)
        {
            return enumerable.Select(m => m.AsKeyValue());
        }

        public static IEnumerable<ValueTuple<T1, T2>> AsTuple<T1, T2>(this IEnumerable<KeyValuePair<T1, T2>> enumerable)
        {
            return enumerable.Select(m => m.AsTuple());
        }

        /// <summary>
        /// Wraps this object instance into an IEnumerable
        /// </summary>
        public static IEnumerable<T> Yield<T>(this T item)
        {
            yield return item;
        }

        public static IEnumerable<T> Concat<T>(this T firstElement, IEnumerable<T>? secondSequence)
        {
            yield return firstElement;
            if (secondSequence == null)
            {
                yield break;
            }

            foreach (var item in secondSequence)
            {
                yield return item;
            }
        }

        [return: MaybeNull]
        public static TResult MaxOr<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector, TResult defaultValue = default)
        {
            return source.Any() ? source.Max(selector) : defaultValue;
        }

        [return: MaybeNull]
        public static TSource MaxOr<TSource>(this IEnumerable<TSource> source, TSource defaultValue = default)
        {
            return source.Any() ? source.Max() : defaultValue;
        }

        [return: MaybeNull]
        public static TResult MinOr<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector, TResult defaultValue = default)
        {
            return source.Any() ? source.Min(selector) : defaultValue;
        }

        [return: MaybeNull]
        public static TSource MinOr<TSource>(this IEnumerable<TSource> source, TSource defaultValue = default)
        {
            return source.Any() ? source.Min() : defaultValue;
        }

        public static TimeSpan Average<TSource>(this IEnumerable<TSource> source, Func<TSource, TimeSpan> selector)
        {
            var ticks = (long)source.Select(m => selector(m).Ticks).Average();
            return TimeSpan.FromTicks(ticks);
        }

        public static TimeSpan Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, TimeSpan> selector)
        {
            var ticks = source.Select(m => selector(m).Ticks).Sum();
            return TimeSpan.FromTicks(ticks);
        }
    }
}
