using UnityEngine;
using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;

public static class Util
{
	private const float EPSILON = 1e-3f;
	
	[System.Diagnostics.Conditional("UNITY_EDITOR")]
	public static void AssertTrue(bool condition, string msg)
	{
		if (!condition)
		{
			Debug.Break();
			throw new InvalidOperationException("Assert failed: " + msg + "\n" + new System.Diagnostics.StackTrace(1).ToString());
		}
	}

    private const string LOWER_CASE_NORMAL_CHARACTERS = "abcdefghijklmnopqrstuvwxyz";
    private const string UPPER_CASE_NORMAL_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private const string LOWER_CASE_ACCENTUADED_CHARACTERS = "àáâãçéêíóôõ";
    private const string UPPER_CASE_ACCENTUADED_CHARACTERS = "ÀÁÂÃÇÉÊÍÓÔÕ";
    private const string SPACE_ACCENTS_SYMBOLS_AND_MARKS = " !'(),-.:;?";
    private const string NUMBERS = "0123456789";
    private const string CHARACTERS_STRING =
        LOWER_CASE_NORMAL_CHARACTERS +
        UPPER_CASE_NORMAL_CHARACTERS +
        LOWER_CASE_ACCENTUADED_CHARACTERS +
        UPPER_CASE_ACCENTUADED_CHARACTERS +
        SPACE_ACCENTS_SYMBOLS_AND_MARKS +
        NUMBERS;

    public static Dictionary<char, float> CreateCharactersLengthDictionary(tk2dTextMesh textMesh)
    {
        Dictionary<char, float> charactersLength = new Dictionary<char, float>();
        CHARACTERS_STRING.ForEach(c =>
        {
            charactersLength[c] = textMesh.GetMeshDimensionsForString(c.ToString()).x;
        });
        return charactersLength;
    }

    private static readonly char BLANK = char.Parse(" ");
    private static readonly char NEWLINE = char.Parse("\n");

    public static string FormatTk2dText(tk2dTextMesh textMesh, float maxLineLength, string text, Dictionary<char, float> charactersLength)
    {
        Util.AssertTrue(text.TrueForAll(c => c != NEWLINE), "Newline character found on the following text: " + text);
        int currentIndex = 0;
        float totalLength = 0.0f;
        int lineQtt = 1;
        while (currentIndex < text.Length)
        {
            if (text[currentIndex] == NEWLINE)
            {
                totalLength = 0.0f;
                lineQtt++;
            }
            else if (charactersLength.ContainsKey(text[currentIndex]))
            {
                totalLength += charactersLength[text[currentIndex]];
            }
            else
            {
                Debug.LogWarning("Length not found for the character: " + text[currentIndex]);
            }
            if (totalLength > maxLineLength)
            {
                while (text[currentIndex] != BLANK)
                {
                    currentIndex--;
                }
                text = text.Substring(0, currentIndex) + NEWLINE + text.Substring(currentIndex + 1, text.Length - currentIndex - 1);
                totalLength = 0.0f;
                lineQtt++;
            }
            currentIndex++;
        }
        return text;
    }

    /*public static void ClearMemory(System.Action postClearAction = null)
    {
        AsyncManager.Execute(null, Internal_ClearMemory, postClearAction);
    }

    private static IEnumerator Internal_ClearMemory(System.Action postClearAction = null)
    {
        System.GC.Collect();
        System.GC.WaitForPendingFinalizers();
        AsyncOperation op = Resources.UnloadUnusedAssets();
        yield return op;
        System.GC.Collect();
        System.GC.WaitForPendingFinalizers();
        if (postClearAction != null)
        {
            postClearAction();
        }
    }*/

    public static bool CheckNonNull(params object[] gameObjs)
    {
        return gameObjs.TrueForAll(obj => obj != null);
    }
	
	public static bool Aproximate(this float num1, float num2, float precision = EPSILON)
	{
		return Mathf.Abs (num1 - num2) < precision;
	}
	
