// ================================================================================================
// Copyright (c) 2012 All Rights Reserved
// Licensed Materials - Property of Zero One
// 
// (c) Copyright Zero One SAS - 2023
// ================================================================================================

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

public static class ZeroOneExtensions
{
    public static void SetLocalPositionX(this Transform transform, float xValue)
    {
        transform.localPosition = new Vector3(xValue, transform.localPosition.y, transform.localPosition.z);
    }

    public static void SetLocalPositionY(this Transform transform, float yValue)
    {
        transform.localPosition = new Vector3(transform.localPosition.x, yValue, transform.localPosition.z);
    }

    public static void SetLocalPositionZ(this Transform transform, float zValue)
    {
        transform.localPosition = new Vector3(transform.localPosition.x, transform.localPosition.y, zValue);
    }

    public static void SetPositionY(this Transform transform, float yValue)
    {
        transform.position = new Vector3(transform.position.x, yValue, transform.position.z);
    }

	public static Vector2 GetVector2(this Vector3 vector3)
	{
		return new Vector2(vector3.x, vector3.y);
	}

	//---------------------------------------------------------------------------------------------
	// Destroy all children of a Transform

	public static void DestroyChildren(this Transform transform)
    {
        foreach (Transform child in transform)
        {
            GameObject.Destroy(child.gameObject);
        }
    }

    //---------------------------------------------------------------------------------------------
    // Destroy all children of a Transform but param transform

    public static void DestroyChildren(this Transform transform, Transform but = null)
    {
        foreach (Transform child in transform)
        {
            if (child != but)
            {
                GameObject.Destroy(child.gameObject);
            }
        }
    }

    //---------------------------------------------------------------------------------------------
    // Destroy all children of a Transform but param transform

    public static void DestroyChildren(this Transform transform, List<Transform> but = null)
    {
        foreach (Transform child in transform)
        {
            if (!but.Contains(child))
            {
                GameObject.Destroy(child.gameObject);
            }
        }
    }

    //---------------------------------------------------------------------------------------------
    // Reset All transforms

    public static void ResetXForm(this Transform transform)
    {
        transform.localPosition = Vector3.zero;
        transform.localRotation = Quaternion.identity;
        transform.localScale = Vector3.one;
    }

    //---------------------------------------------------------------------------------------------
    // Encode a Texture in the best format for DYG

    public static byte[] Encode(this Texture2D texture)
    {
        if (texture.format == TextureFormat.RGBA32 ||
            texture.format == TextureFormat.ARGB32)
        {
            //Debug.Log("Alpha detected");
            return texture.EncodeToPNG();
        }
        else
        {
            Debug.Log("texture.format = " + texture.format);

            if (texture.format == TextureFormat.ETC2_RGBA8)
            {
                return texture.GetRawTextureData();
            }
            return texture.EncodeToJPG(80);
        }
    }

    //---------------------------------------------------------------------------------------------
    // Encode a Texture in the best format for DYG
    public static bool HasAlpha(this Texture2D texture)
    {
        if (texture.format != TextureFormat.ARGB32 && texture.format != TextureFormat.RGBA32)
        {
            return false;
        }

        int lenght = texture.width * texture.height;

        Color32[] pixels = texture.GetPixels32();

        int nb = 0;
        // On fait le crop magic
        for (int index = 0; index < lenght; ++index)
        {
            if (pixels[index].a < 255)
            {
                if (++nb > lenght * 0.001f)
                {
                    return true;
                }
            }
        }

        return false;
    }

	//---------------------------------------------------------------------------------------------
	// return the number of caracter c in a string

	public static string FirstCharToUpper(this string text)
	{
		if (string.IsNullOrEmpty(text))
		{
			return text;
		}

		return char.ToUpper(text[0]) + text.Substring(1).ToLower();
	}

	//---------------------------------------------------------------------------------------------
	// Destroy all children of a Transform but param transform

