using System;
using System.Collections.Generic;
using UnityEngine;
using System.Collections;
using GatherAndDefend.LevelEditor;
using System.Linq;
using System.Threading.Tasks;

public static class Extensions
{
    public static async Task WaitWhile(Func<bool> toKeepTrue)
    {
        while (toKeepTrue?.Invoke() == true)
        {
            await Task.Yield();
        }
    }
    public static T[] GetAllComponents<T>(this Component component) where T : Component
    {
        List<T> comps = new List<T>();
        comps.AddRange(component.GetComponents<T>());
        comps.AddRange(component.GetComponentsInChildren<T>());
        return comps.ToArray();
    }
    public static Rect CalculateBounds(this Level level)
    {
        Rect bound = new Rect(0, 0, 0, 0);
        foreach (var tilemap in level)
        {
            foreach (var tile in tilemap)
            {
                bound = bound.GetUpdatedBound((Vector3)tile.Position);
            }
        }
        return bound;
    }
    public static Rect GetUpdatedBound(this Rect bound, Rect other)
    {
        var rect = bound;
        rect.xMin = other.xMin < rect.xMin ? other.xMin : rect.xMin;
        rect.yMin = other.yMin < rect.yMin ? other.yMin : rect.yMin;
        rect.xMax = other.xMax > rect.xMax ? other.xMax : rect.xMax;
        rect.yMax = other.yMax > rect.yMax ? other.yMax : rect.yMax;
        return rect;
    }
    public static Rect GetUpdatedBound(this Rect bound, Vector2 position)
    {
        var rect = bound;
        rect.xMin = position.x < rect.xMin ? position.x : rect.xMin;
        rect.yMin = position.y < rect.yMin ? position.y : rect.yMin;
        rect.xMax = position.x > rect.xMax ? position.x : rect.xMax;
        rect.yMax = position.y > rect.yMax ? position.y : rect.yMax;
        return rect;
    }
    public static int ToInt(this object jobj)
    {
        return int.Parse(jobj.ToString());
    }
    public static float ToFloat(this object jobj)
    {
        return float.Parse(jobj.ToString());
    }
    public static Vector3 ToVector3(this object jobj)
    {
        var p_enum = (jobj as IEnumerable).GetEnumerator();
        float[] p_array = new float[3];

        p_enum.MoveNext();
        p_array[0] = float.Parse(p_enum.Current.ToString());
        p_enum.MoveNext();
        p_array[1] = float.Parse(p_enum.Current.ToString());
        p_enum.MoveNext();
        p_array[2] = float.Parse(p_enum.Current.ToString());

        return new Vector3(p_array[0], p_array[1], p_array[2]);
    }
    public static bool ToBool(this object jobj) => bool.Parse(jobj.ToString());
    public static GameObject Create(this GameObject prefab, Vector3 position, Quaternion rotation = default, Transform parent = null)
    {
        if (rotation == default) rotation = Quaternion.identity;
        var instance = GameObject.Instantiate(prefab, position, rotation);
        instance.transform.SetParent(parent);
        instance.name = prefab.name;
        return instance;

    }
    public static T GetComponentInChildren<T>(this Component obj, string name) where T : Component
    {
        foreach (var comp in obj.GetComponentsInChildren<T>())
        {
            if (comp.name == name) return comp;
        }
        return null;
    }
    public enum LevelObjectType { GameObject, Tile }

    /// <summary>
    /// check if an object is positioned on the same postion as an other (with a size 1 buffer)
    /// </summary>
    /// <param name="vect"></param>
    /// <param name="tilePosition"></param>
    /// <returns></returns>
    public static bool IsContainedIn(this Vector3 vect, Vector3 tilePosition)
    {
        return Vector2.Distance(vect, tilePosition) < 0.5f;
    }
    public static Vector2 RandomInRectangle(float x, float y)
    {
        var randX = UnityEngine.Random.Range(0, x);
        var randY = UnityEngine.Random.Range(0, y);
        return new Vector2(randX, randY);
    }
    public static T Minimum<T>(this IEnumerable<T> list, Func<T, float> func)
    {
        if (list.Count() < 1) throw new Exception("in " + nameof(Minimum) + " : Cannot find minimum of empty list : " + nameof(list));
        T minT = list.ElementAt(0);
        float minVal = func(minT);
        
        foreach (var obj in list)
        {
            var newVal = func(obj);
            if (minVal > newVal)
            {
                minT = obj;
                minVal = newVal;
            }
        }
        return minT;
    }
    public static IEnumerator FadeTo(this CanvasGroup item, float value, float duration)
    {
        var delta = value - item.alpha;
        var direction = Mathf.Sign(delta);

        while (Mathf.Abs(value - item.alpha) > 0)
        {
            item.alpha += Time.deltaTime * direction / duration;
            yield return null;
        }
        item.alpha = value;
    }
}