using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

namespace XenseAR
{

    public static class Utility
    {
        public static float earthRadius = 20000000f/Mathf.PI;

        public static Texture2D CreateTexture2D(Color baseColor, params ColorModifiner[] modifiners)
        {
            Texture2D result = new Texture2D(1, 1);
            Color fillColor = ModifineColor(baseColor, modifiners);

            result.SetPixel(0, 0, fillColor);
            result.Apply();

            return result;
        }

        public static bool Compare(mTransform transform1, mTransform transform2)
        {
            if (transform1.position.ToUnity() != transform2.position.ToUnity()) return false;
            if (transform1.orientation.ToUnity() != transform2.orientation.ToUnity()) return false;
            if (transform1.scale.ToUnity() != transform2.scale.ToUnity()) return false;

            return true;
        }

        public static Color ModifineColor(Color baseColor, ColorModifiner modifiner)
        {
            Color result = new Color();
            result.r = baseColor.r * modifiner.ColorMultiplier + modifiner.ColorOffset.r;
            result.g = baseColor.g * modifiner.ColorMultiplier + modifiner.ColorOffset.g;
            result.b = baseColor.b * modifiner.ColorMultiplier + modifiner.ColorOffset.b;
            result.a = 1;

            return result;
        }

        public static Color ModifineColor(Color baseColor, params ColorModifiner[] modifiners)
        {
            Color result = baseColor;
            foreach (ColorModifiner colorModifiner in modifiners)
            {
                result = ModifineColor(result, colorModifiner);
            }

            return result;
        }

        public static Bounds GetBound(GameObject go)
        {
            Bounds b = new Bounds(go.transform.position, Vector3.zero);
            var rList = go.GetComponentsInChildren(typeof(Renderer));
            int count = 0;
            foreach (Renderer r in rList)
            {
                if (count == 0)
                {
                    b = r.bounds;
                    count = 1;
                }
                else
                {
                    b.Encapsulate(r.bounds);
                }
            }
            return b;
        }

        public static Vector3 CaculateAngle(Vector3 vector1, Vector3 vector2, bool ignoreY = true)
        {
            if (ignoreY)
            {
                vector1.y = 0f;
                vector2.y = 0f;
            }
            vector1.Normalize();
            vector2.Normalize();
            float cosine = Vector3.Dot(vector1, vector2);
            float sine = Vector3.Cross(vector1, vector2).y;
            float angle = 0f;

            if (cosine >= 0)
            {
                angle = Mathf.Asin(sine) * 180f / Mathf.PI;
            }
            else
            {
                if (sine >= 0)
                {
                    angle = 180f - Mathf.Asin(sine) * 180f / Mathf.PI;
                }
                else
                {
                    angle = -Mathf.Asin(sine) * 180f / Mathf.PI - 180f;
                }
            }
            return new Vector3(0f, angle, 0f);
        }

        public static NavigatePathData GetNavigatePathData(NavMeshPath path)
        {
            NavigatePathData navigatePathData = new NavigatePathData();
            Vector3[] corners = path.corners;
            navigatePathData.segments = new List<NavigatePathSegment>();

            for (int i = 0; i < corners.Length - 1; i++)
            {
                Vector3 startPoint = corners[i];
                Vector3 endPoint = corners[i + 1];
                navigatePathData.segments.Add(new NavigatePathSegment()
                {
                    startPoint = startPoint,
                    endPoint = endPoint,
                    distance = Vector3.Distance(startPoint, endPoint),
                });
                if (i > 0) navigatePathData.segments[i].startPointObject = navigatePathData.segments[i - 1].endPointObject;
                else
                {
                    RaycastHit? startHit = FindNavigationObject(startPoint);
                    if (startHit.HasValue)
                        navigatePathData.segments[i].startPointObject = startHit.Value.transform.gameObject;
                }
                RaycastHit? endHit = FindNavigationObject(endPoint);
                if (endHit.HasValue)
                    navigatePathData.segments[i].endPointObject = endHit.Value.transform.gameObject;
                RaycastHit? middleHit = FindNavigationObject((startPoint + endPoint) / 2f);
                if (middleHit.HasValue)
                {
                    //if (middleHit.Value.transform.gameObject == navigatePathData.segments[i].endPointObject ||
                    //    middleHit.Value.transform.gameObject == navigatePathData.segments[i].startPointObject)
                    navigatePathData.segments[i].isNavMeshLink = false;
                    //else navigatePathData.segments[i].isNavMeshLink = true;
                }
                else navigatePathData.segments[i].isNavMeshLink = true;
                navigatePathData.segments[i].type = findNavigatePathSegmentType(startPoint, endPoint, navigatePathData.segments[i].isNavMeshLink);
            }
            return navigatePathData;
        }