	public static void DestroyImmediateChildren(this Transform transform, Transform but = null)
    {
        for (int i = transform.childCount - 1; i >= 0; i--)
        {
            Transform child = transform.GetChild(i);
            if (child != but)
            {
                GameObject.DestroyImmediate(child.gameObject);
            }
        }
    }

    //---------------------------------------------------------------------------------------------
    // Add a point in a Rect: it grow the rect size

    public static void Add(this ref Rect rect, Vector2 point)
    {
        if (point.x < rect.x)
        {
            rect.width += rect.x - point.x;
            rect.x = point.x;
        }
        else if (point.x > rect.xMax)
        {
            rect.width += point.x - rect.xMax;
        }

        if (point.y < rect.y)
        {
            rect.height += rect.y - point.y;
            rect.y = point.y;
        }
        else if (point.y > rect.yMax)
        {
            rect.height += point.y - rect.yMax;
        }
    }

    public static string Capitalize(this string str)
    {
        if (str == null) return null;
		if (str == "") return "";

		if (str.Length == 1) return str.ToUpper();

		return char.ToUpper(str[0]) + str[1..].ToLower();
	}

    //---------------------------------------------------------------------------------------------
    // Get a random point in a Rect
    public static Vector2 Random(this Rect rect)
    {
        return new Vector2(UnityEngine.Random.Range(rect.xMin, rect.xMax), UnityEngine.Random.Range(rect.yMin, rect.yMax));
    }

	//---------------------------------------------------------------------------------------------
	public static T RandomPow<T>(this List<T> list, float probaFirst = 0.0f)
	{
		if (list.Count == 0)
		{
			return default;
		}

		if (UnityEngine.Random.value < probaFirst)
		{
			return list[0];
		}

		//old
		//return list[(int)(Mathf.Pow(UnityEngine.Random.value, 0.5f) * list.Count)];

		// val = Σ(1,n)n = n(n+1)/2 = n²/2 + n/2
		int val = UnityEngine.Random.Range(0, list.Count * (list.Count + 1) / 2);

		// n²/2 + n/2 - val = 0
		// Δ = 1/4 + 2*val
		// x1 = -1/2 - √(1/4 + 2val) (negative value)
		// x2 = -1/2 + √(1/4 + 2val)
		// x2 = (0.5f + Mathf.Sqrt(0.25f + 2.0f * (float)val))
		int index = (int)(0.5f + Mathf.Sqrt(0.25f + 2.0f * (float)val));

		return list[list.Count - index];
	}

	//---------------------------------------------------------------------------------------------
	// Multiply the 2 values 
	public static Vector2 Scale(this Vector2 vector2, float xFactor, float yFactor)
    {
        return new Vector2(vector2.x * xFactor, vector2.y * yFactor);
    }

    //---------------------------------------------------------------------------------------------
    // Rotate a Vector2
    public static Vector2 Rotate(this Vector2 vector2, float radians)
    {
        var ca = Mathf.Cos(radians);
        var sa = Mathf.Sin(radians);
        return new Vector2(ca * vector2.x - sa * vector2.y, sa * vector2.x + ca * vector2.y);
    }

	//---------------------------------------------------------------------------------------------
	// Return the direction of a Vector from -2Pi to 2Pi
	public static float Angle(this Vector2 vector2)
	{
		return Mathf.Atan2(vector2.x, vector2.y);
	}

	//---------------------------------------------------------------------------------------------
	// Return the direction of a Vector from -2Pi to 2Pi
	public static float AnglePositif(this Vector2 vector2)
	{
		float angle = Mathf.Atan2(vector2.x, vector2.y);

        if (angle < 0) angle += 2f * Mathf.PI;

        return angle;

	}

	//---------------------------------------------------------------------------------------------
	// Get a random object from a list

	public static T Random<T>(this List<T> list)
    {
        if (list.Count == 0)
        {
            return default;
        }

        return list[UnityEngine.Random.Range(0, list.Count)];
    }

	//---------------------------------------------------------------------------------------------
	// Print a list

