using UnityEngine;
using System.Collections;
using Me.StefS.Utility;
using System.Collections.Generic;
using Me.StefS.UnityUtility;

public static class GameObjectExtensions
{
    public static Dictionary<GameObject, List<int>> GameObjectToQueueOfDisplacementCorutinesIdMap =
        new Dictionary<GameObject, List<int>>();

    public static IEnumerator FlashDisable(this GameObject gameObject)
    {
        int repeat = 5;
        for (int time = 0; time < repeat; time++)
        {
            gameObject.SetActive(true);
            yield return Corutines.WaitForSeconds(0.08f);
            gameObject.SetActive(false);
            yield return Corutines.WaitForSeconds(0.08f);
        }
    }

    public static IEnumerator RotateOnce(this GameObject gameObject)
    {
        return AngleAxisInTime(gameObject, 360f, 1.Y(), 1f);
    }

    public static IEnumerator AngleAxisInTime(this GameObject gameObject, float angle, Vector3 axis, float seconds)
    {
        float targetSpin = angle;
        float accumulatedSpin = 0;
        Quaternion originalRotation = gameObject.transform.localRotation;
        if (angle > 0)
        {
            while (accumulatedSpin < targetSpin)
            {
                float currentSpin = Time.unscaledDeltaTime * targetSpin / seconds;
                currentSpin = Mathf.Clamp(currentSpin, 0, targetSpin - accumulatedSpin);
                accumulatedSpin += currentSpin;
                gameObject.transform.localRotation = originalRotation * Quaternion.AngleAxis(accumulatedSpin, axis);
                yield return null;
            }
        }
        else
        {
            while (accumulatedSpin > targetSpin)
            {
                float currentSpin = Time.unscaledDeltaTime * targetSpin / seconds;
                currentSpin = Mathf.Clamp(currentSpin, targetSpin - accumulatedSpin, 0);
                accumulatedSpin += currentSpin;
                gameObject.transform.localRotation = originalRotation * Quaternion.AngleAxis(accumulatedSpin, axis);
                yield return null;
            }
        }
    }

    public static IEnumerator AngleAxisWithSpeed(this GameObject gameObject, Vector3 axis, float rotationsPerSecond)
    {
        float targetSpin = 360;
        float accumulatedSpin = 0;
        Quaternion originalRotation = gameObject.transform.localRotation;
        while (true)
        {
            float currentSpin = Time.unscaledDeltaTime * targetSpin * rotationsPerSecond;
            currentSpin = Mathf.Clamp(currentSpin, 0, targetSpin - accumulatedSpin);
            accumulatedSpin += currentSpin;
            accumulatedSpin %= 360;
            gameObject.transform.localRotation = originalRotation * Quaternion.AngleAxis(accumulatedSpin, axis);
            yield return null;
        }
    }


    public static IEnumerator LerpToLocalPositionInSeconds(this GameObject gameObject, Vector3 destination,
        float seconds, float treshold)
    {
        int corutineIndex = Time.frameCount ^ gameObject.GetHashCode();
        if (GameObjectToQueueOfDisplacementCorutinesIdMap.ContainsKey(gameObject))
        {
            GameObjectToQueueOfDisplacementCorutinesIdMap[gameObject].Add(corutineIndex);
        }
        else
        {
            GameObjectToQueueOfDisplacementCorutinesIdMap[gameObject] = new List<int> {corutineIndex};
        }

        List<int> corrutineQueue = GameObjectToQueueOfDisplacementCorutinesIdMap[gameObject];

        float distance = Vector3.Magnitude(gameObject.transform.localPosition.VectorTo(destination));
        float factor = distance / seconds;
        while (!gameObject.transform.localPosition.IsDistanceBelow(destination, treshold)
               && corrutineQueue[corrutineQueue.Count - 1] == corutineIndex
        )
        {
            gameObject.transform.localPosition =
                Vector3.Lerp(
                    gameObject.transform.localPosition,
                    destination,
                    Time.unscaledDeltaTime * factor);
            yield return null;
        }

        if (corrutineQueue[corrutineQueue.Count - 1] == corutineIndex)
        {
            gameObject.transform.localPosition = destination;
        }

        corrutineQueue.Remove(corutineIndex);
        if (corrutineQueue.Count == 0)
        {
            GameObjectToQueueOfDisplacementCorutinesIdMap.Remove(gameObject);
        }
    }