        public static NavigatePathData OptimizeNavigatePathData(NavigatePathData data)
        {
            if (data == null || data.segments.Count == 0) return null;
            NavigatePathData navigatePathData = new NavigatePathData();
            NavigatePathSegmentType currentType = data.segments[0].type;
            Vector3 currentStartPoint = data.segments[0].startPoint;
            GameObject currentStartPointObject = data.segments[0].startPointObject;
            float distance = 0f;
            for (int i = 0; i < data.segments.Count; i++)
            {
                if (i == data.segments.Count - 1)
                {
                    if (data.segments[i].type != currentType)
                    {
                        navigatePathData.segments.Add(new NavigatePathSegment()
                        {
                            distance = distance,
                            type = currentType,
                            startPoint = currentStartPoint,
                            startPointObject = currentStartPointObject,
                            endPoint = data.segments[i - 1].endPoint,
                            endPointObject = data.segments[i - 1].endPointObject,
                        });
                        navigatePathData.segments.Add(data.segments[i]);
                        break;
                    }
                    else
                    {
                        distance += data.segments[i].distance;
                        navigatePathData.segments.Add(new NavigatePathSegment()
                        {
                            distance = distance,
                            type = currentType,
                            startPoint = currentStartPoint,
                            startPointObject = currentStartPointObject,
                            endPoint = data.segments[i].endPoint,
                            endPointObject = data.segments[i].endPointObject,
                        });
                        break;
                    }
                }
                if (data.segments[i].type == currentType)
                {
                    distance += data.segments[i].distance;
                    continue;
                }
                if (distance < 5f && currentType == NavigatePathSegmentType.walk)
                {
                    currentType = data.segments[i].type;
                    distance = data.segments[i].distance;
                    continue;
                }
                navigatePathData.segments.Add(new NavigatePathSegment()
                {
                    distance = distance,
                    type = currentType,
                    startPoint = currentStartPoint,
                    startPointObject = currentStartPointObject,
                    endPoint = data.segments[i - 1].endPoint,
                    endPointObject = data.segments[i - 1].endPointObject,
                });
                currentType = data.segments[i].type;
                distance = data.segments[i].distance;
                currentStartPoint = data.segments[i].startPoint;
                currentStartPointObject = data.segments[i].startPointObject;
            }
            return navigatePathData;
        }

        public static NavigatePathSegmentType findNavigatePathSegmentType(Vector3 start, Vector3 end, bool isNavMeshLink)
        {
            NavigatePathSegmentType result = NavigatePathSegmentType.walk;
            float distance = Vector3.Distance(start, end);
            if (distance < 1f) return NavigatePathSegmentType.walk;

            float height = end.y - start.y;
            float distanceImage = Mathf.Sqrt(Mathf.Pow(end.x - start.x, 2f) + Mathf.Pow(end.z - start.z, 2f));
            float angle = Mathf.Abs(Mathf.Atan2(height, distanceImage) * 180f / Mathf.PI);

            if (angle > 15f && height > 0f && distance > 2.5f)
                result = NavigatePathSegmentType.escalatorUp;
            if (angle > 15f && height < 0f && distance > 2.5f)
                result = NavigatePathSegmentType.escalatorDown;
            if (angle > 70f && height > 0f && distance > 2.5f)
                result = NavigatePathSegmentType.elevatorUp;
            if (angle > 70f && height < 0f && distance > 2.5f)
                result = NavigatePathSegmentType.elevatorDown;
            return result;
        }

        public static RaycastHit? FindNavigationObject(Vector3 findingPoint)
        {
            RaycastHit hit;
            if (Physics.Raycast(findingPoint, Vector3.down, out hit, 1f, 1 << 6))
            {
                return hit;
            }
            return null;
        }

        public static bool isEscalator(Vector3 start, Vector3 end)
        {
            float distance = Vector3.Distance(start, end);
            if (distance < 1f) return false;

            float height = end.y - start.y;
            float distanceImage = Mathf.Sqrt(Mathf.Pow(end.x - start.x, 2f) + Mathf.Pow(end.z - start.z, 2f));
            float angle = Mathf.Abs(Mathf.Atan2(height, distanceImage) * 180f / Mathf.PI);

            if (angle > 5f) return true;
            return false;
        }

        public static bool IsPointerOverUIElement()
        {
            return IsPointerOverUIElement(GetEventSystemRaycastResults());
        }