	public static void AddIfNotExist<T>(this List<T> list, T elem)
	{
		if (!list.Contains(elem))
        {
			list.Add(elem);
		}
	}


	//---------------------------------------------------------------------------------------------
	// Print a list

	public static string ToJson<T>(this List<T> list, bool withQuotationMarks = true)
    {
        if (list == null || list.Count <= 0)
        {
            return "[]";
        }

        StringBuilder result = new StringBuilder("[");

        bool first = true;
        foreach (T t in list)
        {
            if (withQuotationMarks)
            {
                result.Append(first ? "\"" : "\",\"");
            }
            else if (!first)
            {
				result.Append(",");
			}
            result.Append(t?.ToString());
            first = false;
        }

        if (withQuotationMarks)
        {
            result.Append("\"]");
        }
        else
        {
			result.Append("]");
		}

        return result.ToString();
    }

	//---------------------------------------------------------------------------------------------
	// Print a list

	public static void FromJson(this List<string> list, string json)
	{
        list.Clear();

        if (string.IsNullOrEmpty(json) || !json.StartsWith('[') || !json.EndsWith(']'))
        {
            return;
        }

        json = json[1..^1];

        string[] values = json.Split(',');

        foreach(string val in values)
        {
			if (string.IsNullOrEmpty(val) || !val.StartsWith('"') || !val.EndsWith('"'))
			{
                Debug.LogError("Failed to parse List<string> " + val);
				continue;
			}

			list.Add(val[1..^1]);
		}
	}

    //---------------------------------------------------------------------------------------------
    // Print a list

    public static void Load(this List<string> list, string json)
    {
        list.Clear();

        if (string.IsNullOrEmpty(json))
        { 
            return;
        }

        try
        {
            string[] str = json.Trim(new char[] { '[', ']' }).Replace("\"", "").Split(new char[] { ',' });

            foreach (string val in str)
            {
                if (val.Length > 0)
                {
                    list.Add(val);
                }
            }
        }
        catch (System.Exception e)
        {
            Debug.LogException(e);
        }
    }

    //---------------------------------------------------------------------------------------------
    // Get a random list objects from a list

    public static List<T> Random<T>(this List<T> list, int count)
    {
        if (count < 0) return null;
        if (count >= list.Count) return new List<T>(list);

        List<T> newList = new List<T>(list);

        /*if (count < list.Count / 4)
		{
			// optimization if count is small
			List<T> newList2 = new List<T>();
			while (count >= newList2.Count)
			{
				newList2.Add(newList.RandomRemove<T>());
			}

			return newList2;
		}*/

        int n = 0;
        while (n < count)
        {
            int index = UnityEngine.Random.Range(n, newList.Count);
            if (n != index) (newList[n], newList[index]) = (newList[index], newList[n]);
            n++;
        }

        return newList.GetRange(0, count);
    }

    //---------------------------------------------------------------------------------------------
    // Randomize a list

    public static void Randomize<T>(this List<T> list)
    {
        int n = list.Count;
        while (n > 1)
        {
            n--;
            int k = UnityEngine.Random.Range(0, n + 1);
            T value = list[k];
            list[k] = list[n];
            list[n] = value;
        }
    }

    //---------------------------------------------------------------------------------------------
    // Remove a list in a list

    public static void RemoveRange<T>(this List<T> list, List<T> listToRemove)
    {
        foreach (var item in listToRemove)
        {
            list.Remove(item);
        }
    }

    
    //---------------------------------------------------------------------------------------------
    // Get a random object from a list and remove it

    public static T RandomRemove<T>(this List<T> list)
    {
        if (list.Count == 0)
        {
            return default(T);
        }

        int index = UnityEngine.Random.Range(0, list.Count);

        T value = list[index];
        list.RemoveAt(index);
        return value;
    }

    //---------------------------------------------------------------------------------------------
    public static int NextInt(int min, int max)
    {
        return UnityEngine.Random.Range(min, max);
    }

