using System.Diagnostics;
using System.Runtime.CompilerServices;
using static System.Runtime.CompilerServices.MethodImplOptions;

namespace InnovEnergy.Lib.Utils;

public static class Utils
{

    public static IEnumerable<String> GetEnumStrings<T>(this T e) where T : Enum
    {
        return GetEnumValues<T>().Select(v => v.ToString());
    }

    public static IReadOnlyList<TEnum> GetEnumValues<TEnum>() where TEnum : Enum
    {
        return (TEnum[]) Enum.GetValues(typeof(TEnum));
    }

    [DebuggerStepThrough][MethodImpl(AggressiveInlining)]
    public static T ConvertTo<T>(this IConvertible c) where T : IConvertible
    {
        var t = typeof (T);

        var type = t.IsEnum
                 ? Enum.GetUnderlyingType(t)
                 : t;

        return (T) Convert.ChangeType(c, type);
    }


    [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
    public static void Nop<T>(T _) {}

    [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
    public static T Id<T>(T t) => t;

    [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
    public static T CastTo<T>(this Object source) => (T) source;


    [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
    public static T Apply<T>(this T t, Action<T> f)
    {
        f(t);
        return t;
    }

    [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
    public static R Apply<T, R>(this T t, Func<T, R> f) => f(t);

    [DebuggerStepThrough]
    [MethodImpl(AggressiveInlining | AggressiveOptimization)]
    public static R Apply<T1, T2, R>(this (T1 p1, T2 p2) t, Func<T1, T2, R> f) => f(t.p1, t.p2);

    [DebuggerStepThrough]
    [MethodImpl(AggressiveInlining | AggressiveOptimization)]
    public static R Apply<T1, T2, T3, R>(this (T1 p1, T2 p2,  T3 p3) t, Func<T1, T2, T3, R> f) => f(t.p1, t.p2, t.p3);

    [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
    public static R ApplyOrDefault<T, R>(this T t, Func<T, R> f, R @default)
    {
        try
        {
            return f(t);
        }
        catch
        {
            return @default;
        }
    }

    public static Int32 Modulo(this Int32 index, Int32 length)
    {
        var res = index % length;

        return res >= 0
            ? res
            : res + length;
    }

    public static IEnumerable<T> Traverse<T>(this T root, Func<T, IEnumerable<T>> getChildren)
    {
        var stack = new Stack<IEnumerator<T>>();
        var it    = root.AsSingleEnumerator();
        it.MoveNext();

        while (true)
        {
            //////// going down ////////

            while (true)
            {
                var cit = getChildren(it.Current).GetEnumerator();

                if (cit.MoveNext())  // node has children, must be a branch
                {
                    yield return it.Current;

                    stack.Push(it);
                    it = cit;
                }
                else // no children, hence a leaf
                {
                    var node = it.Current;

                    yield return node;

                    if (!it.MoveNext())
                        break; // no more siblings: goto parent
                }
            }

            //////// going up ////////

            while (true)
            {
                it.Dispose();
                if (stack.Count == 0)
                    yield break; // we got to the bottom of the stack, were done

                it = stack.Pop();

                if (it.MoveNext())
                    break;
            }
        }

    }

    public static Int32 Clamp(this Int32 value, Int32 minValue, Int32 maxValue)
    {
        var clamped = Math.Min(maxValue, value);
        clamped = Math.Max(minValue, clamped);
        
        return clamped;
    }
    
    public static Double Clamp(this Double value, Double minValue, Double maxValue)
    {
        var clamped = Math.Min(maxValue, value);
        clamped = Math.Max(minValue, clamped);
        
        return clamped;
    }
    
    
    public static Decimal Clamp(this Decimal value, Decimal minValue, Decimal maxValue)
    {
        var clamped = Math.Min(maxValue, value);
        clamped = Math.Max(minValue, clamped);
        
        return clamped;
    }


#pragma warning disable 8714

    public static async Task<R> ApplyOrDefault<T, R>(this T t, Func<T, Task<R>> f, R @default)
    {
        try
        {
            return await f(t);
        }
        catch
        {
            return @default;
        }
    }

    public static R ValueOrDefault<T, R>(this Dictionary<T, R> dict, T key, R defaultValue)
    {
        return dict.TryGetValue(key, out var value)
            ? value
            : defaultValue;
    }


    public static Dictionary<K, V> CombineDicts<K,V>(params IEnumerable<KeyValuePair<K,V>>[] dicts)
    {
        return dicts.Flatten().ToDictionary(kv => kv.Key, kv => kv.Value);
    }

#pragma warning restore 8714


    public static void CopyFilesRecursively(String source, String target)
    {
        CopyFilesRecursively(new DirectoryInfo(source), new DirectoryInfo(target));
    }

    public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
    {
        foreach (var file in source.GetFiles())
            file.CopyTo(Path.Combine(target.FullName, file.Name));

        foreach (var dir in source.GetDirectories())
            CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
    }


    public static String ExecutingProcessName => Process.GetCurrentProcess().ProcessName;

    public static IEnumerable<IEnumerable<T>> TraverseWithPath<T>(this T root, Func<T, IEnumerable<T>> getChildren)
    {
        var stack = new Stack<IEnumerator<T>>();
        stack.Push(root.AsSingleEnumerator());

        do
        {
            for (var top = stack.Peek(); top.MoveNext(); top = Push(top))
                yield return stack.Select(p => p.Current);

            var popped = stack.Pop();
            popped.Dispose();
        }
        while (stack.Count > 0);

        IEnumerator<T> Push(IEnumerator<T> node)
        {
            var top = getChildren(node.Current).GetEnumerator();
            stack.Push(top);
            return top;
        }
    }
}