using System;
using System.Collections.Generic;
using System.Linq;

using UnityEngine;
using UnityEngine.AI;

namespace XenseAR
{
    [Serializable]
    public enum NavigationState
    {
        Navigating,
        NotNavigating,
        Paused,
    }

    [Serializable]
    public enum NavigationEvent
    {
        StartNavigate,
        PauseNavigate,
        ResumeNavigate,
        StopNavigate,
        DestinationChange,
        DestinationArrived,
    }

    public class NavigationSystemManager : MonoBehaviour
    {
        [Header("Area Target Position")]
        [SerializeField] Transform AreaTargetTransform;

        [Header("Navigation Agent and its ARCamera")]
        [SerializeField] Transform ArCameraTransform;
        [SerializeField] NavMeshAgent NavigationAgent;

        [Header("Navigation UI")]
        [SerializeField] NavigationUIController navigationUIController;

        [SerializeField] QuestionManager questionManager;

        [Space]

        bool pause = false;

        public static event Action<(NavigationEvent status, Vector3? destination)> NavigationStatusChange;

        NavigationLineManager navigationLineManager;
        NavigationMeshManager navigationMeshManager;

        static NavigationState currentState = NavigationState.NotNavigating;

        bool mHasFoundAreaTarget = false;
        bool hasConfirm = false;
        bool hasExit = true;
        bool escalator = false;
        bool navMeshFound = false;

        int confirmCount = 0;
        int navMeshLayer = 1 << 6;

        const float DISTANCE_THRESHOLD = 4f;
        const float OFFMESHLINK_THRESHOLD = 0.5f;
        const float OFFMESHLINK_THRESHOLD_LONG = 1f;
        const float MAX_CONFIRM = 3;

        Vector3 mAreaTargetOriginalPosition;
        Vector3 mDestinationTransform;
        Vector3? mCurrentDestination;
        Vector3 ConfirmARCamPos = Vector3.zero;

        private LocationInfo locationInfo;
        LocationInfo navMeshNotFoundInfo = null;

        List<Vector3> mDestinationTransforms = new List<Vector3>();

        void Awake()
        {
            navMeshNotFoundInfo = null;
            mAreaTargetOriginalPosition = Vector3.zero;
            if (AreaTargetTransform != null)
                mAreaTargetOriginalPosition = AreaTargetTransform.transform.position;
            Container.Register(this);
        }

        private void Start()
        {
            InvokeRepeating("updateNavigation", 0, 0.2f);
            navigationLineManager = Container.Resolve<NavigationLineManager>();
            navigationMeshManager = Container.Resolve<NavigationMeshManager>();
            EventManager.Instance.AddListener(EVENT_TYPE.FinishloadNavMesh, OnFinishloadNavMesh);
            EventManager.Instance.AddListener(EVENT_TYPE.EndNavigate, OnEndNavigate);
        }

        private void OnEndNavigate(EVENT_TYPE Event_Type, Component Sender, object Param)
        {
            EndNavigation();
        }

        void updateNavigation()
        {
            UpdateNavigationAgentPosition();
            UpdateNavigationVisibility();
            UpdateNavigationLinePath();
        }

        void UpdateNavigationAgentPosition()
        {
            // updates the navigation Agent's position inside the NavMesh, based on the ARCamera's current position
            // in relation to the AreaTarget's current position
            var arCamPositionInAreaTarget = ArCameraTransform.position;
            NavigationAgent.transform.position = arCamPositionInAreaTarget + mAreaTargetOriginalPosition;
        }

        void UpdateNavigationVisibility()
        {
            if (!NavigationAgent.pathPending && NavigationAgent.hasPath)
                OnAreaTargetFound();

            if (mHasFoundAreaTarget)
            {
                bool isCloseToDestination =
                    Vector3.Distance(ArCameraTransform.position, mCurrentDestination.Value) < DISTANCE_THRESHOLD;
                if (isCloseToDestination)
                {
                    NavigationStatusChange.Invoke((NavigationEvent.DestinationArrived, mCurrentDestination.Value));
                    EndNavigation();
                    API.NavigateComplete(locationInfo?.id);
                    Mino.instance.Show(MinoPosition.Top, XenseARDictionary.Instance.GetLocalizationText(Defines.LocalizationKey.DestinationArrived) +
                        "<b>" + locationInfo.name + "</b>", 5f);
                    AnalyticsSender.Instance.SendEvent("navigationComplete", "navigation");

                    if (questionManager != null)
                    {
                        questionManager.QuestionPopUpAboutNavigation();
                        questionManager.QuestionPopUpAboutContent();
                        questionManager.QuestionPopUpAboutFirstTimeExperience();

                        questionManager.navigationCount++;
                    }
                }
            }

            navigationLineManager.UpdateNavigationLineVisibility(mHasFoundAreaTarget && !pause, mCurrentDestination, NavigationAgent.path);
        }

