using System.Collections.Generic;

namespace System.Linq
{
    public static class LinqExtension
    {
        public static void ThrowIfNull<T>(this T val, string message = null!)
        {
            if (null == val)
                throw new ArgumentNullException(message);
        }

        public static void Apply<T>(this IEnumerable<T> seq, Action<T> action)
        {
            seq.ThrowIfNull();
            action.ThrowIfNull();
            foreach (var e in seq)
            {
                action.Invoke(e);
            }
        }
        public static IEnumerable<T[]> ZipLongest<T>(this IEnumerable<T> left, IEnumerable<T> right)
        {
            IEnumerator<T> leftEnumerator = left.GetEnumerator();
            IEnumerator<T> rightEnumerator = right.GetEnumerator();

            bool hasLeft = leftEnumerator.MoveNext();
            bool hasRight = rightEnumerator.MoveNext();

            while (hasLeft || hasRight)
            {
                if (hasLeft && hasRight)
                {
                    yield return new T[] { leftEnumerator.Current, rightEnumerator.Current };
                }
                else if (hasLeft)
                {
                    yield return new T[] { leftEnumerator.Current, default(T)! };
                }
                else if (hasRight)
                {
                    yield return new T[] { default(T)!, rightEnumerator.Current };
                }

                hasLeft = leftEnumerator.MoveNext();
                hasRight = rightEnumerator.MoveNext();
            }
        }

        public static IEnumerable<TSource> DistinctBy<TSource, TKey>
             (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            if (source != null!)
            {
                var seenKeys = new HashSet<TKey>();
                foreach (TSource element in source)
                {
                    if (seenKeys.Add(keySelector(element)))
                    {
                        yield return element;
                    }
                }
            }
        }

        public static IList<TSource> Materialize<TSource>(this IEnumerable<TSource> source)
        {
            if (source is IList<TSource>)
            {
                // Already a list, use it as is
                return (IList<TSource>)source;
            }
            else
            {
                // Not a list, materialize it to a list
                return source.ToList();
            }
        }
        public static IEnumerable<IEnumerable<T>> Chunkify<T>(this IEnumerable<T> source, int size)
        {
            if (source == null!)
            {
                yield break;
            }
            if (size <= 0)
            {
                throw new ArgumentException("chunkSize must be greater than 0.");
            }
            T[] arr = new T[size];
            int index = 0;
            foreach (var obj in source)
            {
                arr[index] = obj;
                index++;
                if (index >= size)
                {
                    yield return arr;
                    index = 0;
                    arr = new T[size];
                }
            }
            if (index > 0)
            {
                var narr = new T[index];
                Array.Copy(arr, narr, index);
                yield return narr;
            }
        }
    }
}