    //---------------------------------------------------------------------------------------------
    public static float DistanceSqr(this Vector2 a, Vector2 b)
    {
        float num = a.x - b.x;
        float num2 = a.y - b.y;
        return (float)(num * num + num2 * num2);
    }

    //---------------------------------------------------------------------------------------------
    // Get a random object from a list and remove it

    public static int Count(this string text, char c)
    {
        if (string.IsNullOrEmpty(text))
        {
            return 0;
        }

        int count = 0;
        for (int i = 0; i < text.Length; i++)
        {
            if (text[i] == c) count++;
        }

        return count;
    }

    //---------------------------------------------------------------------------------------------
    // Get the rect in the current Screen of a RectTransform
    // Null for overlay

    public static Rect GetScreenRect(this RectTransform rectTransform, Camera camera)
    {
        if (camera == null)
        {
			camera = Camera.main;
		}

        Vector3[] corners = new Vector3[4];
        Vector3[] screenCorners = new Vector3[2];

        rectTransform.GetWorldCorners(corners);

        screenCorners[0] = camera.WorldToScreenPoint(corners[1]);
        screenCorners[1] = camera.WorldToScreenPoint(corners[3]);

        Rect r = new Rect(screenCorners[0], screenCorners[1] - screenCorners[0]);

        if (r.height < 0f)
        {
            r.height = -r.height;
            r.y -= r.height;
        }

        if (r.width < 0f)
        {
            r.width = -r.width;
            r.x -= r.width;
        }

        return r;
    }

    //---------------------------------------------------------------------------------------------
    public static Vector2 PointToNormalized(this Bounds bounds, Vector2 point)
    {
        return new Vector2(Mathf.InverseLerp(bounds.min.x, bounds.max.x, point.x), Mathf.InverseLerp(bounds.min.y, bounds.max.y, point.y));
    }

    //---------------------------------------------------------------------------------------------

    public static Texture2D TakeScreenshot(Camera camera, int res = 128)
    {
        RenderTexture rt = new RenderTexture(res, res, 24);
        camera.targetTexture = rt;
        Texture2D screenShot = new Texture2D(res, res, TextureFormat.RGBA32, false);
        camera.Render();
        RenderTexture.active = rt;
        screenShot.ReadPixels(new Rect(0, 0, res, res), 0, 0);
        screenShot.Apply();
        camera.targetTexture = null;
        RenderTexture.active = null; // JC: added to avoid errors

        return screenShot;
    }

    //---------------------------------------------------------------------------------------------

    public static Texture2D Blur(this Texture2D image, int blurSize = 8)
    {
        int width = image.width;
        int height = image.height;

        Color32[] pixels = image.GetPixels32();
        Color32[] pixelsBlurred = new Color32[width * height];

        // look at every pixel in the blur rectangle
        int avgR = 0, avgG = 0, avgB = 0;
        int aavgR = 0;
        int aavgG = 0;
        int aavgB = 0;
        int blurPixelCount = 0;

        for (int xx = 0; xx < width; xx++)
        {
            for (int yy = 0; yy < height; yy++)
            {
                avgR = 0;
                avgG = 0;
                avgB = 0;
                blurPixelCount = 0;

                Color32 pixel = pixels[xx + yy * width];

                aavgR += pixel.r;
                aavgG += pixel.g;
                aavgB += pixel.b;

                // average the color of the red, green and blue for each pixel in the
                // blur size while making sure you don't go outside the image bounds
                for (int x = Mathf.Max(0, xx - blurSize); (x < xx + blurSize && x < width); x++)
                {
                    for (int y = Mathf.Max(yy - blurSize, 0); (y < yy + blurSize && y < height); y++)
                    {
                        //float weight = 1f - (x + y) / (2.0f * (float)blurSize);

                        pixel = pixels[x + y * width];

                        avgR += pixel.r;
                        avgG += pixel.g;
                        avgB += pixel.b;

                        blurPixelCount++;
                    }
                }

                //            int x = Mathf.Max(0, xx - blurSize);
                //int y = Mathf.Max(0, yy - blurSize);

                //            for (; (x < xx + blurSize && x < width) && y < height; x++, y++)
                //{
                //	Color32 pixel = pixels[x + y * width];
                //	avgR += pixel.r;
                //	avgG += pixel.g;
                //	avgB += pixel.b;

                //	blurPixelCount ++;
                //}

                //y = Mathf.Min(height - 1, yy + blurSize);
                //for (; (x < xx + blurSize && x < width) && y > 0; x++, y--)
                //{
                //	Color32 pixel = pixels[x + y * width];
                //	avgR += pixel.r;
                //	avgG += pixel.g;
                //	avgB += pixel.b;

                //	blurPixelCount++;
                //}

                avgR /= blurPixelCount;
                avgG /= blurPixelCount;
                avgB /= blurPixelCount;

                pixelsBlurred[xx + (yy * width)] = new Color32((byte)avgR, (byte)avgG, (byte)avgB, 255);
            }
        }

        Texture2D blurred = new Texture2D(width, height);
        blurred.SetPixels32(pixelsBlurred);
        blurred.Apply();
        return blurred;
    }