        void UpdateNavigationLinePath()
        {
            if (NavigationAgent.pathPending || !NavigationAgent.hasPath || pause) return;
            float threshold;

            //PrecomputePath();
            threshold = OFFMESHLINK_THRESHOLD_LONG;

            escalator = false;

            if (NavigationAgent.currentOffMeshLinkData.valid)
            {
                escalator = true;
            }

            if (!escalator && NavigationAgent.nextOffMeshLinkData.valid)
            {
                float distance = 0f;
                OffMeshLinkData data = NavigationAgent.nextOffMeshLinkData;
                if (ArCameraTransform.position.y > data.startPos.y)
                    distance = Vector2.Distance(new Vector2(ArCameraTransform.position.x, ArCameraTransform.position.z), new Vector2(data.startPos.x, data.startPos.z));

                //distance = Vector3.Distance(ArCameraTransform.position, data.startPos);

                if (distance != 0f && distance < threshold)
                {
                    escalator = true;
                }
            }

            if (Vector3.Distance(ArCameraTransform.position, ConfirmARCamPos) > 2)
            {
                hasConfirm = false;
            }

            if (escalator)
            {
                navigationUIController?.SetDisable(true);
            }
            else
            {
                navigationUIController?.SetDisable(false);
            }

            if (escalator && confirmCount < MAX_CONFIRM)
            {
                pause = true;
                string destination = XenseARDictionary.Instance.GetLocalizationText(Defines.LocalizationKey.escalatorSafetyWarning) + "!";

                RaycastHit? hit = FindEscalatorExit(NavigationAgent.path);

                if (hit != null)
                {
                    destination = XenseARDictionary.Instance.GetLocalizationText(Defines.LocalizationKey.escalatorSafetyWarning) + " t\u1EA1i " + hit.Value.transform.gameObject.name + "!";
                }

                NavigationStatusChange.Invoke((NavigationEvent.PauseNavigate, null));
                currentState = NavigationState.Paused;
                Instruction.instance.Show("", Defines.InstructionDynamicContent.EnterNavMeshLink);
                Instruction.instance.ShowButton(Resume, XenseARDictionary.Instance.GetLocalizationText(Defines.LocalizationKey.exitEscalator));
                Mino.instance.Show(MinoPosition.Bottom, XenseARDictionary.Instance.GetLocalizationText(Defines.LocalizationKey.ExitComfirm));
            }

            if (!NavigationAgent.pathPending && !pause)
            {
                navigationLineManager.DrawPath();
                navigationUIController?.UpdateNavigation(NavigationAgent, NavigationAgent.path);
                if (navigationUIController != null)
                {
                    if (navigationUIController.activeIconType == NavigationIconType.WarningIcon)
                    {
                        navigationUIController.forceUpdateIcon();
                    }
                }
            }
        }

        public void Resume()
        {
            navigationUIController?.forceUpdateIcon();
            NavigationStatusChange.Invoke((NavigationEvent.ResumeNavigate, mCurrentDestination));
            currentState = NavigationState.Navigating;
            pause = false;
            hasConfirm = true;
            ConfirmARCamPos = ArCameraTransform.position;
            navigationUIController?.SetPause(true);
            Mino.instance.Hide();
            confirmCount++;
        }

        private void RecaculateOptimizePath()
        {
            RaycastHit hit;
            if (Physics.Raycast(ArCameraTransform.position, Vector3.down, out hit, 3f, (1 << 6) + (1 << 9)))
            {
                if (Vector3.Distance(hit.point, NavigationAgent.nextPosition) > 1f)
                {
                    NavigationAgent.Warp(hit.point);
                    NavigationAgent.transform.position = ArCameraTransform.position;
                }
            }
            NavigationAgent.ResetPath();
            if (mDestinationTransforms.Count > 1)
            {
                float closestDistance = -1f;
                foreach (Vector3 destination in mDestinationTransforms)
                {
                    NavMeshPath path = new NavMeshPath();
                    if (NavigationAgent.CalculatePath(destination, path) && path.corners.Length > 1)
                    {
                        float remainDistance = 0f;
                        for (int i = 1; i < path.corners.Length; i++)
                        {
                            remainDistance += Vector3.Distance(path.corners[i], path.corners[i - 1]);
                        }

                        if (closestDistance < 0f || closestDistance > remainDistance)
                        {
                            closestDistance = remainDistance;
                            mDestinationTransform = destination;
                        }
                    }
                }
            }
            // position which is from the Moving AreaTarget space has to be transformed into the Static Navmesh space
            Vector3 localPositionInAreaTarget = mDestinationTransform;
            if (AreaTargetTransform != null)
                localPositionInAreaTarget = AreaTargetTransform.InverseTransformPoint(mDestinationTransform);
            if (!mCurrentDestination.HasValue || (mCurrentDestination.Value != localPositionInAreaTarget + mAreaTargetOriginalPosition))
                NavigationStatusChange.Invoke((NavigationEvent.DestinationChange, localPositionInAreaTarget + mAreaTargetOriginalPosition));
            mCurrentDestination = localPositionInAreaTarget + mAreaTargetOriginalPosition;
            NavigationAgent.SetDestination(mCurrentDestination.Value);
            //navigationUIController?.SetNavigateIcon();
        }

