using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
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 convertible) where T : IConvertible
    {
        return (T)ConvertTo(convertible, typeof(T));
    }

    [DebuggerStepThrough][MethodImpl(AggressiveInlining)]
    public static Object ConvertTo(this IConvertible convertible, Type type)
    {
        var t = type.IsEnum
              ? Enum.GetUnderlyingType(type)
              : type;

        return Convert.ChangeType(convertible, t, CultureInfo.InvariantCulture);
    }

    public static T Do<T>(this T t, Action action)
    {
        action();
        return t;
    }
    
    
    [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;
    }

    // Below does not work ;(
    // [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
    // public static R Apply<T, S, R>(this T t, Func<S, R> f) where T : S
    // {
    //     return f(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 Decimal Modulo(this Decimal dividend, Decimal divisor)
    {
        var res = dividend % divisor;

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


    public static Int32 Clamp(this Int32 value, Int32 minValue, Int32 maxValue)
    {
        if (minValue > maxValue)
            throw new ArgumentException();
        
        var clamped = Math.Min(maxValue, value);
        clamped = Math.Max(minValue, clamped);

        return clamped;
    }

    public static Double Clamp(this Double value, Double minValue, Double maxValue)
    {
        if (minValue > maxValue)
            throw new ArgumentException();

        var clamped = Math.Min(maxValue, value);
        clamped = Math.Max(minValue, clamped);
        
        return clamped;
    }

    public static Double ClampMax(this Double value, Double maxValue)
    {
        return Math.Min(maxValue, value);
    }

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

        return clamped;
    }

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

    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 Func<A, R> Memoize<A, R>(Func<A, R> func) where A : notnull
    {
        var cache = new ConcurrentDictionary<A, R>();
        
        return arg => cache.GetOrAdd(arg, func);
    }
}