    //---------------------------------------------------------------------------------------------

    public static Texture2D Blur(this Texture2D image, int blurSize, out Color meanColor)
    {
        int width = image.width;
        int height = image.height;

        Color32[] pixels = image.GetPixels32();
        Color32[] pixelsBlurred = new Color32[width * height];

        // look at every pixel in the blur rectangle
        int avgR = 0, avgG = 0, avgB = 0;
        float aavgR = 0;
        float aavgG = 0;
        float aavgB = 0;
        int blurPixelCount = 0;

        for (int xx = 0; xx < width; xx++)
        {
            for (int yy = 0; yy < height; yy++)
            {
                avgR = 0;
                avgG = 0;
                avgB = 0;
                blurPixelCount = 0;

                Color32 pixel = pixels[xx + yy * width];

                aavgR += pixel.r / 255f;
                aavgG += pixel.g / 255f;
                aavgB += pixel.b / 255f;

                // average the color of the red, green and blue for each pixel in the
                // blur size while making sure you don't go outside the image bounds
                for (int x = Mathf.Max(0, xx - blurSize); (x < xx + blurSize && x < width); x++)
                {
                    for (int y = Mathf.Max(yy - blurSize, 0); (y < yy + blurSize && y < height); y++)
                    {
                        //float weight = 1f - (x + y) / (2.0f * (float)blurSize);

                        pixel = pixels[x + y * width];

                        avgR += pixel.r;
                        avgG += pixel.g;
                        avgB += pixel.b;

                        blurPixelCount++;
                    }
                }

                //            int x = Mathf.Max(0, xx - blurSize);
                //int y = Mathf.Max(0, yy - blurSize);

                //            for (; (x < xx + blurSize && x < width) && y < height; x++, y++)
                //{
                //	Color32 pixel = pixels[x + y * width];
                //	avgR += pixel.r;
                //	avgG += pixel.g;
                //	avgB += pixel.b;

                //	blurPixelCount ++;
                //}

                //y = Mathf.Min(height - 1, yy + blurSize);
                //for (; (x < xx + blurSize && x < width) && y > 0; x++, y--)
                //{
                //	Color32 pixel = pixels[x + y * width];
                //	avgR += pixel.r;
                //	avgG += pixel.g;
                //	avgB += pixel.b;

                //	blurPixelCount++;
                //}

                avgR /= blurPixelCount;
                avgG /= blurPixelCount;
                avgB /= blurPixelCount;

                pixelsBlurred[xx + (yy * width)] = new Color32((byte)avgR, (byte)avgG, (byte)avgB, 255);
            }
        }

        aavgR /= (float)(width * height);
        aavgG /= (float)(width * height);
        aavgB /= (float)(width * height);


        meanColor = new Color(aavgR, aavgG, aavgB, 1.0f);

        Texture2D blurred = new Texture2D(width, height);
        blurred.SetPixels32(pixelsBlurred);
        blurred.Apply();
        return blurred;
    }

