using System;
using System.Collections.Generic;
using System.Linq;
using Godot;

namespace ld48 {
    public static class Random {
        private static RandomNumberGenerator r = new RandomNumberGenerator { Seed = (ulong) Guid.NewGuid().GetHashCode() };
        
        public static T RandItem<T>(this List<T> items) {
            return items[RandInt(0, items.Count)];
        }
        
        public static int RandInt(int from, int to) {
            return r.RandiRange(from, to-1);
        }
        
        public static float RandFloat(float from, float to) {
            return r.RandfRange(from, to);
        }

        public static bool Probability(float p) {
            return r.Randf() <= p;
        }
    }
    
    public static class Extensions {
        public static List<Vector3> orthogonals = new List<Vector3> {
            Vector3.Back, Vector3.Forward, Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right
        };

        public static Vector2 V2(float x, float y) {
            return new Vector2(x, y);
        }

        public static Vector2 V2(float n) {
            return new Vector2(n, n);
        }
        
        public static Vector3 V3(float x, float y, float z) {
            return new Vector3(x, y, z);
        }
        
        public static Vector3 V3(float n) {
            return new Vector3(n, n, n);
        }

        public static Color C(float x, float y, float z) {
            return new Color(x, y, z);
        }

        public static Color C(float x, float y, float z, float a) {
            return new Color(x, y, z, a);
        }

        public static Color C(float n) {
            return new Color(n, n, n);
        }

        public static Vector2 Copy(this Vector2 v) {
            return V2(v.x, v.y);
        }

        public static Vector3 Copy(this Vector3 v) {
            return V3(v.x, v.y, v.z);
        }
            
        public static Vector3 FloorToInt(this Vector3 v) {
            return V3(Mathf.FloorToInt(v.x), Mathf.FloorToInt(v.y), Mathf.FloorToInt(v.z));
        }
        
        public static Vector3 RoundToInt(this Vector3 v) {
            return V3(Mathf.RoundToInt(v.x), Mathf.RoundToInt(v.y), Mathf.RoundToInt(v.z));
        }

        public static Vector3 Times(this Vector3 a, Vector3 b) {
            return V3(a.x * b.x, a.y * b.y, a.z * b.z);
        }

        public static Vector3 RotateAround(this Vector3 v, Vector3 pivot, Vector3 normal, float angle) {
            return (v - pivot).Rotated(normal, angle) + pivot;
        }

        public static Vector2 MinComponents(this Vector2 a, Vector2 b) {
            return V2(Mathf.Min(a.x, b.x), Mathf.Min(a.y, b.y));
        }

        public static Vector2 MaxComponents(this Vector2 a, Vector2 b) {
            return V2(Mathf.Max(a.x, b.x), Mathf.Max(a.y, b.y));
        }

        public static Vector3 MinComponents(this Vector3 a, Vector3 b) {
            return V3(Mathf.Min(a.x, b.x), Mathf.Min(a.y, b.y), Mathf.Min(a.z, b.z));
        }

        public static Vector3 MaxComponents(this Vector3 a, Vector3 b) {
            return V3(Mathf.Max(a.x, b.x), Mathf.Max(a.y, b.y), Mathf.Max(a.z, b.z));
        }

        public static Vector2 Clamp(this Vector2 a, Vector2 min, Vector2 max) {
            return V2(Mathf.Min(Mathf.Max(a.x, min.x), max.x), Mathf.Min(Mathf.Max(a.y, min.y), max.y));
        }

