// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using UnityEngine;

namespace HoloToolkit.Unity
{
    /// <summary>
    /// Miscellaneous utility methods.
    /// </summary>
    public static class Utils
    {
        [System.Obsolete("Use GameObjectExtensions.SetLayerRecursively(gameObject, layer) instead;")]
        public static void SetLayerRecursively(GameObject gameObject, int layer)
        {
            gameObject.SetLayerRecursively(layer);
        }

        /// <summary>
        /// This takes a value, whose proportions are defined by minA and maxA,
        /// and scales it to a space defined by minB and maxB. For example, 5 in
        /// the space of 0 to 10 will map to 50 in the space of 0 to 100.
        /// </summary>
        /// <param name="minA">lower bound for the original scale</param>
        /// <param name="maxA">upper bound for the original scale</param>
        /// <param name="minB">lower bound for new scale</param>
        /// <param name="maxB">upper bound for new scale</param>
        /// <param name="value">the original scale value to convert to the new
        /// scale.</param>
        /// <returns>"value" mapped to the space defined by minB and maxB.</returns>
        public static float Map(float minA, float maxA, float minB, float maxB, float value)
        {
            return (value - minA) * (maxB / maxA) + minB;
        }

        /// <summary>
        /// This wrapper performs a map, but sets the results upper and lower limit
        /// based on the upper and lower bounds of the new scale.
        /// </summary>
        /// <param name="minA">lower bound for the original scale</param>
        /// <param name="maxA">upper bound for the original scale</param>
        /// <param name="minB">lower bound for new scale</param>
        /// <param name="maxB">upper bound for new scale</param>
        /// <param name="value">the original scale value to convert to the new
        /// scale.</param>
        /// <returns>"value" mapped to the space defined by minB and maxB,
        /// constrained by minB and maxB.</returns>
        public static float MapAndClamp(float minA, float maxA, float minB, float maxB, float value)
        {
            return Mathf.Clamp(Map(minA, maxA, minB, maxB, value), minB, maxB);
        }

        /// <summary>
        /// Position transform along the gaze direction and orient yaw to match, with the specified offset
        /// </summary>
        /// <param name="stageTransform">transform of higher-level space where 0,1,0 is up</param>
        /// <param name="tran">transform to be repositioned</param>
        /// <param name="offset">translation offset, Z is forward</param>
        /// <param name="yawOffset">yaw offset</param>
        public static void MoveObjectInFrontOfUser(Transform stageTransform, Transform tran, Vector3 offset, float yawOffset)
        {
            // have obj track head position with translation offset
            Vector3 stageHeadPos = MathUtils.TransformPointFromTo(null, stageTransform, CameraCache.Main.transform.position);
            Vector3 stageHeadDir = MathUtils.TransformDirectionFromTo(null, stageTransform, CameraCache.Main.transform.forward);
            stageHeadDir.y = 0.0f; // ignore head pitch - use head position to set height
            stageHeadDir.Normalize();
            Vector3 sideDir = Vector3.Cross(stageHeadDir, Vector3.up).normalized;
            Vector3 stageNewPos = stageHeadPos + stageHeadDir * offset.z + Vector3.up * offset.y + sideDir * offset.x;
            tran.localPosition = MathUtils.TransformPointFromTo(stageTransform, tran.parent, stageNewPos);

            // also track head yaw
            Vector3 toDir = MathUtils.TransformDirectionFromTo(stageTransform, tran.parent, stageHeadDir);
            Quaternion rot = Quaternion.FromToRotation(Vector3.forward, toDir.normalized) * Quaternion.Euler(0.0f, yawOffset, 0.0f);
            tran.localRotation = rot;
        }

        /// <summary>
        /// Given two points and the transform that they are in, set position, rotation, and scale of a cylinder transform to connect it between the two points
        /// </summary>
        /// <param name="endPointSpace"></param>
        /// <param name="a">One end point in the space of endPointSpace</param>
        /// <param name="b">Other end point in the space of endPointSpace</param>
        /// <param name="cylTransform">transform for the cylinder primitive to connect</param>
        public static void ConnectCylinderBetweenPoints(Transform endPointSpace, Vector3 a, Vector3 b, Transform cylTransform)
        {
            cylTransform.localPosition = MathUtils.TransformPointFromTo(endPointSpace, cylTransform.parent, 0.5f * (a + b));
            Vector3 dir = MathUtils.TransformDirectionFromTo(endPointSpace, cylTransform.parent, (b - a).normalized);
            cylTransform.localRotation = Quaternion.LookRotation(dir) * Quaternion.FromToRotation(Vector3.up, Vector3.forward);
            Vector3 scale = cylTransform.localScale;
            scale.y = (a - b).magnitude * 0.5f;
            cylTransform.localScale = scale;
        }

        /// <summary>
        /// Change material for every object in hierarchy
        /// </summary>
        /// <param name="t">root transform to start looking for renderers</param>
        /// <param name="mat">material to set everything to</param>
        public static void SetMaterialRecursive(Transform t, Material mat)
        {
            if (t.gameObject && t.gameObject.GetComponent<Renderer>())
            {
                t.gameObject.GetComponent<Renderer>().material = mat;
            }

            for (int ii = 0; ii < t.childCount; ++ii)
            {
                SetMaterialRecursive(t.GetChild(ii), mat);
            }
        }

        /// <summary>
        /// Change material for every object in hierarchy which has a name equal to nameToTest.  WARNING! 
        /// <see cref="http://answers.unity3d.com/questions/548420/material-memory-leak.html">See Community Answer</see>
        /// This function automatically instantiates the materials and makes them unique to this renderer.
        /// It is your responsibility to destroy the materials when the game object is being destroyed.
        /// Resources.UnloadUnusedAssets also destroys the materials but it is usually only called when loading a new level.
        /// <see cref="https://docs.unity3d.com/ScriptReference/Renderer-material.html">See Unity Documentation</see>
        /// </summary>
        /// <param name="t">root transform to start looking for renderers</param>
        /// <param name="mat">material to set everything to</param>
        /// <param name="nameToTest">ignore GameObjects with this name</param>
        public static void SetMaterialRecursiveForName(Transform t, Material mat, string nameToTest)
        {
            if (t.gameObject && t.gameObject.GetComponent<Renderer>() && t.gameObject.name == nameToTest)
            {
                t.gameObject.GetComponent<Renderer>().material = mat;
            }

            for (int ii = 0; ii < t.childCount; ++ii)
            {
                SetMaterialRecursiveForName(t.GetChild(ii), mat, nameToTest);
            }

            Resources.UnloadUnusedAssets();
        }

        /// <summary>
        /// helper for detecting if running in editor
        /// </summary>
        /// <returns>true if running in editor, false if windows store app</returns>
        public static bool IsInEditor()
        {
#if UNITY_WSA && !UNITY_EDITOR
            return false;
#else
            return true;
#endif
        }

        /// <summary>
        /// walk hierarchy looking for named transform
        /// </summary>
        /// <param name="t">root transform to start searching from</param>
        /// <param name="name">name to look for</param>
        /// <returns>returns found transform or null if none found</returns>
        public static Transform GetChildRecursive(Transform t, string name)
        {
            int numChildren = t.childCount;
            for (int ii = 0; ii < numChildren; ++ii)
            {
                Transform child = t.GetChild(ii);
                if (child.name == name)
                {
                    return child;
                }
                Transform foundIt = GetChildRecursive(child, name);
                if (foundIt != null)
                {
                    return foundIt;
                }
            }
            return null;
        }
    }
}