using System.Linq;
using UnityEngine;
using System;
using System.Collections.Generic;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;
using UnityEngine.EventSystems;
using UnityEngine.Events;

public static class Extensions
{
    public static T PopFirst<T>(this IList<T> t)
    {
        T element = t[0];
        t.RemoveAt(0);
        return element;       
    }

    public static void PushFirst<T>(this IList<T> t, T element)
    {
        t.Insert(0, element);
    }

    public static T PopLast<T>(this IList<T> t)
    {
        T element = t[t.Count - 1];
        t.RemoveAt(t.Count - 1);
        return element;
    }

    public static void PushLast<T>(this IList<T> t, T element)
    {
        t.Add(element);
    }


    public static T PickRandom<T>(this IEnumerable<T> ie)
    {
        List<T> t = ie as List<T> ?? ie.ToList();
        if(t.Count == 0)
		{
			Debug.LogError("Range is zero!");
			return default(T);
		}
		return t[Random.Range(0, t.Count)];
	}

	public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
	{
		if (val.CompareTo(max) > 0) return max;
		if (val.CompareTo(min) < 0) return min;
		return val;
	}

    public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> selector)
    {
        return source.MinBy(selector, Comparer<TKey>.Default);
    }

    public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
        Func<TSource, TKey> selector, IComparer<TKey> comparer)
    {
        source.ThrowIfNull("source");
        selector.ThrowIfNull("selector");
        comparer.ThrowIfNull("comparer");
        using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
        {
            if (!sourceIterator.MoveNext())
            {
                throw new InvalidOperationException("Sequence was empty");
            }
            TSource min = sourceIterator.Current;
            TKey minKey = selector(min);
            while (sourceIterator.MoveNext())
            {
                TSource candidate = sourceIterator.Current;
                TKey candidateProjected = selector(candidate);
                if (comparer.Compare(candidateProjected, minKey) < 0)
                {
                    min = candidate;
                    minKey = candidateProjected;
                }
            }
            return min;
        }
    }

    public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source,
            Func<TSource, TKey> selector)
    {
        return source.MaxBy(selector, Comparer<TKey>.Default);
    }


    public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source,
            Func<TSource, TKey> selector, IComparer<TKey> comparer)
    {
        source.ThrowIfNull("source");
        selector.ThrowIfNull("selector");
        comparer.ThrowIfNull("comparer");
        using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
        {
            if (!sourceIterator.MoveNext())
            {
                throw new InvalidOperationException("Sequence was empty");
            }
            TSource max = sourceIterator.Current;
            TKey maxKey = selector(max);
            while (sourceIterator.MoveNext())
            {
                TSource candidate = sourceIterator.Current;
                TKey candidateProjected = selector(candidate);
                if (comparer.Compare(candidateProjected, maxKey) > 0)
                {
                    max = candidate;
                    maxKey = candidateProjected;
                }
            }
            return max;
        }
    }

    public static T ThrowIfNull<T>(this T value, string variableName) where T : class
    {
        if (value == null)
        {
            throw new NullReferenceException(string.Format("Value is Null: {0}", variableName));
        }

        return value;
    }

    public static void SetAlpha(this Renderer renderer, float newAlpha)
    {
        Color c = renderer.material.color;
        c.a = newAlpha;
        renderer.material.color = c;
    }

    public static float GetAlpha(this Renderer renderer)
    {
        return renderer.material.color.a;
    }

    public static void SetColor(this Renderer renderer, Color color) //not alpha
    {
        Color c = renderer.material.color;
        c.r = color.r;
        c.g = color.g;
        c.b = color.b;
        renderer.material.color = c;
    }

    public static Color GetColor(this Renderer renderer)
    {
        return renderer.material.color;
    }


    public static void SetColors(this IEnumerable<MeshRenderer> renderers, Color color)
    {
        foreach (Renderer renderer in renderers)
        {
            if (renderer == null) continue;
            Color c = renderer.material.color;
            if (c.r != color.r || c.g != color.g || c.b != color.b)
            {
                c.r = color.r;
                c.g = color.g;
                c.b = color.b;
                renderer.material.color = c;
            }
        }
    }


    public static void SetColors(this MeshRenderer[] renderers, Color color)
    {
        for (int i = 0; i < renderers.Length; i++)
        {
            Renderer renderer = renderers[i];
            if (renderer == null) continue;
            Color c = renderer.material.color;
            if (c.r != color.r || c.g != color.g || c.b != color.b)
            {
                c.r = color.r;
                c.g = color.g;
                c.b = color.b;
                renderer.material.color = c;
            }
        }
    }

    public static T GetComponentInAncestors<T>(this GameObject gameObject) where T : Component
    {
        return gameObject.transform.GetComponentInAncestors<T>();
    }

    public static T GetComponentInAncestors<T>(this Transform transform) where T : Component
    {
        var t = transform;
        T component = null;
        while (t != null && (component = t.GetComponent<T>()) == null)
        {
            t = t.parent;
        }
        return component;
    }

    public static Vector3 Clamp(this Vector3 val, Vector3 min, Vector3 max)
    {
        return new Vector3(Mathf.Clamp(val.x, min.x, max.x),
                           Mathf.Clamp(val.y, min.y, max.y),
                           Mathf.Clamp(val.z, min.z, max.z));
    }
    public static Vector2 Clamp(this Vector2 val, Vector2 min, Vector2 max)
    {
        return new Vector2(Mathf.Clamp(val.x, min.x, max.x),
                           Mathf.Clamp(val.y, min.y, max.y));
    }

    public static T GetOrAddComponent<T>(this GameObject go) where T : Component
    {
        var component = go.GetComponent<T>();
        if (component == null)
        {
            component = go.AddComponent<T>();
        }
        return component;
    }
    public static T GetOrAddComponent<T>(this Transform t) where T : Component
    {
        return t.gameObject.GetOrAddComponent<T>();
    }

    public static void AddListener(this EventTrigger eventTrigger, EventTriggerType type, UnityAction<BaseEventData> action)
    {
        if (eventTrigger.delegates == null)
        {
            eventTrigger.delegates = new List<EventTrigger.Entry>();
        }
        var entry = eventTrigger.delegates.Find(e => e.eventID == type);

        if (entry == null)
        {
            entry = new EventTrigger.Entry();
            entry.eventID = type;
            entry.callback = new EventTrigger.TriggerEvent();
            
            eventTrigger.delegates.Add(entry);
        }
        entry.callback.AddListener(action);

    }

    public static void SetSelected(this EventSystem eventSystem, MonoBehaviour selected)
    {
        var pointer = new BaseEventData(eventSystem);
        eventSystem.SetSelectedGameObject(selected.gameObject, pointer);
    }
    
    public static void SetSelected(this EventSystem eventSystem, GameObject selected)
    {
        var pointer = new BaseEventData(eventSystem);
        eventSystem.SetSelectedGameObject(selected, pointer);
    }

}