    //---------------------------------------------------------------------------------------------

    //public static Texture2D Blur(this Texture2D image, int blurSize = 8)
    //{
    //	int width = image.width;
    //	int height = image.height;

    //	Color[] pixels = image.GetPixels();
    //	Color32[] pixelsBlurred = new Color32[width * height];

    //	Debug.Log("Blur Now " + width + " " + height + " " + blurSize);
    //	// look at every pixel in the blur rectangle
    //	for (int xx = 0; xx < width; xx++)
    //	{
    //		for (int yy = 0; yy < height; yy++)
    //		{
    //			Color avg = Color.black;
    //			float blurPixelCount = 0;

    //			// average the color of the red, green and blue for each pixel in the
    //			// blur size while making sure you don't go outside the image bounds
    //			for (int x = Mathf.Max(0, xx - blurSize); (x < xx + blurSize && x < width); x += 2)
    //			{
    //				for (int y = Mathf.Max(yy - blurSize, 0); (y < yy + blurSize && y < height); y += 2)
    //				{
    //					avg += pixels[x + y * width];

    //					blurPixelCount++;
    //				}
    //			}

    //			//            int x = Mathf.Max(0, xx - blurSize);
    //			//int y = Mathf.Max(0, yy - blurSize);

    //			//            for (; (x < xx + blurSize && x < width) && y < height; x++, y++)
    //			//{
    //			//	Color32 pixel = pixels[x + y * width];
    //			//	avgR += pixel.r;
    //			//	avgG += pixel.g;
    //			//	avgB += pixel.b;

    //			//	blurPixelCount ++;
    //			//}

    //			//y = Mathf.Min(height - 1, yy + blurSize);
    //			//for (; (x < xx + blurSize && x < width) && y > 0; x++, y--)
    //			//{
    //			//	Color32 pixel = pixels[x + y * width];
    //			//	avgR += pixel.r;
    //			//	avgG += pixel.g;
    //			//	avgB += pixel.b;

    //			//	blurPixelCount++;
    //			//}

    //			avg /= blurPixelCount;
    //			avg.a = 1.0f;
    //			pixelsBlurred[xx + (yy * width)] = avg;
    //		}
    //	}

    //	Texture2D blurred = new Texture2D(width, height);
    //	blurred.SetPixels32(pixelsBlurred);
    //	blurred.Apply();

    //	Debug.Log("Blur Done");
    //	return blurred;
    //}
    //---------------------------------------------------------------------------------------------


    public static void SetGameLayerRecursive(GameObject gameObject, int layer, List<int> exceptedLayers = null)
    {
        gameObject.layer = layer;
        foreach (Transform child in gameObject.transform)
        {
            if (exceptedLayers == null || !exceptedLayers.Contains(child.gameObject.layer))
            {
                SetGameLayerRecursive(child.gameObject, layer);
            }
        }
    }
    //#region ContinueOnMainThread

    #if NO_FIREBASE

    public static Task ContinueWithOnMainThread(this Task task, System.Action<Task> continuation)
    {
        return task.ContinueWith(continuation);
    }

	public static Task ContinueWithOnMainThread<T>(this Task<T> task, System.Action<Task<T>> continuation)
    {
		return task.ContinueWith(continuation);
	}

	/*
    public static Task<TResult> ContinueWithOnMainThread<TResult>(this Task task, System.Func<Task, TResult> continuation)
    {
		return task.ContinueWith(continuation);
	}*/

	public static Task ContinueWithOnMainThread<T>(this Task<T> task, System.Action<Task<T>> continuation, System.Threading.CancellationToken token)
    {
		return task.ContinueWith(continuation);
	}

    public static Task<TResult> ContinueWithOnMainThread<TResult, T>(this Task<T> task, System.Func<Task<T>, TResult> continuation)
    {
		return task.ContinueWith(continuation);
	}
    #endif

   // #region
}