        ///Returns 'true' if we touched or hovering on Unity UI element.
        static bool IsPointerOverUIElement(List<RaycastResult> eventSystemRaycastResults)
        {
            for (int index = 0; index < eventSystemRaycastResults.Count; index++)
            {
                RaycastResult curRaysastResult = eventSystemRaycastResults[index];

                if (curRaysastResult.gameObject.layer == LayerMask.NameToLayer("UI"))
                    return true;
            }

            return false;
        }


        ///Gets all event systen raycast results of current mouse or touch position.
        static List<RaycastResult> GetEventSystemRaycastResults()
        {
            PointerEventData eventData = new PointerEventData(EventSystem.current);
            eventData.position = Input.mousePosition;

            List<RaycastResult> raycastResults = new List<RaycastResult>();
            EventSystem.current.RaycastAll(eventData, raycastResults);

            return raycastResults;
        }

        public static void AddPlayerPresString(string key, string playerPresString)
        {
            if (PlayerPrefs.HasKey(key))
            {
                if (!PlayerPrefs.GetString(key).Contains(playerPresString))
                    PlayerPrefs.SetString(key, PlayerPrefs.GetString(key) + "," + playerPresString);
            }
            else
                PlayerPrefs.SetString(key, playerPresString);
        }

        public static IEnumerable<TSource> DistinctBy<TSource, TKey>
        (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            HashSet<TKey> seenKeys = new HashSet<TKey>();
            foreach (TSource element in source)
            {
                if (seenKeys.Add(keySelector(element)))
                {
                    yield return element;
                }
            }
        }
        static public void PlaySelfAnimation(GameObject go, string selfAnimation, bool isPlay = true)
        {

            if (string.IsNullOrEmpty(selfAnimation)) return;
            Animation animation = go.GetComponentInChildren<Animation>();
            if (animation == null) return;
            if (isPlay)
            {
                animation.clip = animation.GetClip(selfAnimation);
                //Play selected Animation
                if (animation.GetClip(selfAnimation))
                {
                    animation.Play(selfAnimation, PlayMode.StopAll);
                }
            }
            else
            {
                //Stop Animation
                if (animation.isPlaying)
                {
                    animation.Stop();
                }
            }
        }

        static public Vector3 ToGPSCoordinate(GPS blockGPSCoordinate, Vector3 anchorRelativeTransform)
        {
            Vector3 gpsCoordinate = Vector3.zero;

            gpsCoordinate.z = blockGPSCoordinate.x + 180f * anchorRelativeTransform.z / (Mathf.PI * earthRadius);
            gpsCoordinate.y = anchorRelativeTransform.y;
            gpsCoordinate.x = blockGPSCoordinate.y + 180f * anchorRelativeTransform.x / (Mathf.PI * earthRadius * Mathf.Cos(gpsCoordinate.z * Mathf.PI / 180f));

            return gpsCoordinate;
        }

        static public Vector3 FromGPSCoordinate(GPS blockGPSCoordinate, Vector3 anchorGPSCoordinate)
        {
            Vector3 relativePosition = Vector3.zero;

            relativePosition.z = (anchorGPSCoordinate.z - blockGPSCoordinate.x) * (Mathf.PI * earthRadius) / 180f;
            relativePosition.y = anchorGPSCoordinate.y;
            relativePosition.x = (anchorGPSCoordinate.x - blockGPSCoordinate.y) * (Mathf.PI * earthRadius * Mathf.Cos(anchorGPSCoordinate.z * Mathf.PI / 180f)) / 180f;

            return relativePosition;
        }

        public static class Debug
        {
            public static string GetHierarchyData(string sceneName)
            {
                Scene scene = SceneManager.GetSceneByName(sceneName);
                if (!scene.IsValid()) return "";
                GameObject[] rootGO = scene.GetRootGameObjects();
                string json = "{\"data\":[";
                foreach (GameObject go in rootGO)
                {
                    json = json + GetObjectData(go) + ",";
                }
                json = json + "]}";
                return json;
            }

            public static string GetObjectData(GameObject GO)
            {
                string json = "{";
                json = json +
                    $"\"name\": \"{GO.name}\"," +
                    $"\"position\": \"{GO.transform.localPosition}\"," +
                    $"\"rotation\": \"{GO.transform.localEulerAngles}\"," +
                    $"\"scale\": \"{GO.transform.localScale}\"," +
                    $"\"child\": [";
                for (int i = 0; i < GO.transform.childCount; i++)
                {
                    json = json + GetObjectData(GO.transform.GetChild(i).gameObject) + ",";
                }
                json = json + "],}";
                return json;
            }
        }
    }
}