        public static Vector3 Abs(this Vector3 v) {
            return V3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z));
        }

        public static Vector3 Snap(this Vector3 v) {
            return Snap(v, Vector3.One);
        }

        public static Vector3 Snap(this Vector3 v, Vector3 resolution) {
            return V3(
                Mathf.Floor(v.x / resolution.x) * resolution.x,
                Mathf.Floor(v.y / resolution.y) * resolution.y,
                Mathf.Floor(v.z / resolution.z) * resolution.z);
        }

        private static Vector3[] neighborhood = new[] {
            V3(-1, 0, 0), 
            V3(1, 0, 0), 
            V3(0, -1, 0), 
            V3(0, 1, 0), 
            V3(0, 0, -1), 
            V3(0, 0, 1), 
        };
        public static List<Vector3> Neighbors(this Vector3 v) {
            return neighborhood.Select(o => v + o).ToList();
        }

        public static Vector3 Normalized(this Vector3 v) {
            return v / (int) v.Length();
        }

        public static Vector2 Rotate90(this Vector2 v, int direction) {
            if (direction >= 0) {
                return V2(-v.y, v.x);
            }
            return V2(v.y, -v.x);
        }
        
        public static void ForEachIndexed<T>(this IEnumerable<T> ie, Action<int, T> action) {
            var i = 0;
            foreach (var e in ie) action(i++, e);
        }
        
        public static Vector3 xxx(this Vector3 v) { return V3(v.x, v.x, v.x); }
        public static Vector3 xxy(this Vector3 v) { return V3(v.x, v.x, v.y); }
        public static Vector3 xxz(this Vector3 v) { return V3(v.x, v.x, v.z); }
        public static Vector3 xxo(this Vector3 v) { return V3(v.x, v.x, 0f); }
        public static Vector3 xyx(this Vector3 v) { return V3(v.x, v.y, v.x); }
        public static Vector3 xyy(this Vector3 v) { return V3(v.x, v.y, v.y); }
        public static Vector3 xyz(this Vector3 v) { return V3(v.x, v.y, v.z); }
        public static Vector3 xyo(this Vector3 v) { return V3(v.x, v.y, 0f); }
        public static Vector3 xzx(this Vector3 v) { return V3(v.x, v.z, v.x); }
        public static Vector3 xzy(this Vector3 v) { return V3(v.x, v.z, v.y); }
        public static Vector3 xzz(this Vector3 v) { return V3(v.x, v.z, v.z); }
        public static Vector3 xzo(this Vector3 v) { return V3(v.x, v.z, 0f); }
        public static Vector3 xox(this Vector3 v) { return V3(v.x, 0f, v.x); }
        public static Vector3 xoy(this Vector3 v) { return V3(v.x, 0f, v.y); }
        public static Vector3 xoz(this Vector3 v) { return V3(v.x, 0f, v.z); }
        public static Vector3 xoo(this Vector3 v) { return V3(v.x, 0f, 0f); }
        public static Vector3 yxx(this Vector3 v) { return V3(v.y, v.x, v.x); }
        public static Vector3 yxy(this Vector3 v) { return V3(v.y, v.x, v.y); }
        public static Vector3 yxz(this Vector3 v) { return V3(v.y, v.x, v.z); }
        public static Vector3 yxo(this Vector3 v) { return V3(v.y, v.x, 0f); }
        public static Vector3 yyx(this Vector3 v) { return V3(v.y, v.y, v.x); }
        public static Vector3 yyy(this Vector3 v) { return V3(v.y, v.y, v.y); }
        public static Vector3 yyz(this Vector3 v) { return V3(v.y, v.y, v.z); }
        public static Vector3 yyo(this Vector3 v) { return V3(v.y, v.y, 0f); }
        public static Vector3 yzx(this Vector3 v) { return V3(v.y, v.z, v.x); }
        public static Vector3 yzy(this Vector3 v) { return V3(v.y, v.z, v.y); }
        public static Vector3 yzz(this Vector3 v) { return V3(v.y, v.z, v.z); }
        public static Vector3 yzo(this Vector3 v) { return V3(v.y, v.z, 0f); }
        public static Vector3 yox(this Vector3 v) { return V3(v.y, 0f, v.x); }
        public static Vector3 yoy(this Vector3 v) { return V3(v.y, 0f, v.y); }
        public static Vector3 yoz(this Vector3 v) { return V3(v.y, 0f, v.z); }
        public static Vector3 yoo(this Vector3 v) { return V3(v.y, 0f, 0f); }
        public static Vector3 zxx(this Vector3 v) { return V3(v.z, v.x, v.x); }
        public static Vector3 zxy(this Vector3 v) { return V3(v.z, v.x, v.y); }
        public static Vector3 zxz(this Vector3 v) { return V3(v.z, v.x, v.z); }
        public static Vector3 zxo(this Vector3 v) { return V3(v.z, v.x, 0f); }
        public static Vector3 zyx(this Vector3 v) { return V3(v.z, v.y, v.x); }
        public static Vector3 zyy(this Vector3 v) { return V3(v.z, v.y, v.y); }
        public static Vector3 zyz(this Vector3 v) { return V3(v.z, v.y, v.z); }
        public static Vector3 zyo(this Vector3 v) { return V3(v.z, v.y, 0f); }
        public static Vector3 zzx(this Vector3 v) { return V3(v.z, v.z, v.x); }
        public static Vector3 zzy(this Vector3 v) { return V3(v.z, v.z, v.y); }
        public static Vector3 zzz(this Vector3 v) { return V3(v.z, v.z, v.z); }
        public static Vector3 zzo(this Vector3 v) { return V3(v.z, v.z, 0f); }
        public static Vector3 zox(this Vector3 v) { return V3(v.z, 0f, v.x); }
        public static Vector3 zoy(this Vector3 v) { return V3(v.z, 0f, v.y); }
        public static Vector3 zoz(this Vector3 v) { return V3(v.z, 0f, v.z); }
        public static Vector3 zoo(this Vector3 v) { return V3(v.z, 0f, 0f); }
        public static Vector3 oxx(this Vector3 v) { return V3(0f, v.x, v.x); }
        public static Vector3 oxy(this Vector3 v) { return V3(0f, v.x, v.y); }
        public static Vector3 oxz(this Vector3 v) { return V3(0f, v.x, v.z); }
        public static Vector3 oxo(this Vector3 v) { return V3(0f, v.x, 0f); }
        public static Vector3 oyx(this Vector3 v) { return V3(0f, v.y, v.x); }
        public static Vector3 oyy(this Vector3 v) { return V3(0f, v.y, v.y); }
        public static Vector3 oyz(this Vector3 v) { return V3(0f, v.y, v.z); }
        public static Vector3 oyo(this Vector3 v) { return V3(0f, v.y, 0f); }
        public static Vector3 ozx(this Vector3 v) { return V3(0f, v.z, v.x); }
        public static Vector3 ozy(this Vector3 v) { return V3(0f, v.z, v.y); }
        public static Vector3 ozz(this Vector3 v) { return V3(0f, v.z, v.z); }
        public static Vector3 ozo(this Vector3 v) { return V3(0f, v.z, 0f); }
        public static Vector3 oox(this Vector3 v) { return V3(0f, 0f, v.x); }
        public static Vector3 ooy(this Vector3 v) { return V3(0f, 0f, v.y); }
        public static Vector3 ooz(this Vector3 v) { return V3(0f, 0f, v.z); }
        public static Vector3 ooo(this Vector3 v) { return V3(0f, 0f, 0f); }
        
        public static Vector2 xx(this Vector3 v) { return V2(v.x, v.x); }
        public static Vector2 xy(this Vector3 v) { return V2(v.x, v.y); }
        public static Vector2 xz(this Vector3 v) { return V2(v.x, v.z); }
        public static Vector2 xo(this Vector3 v) { return V2(v.x, 0f); }
        public static Vector2 yx(this Vector3 v) { return V2(v.y, v.x); }
        public static Vector2 yy(this Vector3 v) { return V2(v.y, v.y); }
        public static Vector2 yz(this Vector3 v) { return V2(v.y, v.z); }
        public static Vector2 yo(this Vector3 v) { return V2(v.y, 0f); }
        public static Vector2 zx(this Vector3 v) { return V2(v.z, v.x); }
        public static Vector2 zy(this Vector3 v) { return V2(v.z, v.y); }
        public static Vector2 zz(this Vector3 v) { return V2(v.z, v.z); }
        public static Vector2 zo(this Vector3 v) { return V2(v.z, 0f); }
        public static Vector2 ox(this Vector3 v) { return V2(0f, v.x); }
        public static Vector2 oy(this Vector3 v) { return V2(0f, v.y); }
        public static Vector2 oz(this Vector3 v) { return V2(0f, v.z); }
        public static Vector2 oo(this Vector3 v) { return V2(0f, 0f); }
        
        public static Vector3 xxx(this Vector2 v) { return V3(v.x, v.x, v.x); }
        public static Vector3 xxy(this Vector2 v) { return V3(v.x, v.x, v.y); }
        public static Vector3 xxo(this Vector2 v) { return V3(v.x, v.x, 0); }
        public static Vector3 xyx(this Vector2 v) { return V3(v.x, v.y, v.x); }
        public static Vector3 xyy(this Vector2 v) { return V3(v.x, v.y, v.y); }
        public static Vector3 xyo(this Vector2 v) { return V3(v.x, v.y, 0); }
        public static Vector3 xox(this Vector2 v) { return V3(v.x, 0, v.x); }
        public static Vector3 xoy(this Vector2 v) { return V3(v.x, 0, v.y); }
        public static Vector3 xoo(this Vector2 v) { return V3(v.x, 0, 0); }
        public static Vector3 yxx(this Vector2 v) { return V3(v.y, v.x, v.x); }
        public static Vector3 yxy(this Vector2 v) { return V3(v.y, v.x, v.y); }
        public static Vector3 yxo(this Vector2 v) { return V3(v.y, v.x, 0); }
        public static Vector3 yyx(this Vector2 v) { return V3(v.y, v.y, v.x); }
        public static Vector3 yyy(this Vector2 v) { return V3(v.y, v.y, v.y); }
        public static Vector3 yyo(this Vector2 v) { return V3(v.y, v.y, 0); }
        public static Vector3 yox(this Vector2 v) { return V3(v.y, 0, v.x); }
        public static Vector3 yoy(this Vector2 v) { return V3(v.y, 0, v.y); }
        public static Vector3 yoo(this Vector2 v) { return V3(v.y, 0, 0); }
        public static Vector3 oxx(this Vector2 v) { return V3(0, v.x, v.x); }
        public static Vector3 oxy(this Vector2 v) { return V3(0, v.x, v.y); }
        public static Vector3 oxo(this Vector2 v) { return V3(0, v.x, 0); }
        public static Vector3 oyx(this Vector2 v) { return V3(0, v.y, v.x); }
        public static Vector3 oyy(this Vector2 v) { return V3(0, v.y, v.y); }
        public static Vector3 oyo(this Vector2 v) { return V3(0, v.y, 0); }
        public static Vector3 oox(this Vector2 v) { return V3(0, 0, v.x); }
        public static Vector3 ooy(this Vector2 v) { return V3(0, 0, v.y); }

        public static Vector2 xx(this Vector2 v) { return V2(v.x, v.x); }
        public static Vector2 xy(this Vector2 v) { return V2(v.x, v.y); }
        public static Vector2 xo(this Vector2 v) { return V2(v.x, 0f); }
        public static Vector2 yx(this Vector2 v) { return V2(v.y, v.x); }
        public static Vector2 yy(this Vector2 v) { return V2(v.y, v.y); }
        public static Vector2 yo(this Vector2 v) { return V2(v.y, 0f); }
        public static Vector2 ox(this Vector2 v) { return V2(0f, v.x); }
        public static Vector2 oy(this Vector2 v) { return V2(0f, v.y); }
        public static Vector2 oo(this Vector2 v) { return V2(0f, 0f); }

        public static Color Alpha(this Color c, float alpha) {
            return C(c.r, c.g, c.b, alpha);
        }

        public static Color Mix(this Color c, Color o, float alpha) {
            return C(
                c.r + (o.r - c.r) * alpha,
                c.g + (o.g - c.g) * alpha,
                c.b + (o.b - c.b) * alpha,
                c.a + (o.a - c.a) * alpha
            );
        }
        
        public static float Clamp(this float a, float min, float max) {
            return Mathf.Min(Mathf.Max(a, min), max);
        }
        
        public static float Lerp(this float a, float b, float t) {
            return (1f - t) * a + b * t;
        }
        
        public static float InvLerp(this float v, float a, float b) {
            return (v - a) / (b - a);
        }

        public static float Remap(this float v, float iMin, float iMax, float oMin, float oMax) {
            float t = InvLerp(iMin, iMax, v);
            return Lerp(oMin, oMax, t);
        }
        
        public static float InvLerpClamped(this float v, float a, float b) {
            return Mathf.Clamp((v - a) / (b - a), 0f, 1f);
        }

        public static float RemapClamped(this float v, float iMin, float iMax, float oMin, float oMax) {
            float t = InvLerpClamped(iMin, iMax, v);
            return Lerp(oMin, oMax, t);
        }
    }
}