    public static IEnumerator GoToLocalPositionInSeconds(this GameObject gameObject, Vector3 destination, float seconds)
    {
        int corutineIndex = Time.frameCount ^ gameObject.GetHashCode();
        if (GameObjectToQueueOfDisplacementCorutinesIdMap.ContainsKey(gameObject))
        {
            GameObjectToQueueOfDisplacementCorutinesIdMap[gameObject].Add(corutineIndex);
        }
        else
        {
            GameObjectToQueueOfDisplacementCorutinesIdMap[gameObject] = new List<int> {corutineIndex};
        }

        List<int> corrutineQueue = GameObjectToQueueOfDisplacementCorutinesIdMap[gameObject];

        float distance = Vector3.Magnitude(gameObject.transform.localPosition.VectorTo(destination));
        float factor = distance / seconds;
        float aggregatedTime = 0;
        Vector3 startPosition = gameObject.transform.localPosition;
        while (aggregatedTime <= seconds
               && corrutineQueue[corrutineQueue.Count - 1] == corutineIndex
        )
        {
            aggregatedTime += Time.deltaTime;
            gameObject.transform.localPosition =
                startPosition +
                startPosition.VectorTowardsWithLength(
                    destination,
                    Mathf.Clamp(aggregatedTime * factor, 0, distance));
            yield return null;
        }

        if (corrutineQueue[corrutineQueue.Count - 1] == corutineIndex)
        {
            gameObject.transform.localPosition = destination;
        }

        corrutineQueue.Remove(corutineIndex);
        if (corrutineQueue.Count == 0)
        {
            GameObjectToQueueOfDisplacementCorutinesIdMap.Remove(gameObject);
        }
    }

    public static IEnumerator LerpToLocalRotationInSeconds(this GameObject gameObject, Quaternion rotation,
        float seconds, float treshold)
    {
        int corutineIndex = Time.frameCount ^ gameObject.GetHashCode();
        if (GameObjectToQueueOfDisplacementCorutinesIdMap.ContainsKey(gameObject))
        {
            GameObjectToQueueOfDisplacementCorutinesIdMap[gameObject].Add(corutineIndex);
        }
        else
        {
            GameObjectToQueueOfDisplacementCorutinesIdMap[gameObject] = new List<int> {corutineIndex};
        }

        List<int> corrutineQueue = GameObjectToQueueOfDisplacementCorutinesIdMap[gameObject];

        float distance = Vector3.Magnitude(
            (gameObject.transform.localRotation * Vector3.up).VectorTo(
                rotation * Vector3.up
            ));
        float factor = distance / seconds;
        while (!(gameObject.transform.localRotation * Vector3.up).IsDistanceBelow(rotation * Vector3.up, treshold)
               && corrutineQueue[corrutineQueue.Count - 1] == corutineIndex
        )
        {
            gameObject.transform.localRotation =
                Quaternion.Lerp(
                    gameObject.transform.localRotation,
                    rotation,
                    Time.unscaledDeltaTime * factor);
            yield return null;
        }

        if (corrutineQueue[corrutineQueue.Count - 1] == corutineIndex)
        {
            gameObject.transform.localRotation = rotation;
        }

        corrutineQueue.Remove(corutineIndex);
        if (corrutineQueue.Count == 0)
        {
            GameObjectToQueueOfDisplacementCorutinesIdMap.Remove(gameObject);
        }
    }
}