        public void EndNavigation()
        {
            NavigationStatusChange.Invoke((NavigationEvent.StopNavigate, null));
            currentState = NavigationState.NotNavigating;
            CancelInvoke(nameof(RecaculateOptimizePath));
            OnAreaTargetLost();
            API.NavigateEnd("");
        }

        private void OnFinishloadNavMesh(EVENT_TYPE Event_Type, Component Sender, object Param)
        {
            navMeshFound = true;

            if (navMeshNotFoundInfo != null)
                NavigateTo(navMeshNotFoundInfo);
            navMeshNotFoundInfo = null;
        }

        public bool NavigateTo(string message)
        {
            LocationInfo info = JsonUtility.FromJson<LocationInfo>(message);
            return NavigateTo(info);
        }

        public bool NavigateTo(LocationInfo info)
        {
            locationInfo = info;
            if (navMeshFound)
            {
                List<Vector3> targets = info.navigationTargets.Select(navigationTarget => navigationTarget.transform.position.ToUnity()).ToList();
                NavigateTo(targets);
                if (!string.IsNullOrEmpty(info.message))
                {
                    Mino.instance.Show(MinoPosition.Top, info.message, 5f);
                }
                return true;
            }
            else
            {
                navMeshNotFoundInfo = locationInfo;
                return false;
            }
        }

        public void NavigateTo(List<Vector3> target)
        {
            mDestinationTransforms = target;
            mDestinationTransform = mDestinationTransforms[0];
            NavigateTo(mDestinationTransform);
        }

        public void NavigateTo(Vector3 destination)
        {
            mDestinationTransform = destination;
            NavigationStatusChange.Invoke((NavigationEvent.StartNavigate, mDestinationTransform));
            currentState = NavigationState.Navigating;
            navigationUIController?.ClearNavigateIcon();

            if (!NavigationAgent.isOnNavMesh)
            {
                if (!NavigationAgent.Warp(ArCameraTransform.position))
                {
                    PopupDialog.instance.showButtonDialog(PopupDialog.DialogTemplate.Notification,
                        XenseARDictionary.Instance.GetLocalizationText(Defines.LocalizationKey.noNavigationDataFound), "OK", null, Defines.Theme.ThemeColor, Defines.Theme.ThemeBoldTextColor);
                    EndNavigation();
                    return;
                }
            }
            if (!IsInvoking(nameof(RecaculateOptimizePath)))
                InvokeRepeating(nameof(RecaculateOptimizePath), 0f, 3f);
        }

        private void OnAreaTargetFound()
        {
            mHasFoundAreaTarget = true;
        }

        private void OnAreaTargetLost()
        {
            mDestinationTransforms.Clear();
            mDestinationTransform = Vector3.zero;
            mCurrentDestination = null;
            mHasFoundAreaTarget = false;
            NavigationAgent.isStopped = true;
            NavigationAgent.ResetPath();
        }

        private RaycastHit? FindEscalatorExit(NavMeshPath path)
        {
            Vector3[] corners = path.corners;
            float maxDistance = 3f;
            float currentDistance = 0f;
            bool end = false;
            for (int i = 1; i < corners.Length; i++)
            {
                float distance = Vector3.Distance(corners[i - 1], corners[i]);
                if (!Utility.isEscalator(corners[i - 1], corners[i]))
                {
                    if (currentDistance + distance > maxDistance)
                        return Utility.FindNavigationObject(corners[i]);
                    else currentDistance += distance;
                }
                else
                {
                    currentDistance = 0f;
                }
            }

            return Utility.FindNavigationObject(corners[corners.Length - 1]);
        }
    }
}