	public static int Compare(this float num1, float num2, float precision = EPSILON)
	{
		float diff = num1 - num2;
		return Mathf.Abs(diff) < precision ? 0 : (int)Mathf.Sign(diff);
	}
	
	public static bool Between(this int value, int lesser, int greater)
	{
		// << 1 equivale a mutliplicar por 2
		return Mathf.Abs(value << 1 - (greater + lesser)) < Mathf.Abs (greater - lesser);
	}
	
	public static bool Between(this float value, float lesser, float greater)
	{
		return Mathf.Abs(value * 2 - (greater + lesser)) < Mathf.Abs (greater - lesser);
	}
	
	public static IEnumerable<Transform> GetChildren(this Transform parent)
	{
		AssertTrue(parent != null, "'parent' must be non null");
		
		return IEnumerableExtensions.Range(0, parent.childCount) // De 0 - (childCount - 1)
			.Select((idx) => parent.GetChild(idx)); // Pegue o filho em idx
	}
	
	public static T GetComponent<T>(this Component component, bool force) where T : Component
	{
		AssertTrue(force && component.GetComponent<T>() != null, "Component '" + typeof(T).Name + "' not found");
		return component.GetComponent<T>();
	}
	
	public static T GetComponent<T>(this GameObject gameObject, bool force) where T : Component
	{
		AssertTrue(force && gameObject.GetComponent<T>() != null, "Component '" + typeof(T).Name + "' not found");
		return gameObject.GetComponent<T>();
	}
	
	/*public static T AddComponent<T>(this GameObject gameObject, params object[] parameters) where T : Component, IInitiable
	{
		T component = gameObject.AddComponent<T>();
		component.Init(parameters);
		return component;
	}*/
	
	public static IEnumerable<Type> GetSubclasses<T>(bool concreteOnly)
	{
		return GetSubclasses(typeof(T), concreteOnly);
	}
	
	public static IEnumerable<Type> GetSubclasses(this Type parent, bool concreteOnly)
	{
		Util.AssertTrue(parent != null, "'parent' must be non-null");
		Util.AssertTrue(parent.IsClass, "Type '" + parent.Name + "' is not a class type");
		
		return Assembly.GetAssembly(parent).GetTypes()
			.Where(type => concreteOnly ? // Se for apenas concreta
				!(type.IsAbstract || type.IsInterface) && type.IsSubclassOf(parent) // não retorna abstratas e interfaces
				: type.IsSubclassOf(parent));
	}
	
	public static IEnumerable<Type> GetImplementing<T>(bool concreteOnly)
	{
		return GetImplementing(typeof(T), concreteOnly);
	}
	
	public static IEnumerable<Type> GetImplementing(this Type parentInterface, bool concreteOnly)
	{
		Util.AssertTrue(parentInterface != null, "'parent' must be non-null");
		Util.AssertTrue(parentInterface.IsInterface, "Type '" + parentInterface.Name + "' is not a interface type");
		
		//parent.GetInterfaces().
		
		return Assembly.GetAssembly(parentInterface).GetTypes()
			.Where(type => concreteOnly ? // Se for apenas concreta
				!(type.IsAbstract || type.IsInterface) && type.GetInterfaces().Contains(parentInterface) // não retorna abstratas e interfaces
				: type.GetInterfaces().Contains(parentInterface));
	}
	
	public static T[] Merge<T>(T[] first, T[] second)
	{
		Util.AssertTrue(first != null && second != null, "'All parameters must be non-null");
		
		T[] merged = new T[first.Length + second.Length];
		first.CopyTo(merged, 0);
		second.CopyTo(merged, first.Length);
		
		return merged;
	}
	
	public static T[] Merge<T>(T[] first, T[] second, T[] third)
	{
		Util.AssertTrue(first != null && second != null && third != null, "'All parameters must be non-null");
		
		T[] merged = new T[first.Length + second.Length + third.Length];
		first.CopyTo(merged, 0);
		second.CopyTo(merged, first.Length);
		third.CopyTo(merged, first.Length + second.Length);
		return merged;
	}

    public static T[] Merge<T>(T[] first, T[] second, T[] third, T[] fourth)
    {
        Util.AssertTrue(first != null && second != null && third != null && fourth != null, "'All parameters must be non-null");

        T[] merged = new T[first.Length + second.Length + third.Length + fourth.Length];
        first.CopyTo(merged, 0);
        second.CopyTo(merged, first.Length);
        third.CopyTo(merged, first.Length + second.Length);
        fourth.CopyTo(merged, first.Length + second.Length + third.Length);
        return merged;
    }

    public static T[] Merge<T>(T[] first, T[] second, T[] third, T[] fourth, T[] fifth)
    {
        Util.AssertTrue(first != null && second != null && third != null && fourth != null && fifth != null, "'All parameters must be non-null");

        T[] merged = new T[first.Length + second.Length + third.Length + fourth.Length + fifth.Length];
        first.CopyTo(merged, 0);
        second.CopyTo(merged, first.Length);
        third.CopyTo(merged, first.Length + second.Length);
        fourth.CopyTo(merged, first.Length + second.Length + third.Length);
        fifth.CopyTo(merged, first.Length + second.Length + third.Length + fourth.Length);
        return merged;
    }
	
	public static TNumber Get<TNumber>(TNumber value) where TNumber : struct, IComparable, IFormattable, IConvertible, IComparable<TNumber>, IEquatable<TNumber>
	{
		Util.AssertTrue(value.IsNumeric(), "'value' is not a numeric type");
		return (TNumber)value;
	}
	
	public static TEnum Get<TEnum>(ValueType value) where TEnum : struct, IComparable, IFormattable, IConvertible
	{
		Util.AssertTrue(value is Enum, "'value' is not a Enum type");
		return (TEnum)value;
	}
	
	public static bool IsNumeric(this ValueType value)
	{
		return
			value is Byte ||
			value is Int32 ||
			value is Int64 ||
			value is SByte ||
			value is UInt16 ||
			value is UInt32 ||
			value is UInt64 ||
			value is Decimal ||
			value is Double ||
			value is Single;
	}
	
	public static bool IsFloat(this ValueType value) 
	{         
		return
			value is Decimal ||
			value is Double ||
			value is Single;
	}

	public static bool IsInteger(this ValueType value)
	{
		return
			value is Byte ||
			value is Int32 ||
			value is Int64 ||
			value is SByte ||
			value is UInt16 ||
			value is UInt32 ||
			value is UInt64;
	}
	
	public static void Repeat(int times, Action action)
	{
		while (times-- > 0) { action(); }
	}
	
	public static object GetDefault(this Type type)
	{
		if (type == null)
		{
			throw new ArgumentNullException("type");
		}
		if (type.IsValueType)
		{
			return Activator.CreateInstance(type);
		}
		return null;
	}

    public static void TransportObject(GameObject obj, Vector3 fromPosition, Camera fromCamera, Camera toCamera, float cameraDistance = 15.0f)
    {
        obj.transform.position = TransportPosition(fromPosition, fromCamera, toCamera, cameraDistance);
    }

    public static Vector3 TransportPosition(Vector3 fromPosition, Camera fromCamera, Camera toCamera, float cameraDistance = 15.0f)
    {
        Vector3 viewportPos = fromCamera.WorldToViewportPoint(fromPosition);
        Vector3 newPos = toCamera.ViewportToWorldPoint(viewportPos);
        Vector3 adjustedNewPos = new Vector3(newPos.x, newPos.y, toCamera.gameObject.transform.position.z + cameraDistance);
        return adjustedNewPos;
    }

    /*public static void WaitFrameAndExecute(Action action)
    {
        AsyncManager.Execute(null, InternalWaitFrame, action);
        //AsyncManager.Execute(null, action => { yield return null; action(); }, action);
    }
    private static IEnumerator InternalWaitFrame(Action action)
    {
        yield return null;
        action();
    }*/
}