using System;
using RothenburgAR.Common;
using RothenburgAR.PointOfInterest;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System.Threading;

namespace RothenburgAR.UI
{
    public class DetailsPanelBehaviour : MonoBehaviour
    {
        public TextMeshProUGUI DetailsText;
        public ScrollRect DetailsTextScrollRect;
        public DetailsModelBehaviour DetailsModel;
        public GameObject IconLoading;
        public GameObject Icon3D;

        private float initialHeight;
        private float initialWidth;

        private void Start()
        {
            gameObject.SetActive(false);
            DisplayManager.Instance.AddOrientationChangeListener(SetSize);
        }

        private void OnEnable()
        {
            SetSize();
        }

        private void OnDestroy()
        {
            DisplayManager.Instance.RemoveOrientationChangeListener(SetSize);
            RemoveModel();
        }

        private void SetSize()
        {
            var transform = GetComponent<RectTransform>();
            var border = 0f;

            initialWidth = initialWidth == 0
                ? transform.offsetMax.x
                : initialWidth;

            initialHeight = initialHeight == 0
                ? transform.offsetMax.y
                : initialHeight;

            if (Screen.orientation == ScreenOrientation.Portrait)
            {
                // stretch horizontally
                transform.anchorMax = new Vector2(1, 0);
                transform.offsetMin = new Vector2(border, border);
                transform.offsetMax = new Vector2(-border, initialHeight);
            }
            else //if (Screen.orientation == ScreenOrientation.Landscape) //landscape or default?
            {
                // stretch vertically
                transform.anchorMax = new Vector2(0, 1);
                transform.offsetMin = new Vector2(border, border);
                transform.offsetMax = new Vector2(initialWidth, -border);
            }
        }

        public bool HasModel
        {
            get { return _currentDisplayedModelGo != null; }
        }

        private GameObject _currentDisplayedModelGo;

        public void SetText(TextElement el)
        {
            SetText(el.GetTextByLanguage(LanguageManager.Instance.CurrentLanguageKey));
        }

        public void SetText(String el)
        {
            DetailsText.text = el;

            // Scroll to top
            DetailsTextScrollRect.normalizedPosition = new Vector2(0, 1);
        }

        public void SetModelFromPoi(PoiData data)
        {
            if (!data.HasModelDescription) return;

            StartCoroutine("SetModelCoroutine", data);
        }

        public IEnumerator SetModelCoroutine(PoiData data)
        {
            if (!data.IsModelLoaded)
            {
                StartCoroutine(data.LoadModel());

                DetailsModel.gameObject.SetActive(true);
                Icon3D.SetActive(true);
                IconLoading.SetActive(true);

                while (!data.IsModelLoaded && !data.ModelLoadFailed)
                {
                    yield return new WaitForSecondsRealtime(.1f);
                }
            }

            if (data.ModelLoadFailed)
            {
                //TODO maybe display error message to user
                RemoveModel();
                yield break;
            }

            try
            {
                SetModel(data.InstantiateModelPrefab());
            }
            catch (Exception)
            {
                // ignore, just do not display an object
                RemoveModel();
            }
        }

        public void SetModel(GameObject modelGo)
        {
            modelGo.transform.SetParent(UIManager.Instance.UIObjectCamera.transform, false);

            var meshFilter = modelGo.GetComponentInChildren<MeshFilter>();
            if (meshFilter == null) throw new Exception("No MeshFilter in Object!");

            var mesh = meshFilter.mesh;
            if (mesh == null) throw new Exception("No Mesh in Model!");

            //TODO remove when metadata model rotation is no longer a thing
            mesh.RecalculateBounds();

            var scale = 1f / Mathf.Max(mesh.bounds.size.x, mesh.bounds.size.y, mesh.bounds.size.z);
            modelGo.transform.localScale = new Vector3(scale, scale, scale);
            modelGo.transform.localPosition = mesh.bounds.center * scale + new Vector3(0, 0, 1.8f);

            _currentDisplayedModelGo = modelGo;
            DetailsModel.CurrentModel = _currentDisplayedModelGo;

            DetailsModel.gameObject.SetActive(true);
            Icon3D.SetActive(false);
            IconLoading.SetActive(false);
        }

        public void RemoveModel()
        {
            DetailsModel.gameObject.SetActive(false);
            DetailsModel.CurrentModel = null;

            if (HasModel)
            {
                Destroy(_currentDisplayedModelGo);
                _currentDisplayedModelGo = null;
                Icon3D.SetActive(false);
                IconLoading.SetActive(false);
            }
        }

        public void UpdateOpeningAnimation(Vector3 position)
        {
            float duration = .4f;
            StartCoroutine("SetSizeAfterSeconds", duration + .1f);

            var scale = DisplayManager.Instance.GetComponent<RectTransform>().localScale.x;
            Vector3 screenMiddle = new Vector3(Screen.width / scale, Screen.height / scale, 0) * 0.5f;

            Vector3 startPos = Camera.main.WorldToScreenPoint(position) / scale - screenMiddle;
            Vector3 endPos = -screenMiddle;

            float startScale = 0.05f;
            float endScale = 1;

            var anim = GetComponent<Animation>();
            var clip = new AnimationClip
            {
                legacy = true,
                name = "DetailsOpen",
            };

            // inTangent and outTangent are the curve's angles to and from this keyframe in radians.
            // outTangent for all the first keyframe of all curves are set to directly point at the second frame,
            // effectively disabling the easing and making it a linear motion.
            // beware: setters for Keyframe.inTangent and Keyframe.outTangent seem to not work.
            var curvePosX = new AnimationCurve();
            curvePosX.AddKey(new Keyframe(0f, startPos.x, 0f, (endPos.x - startPos.x) / duration));
            curvePosX.AddKey(new Keyframe(duration, endPos.x));

            var curvePosY = new AnimationCurve();
            curvePosY.AddKey(new Keyframe(0f, startPos.y, 0f, (endPos.y - startPos.y) / duration));
            curvePosY.AddKey(new Keyframe(duration, endPos.y));

            clip.SetCurve(string.Empty, typeof(Transform), "localPosition.x", curvePosX);
            clip.SetCurve(string.Empty, typeof(Transform), "localPosition.y", curvePosY);

            var curveScaleX = new AnimationCurve();
            curveScaleX.AddKey(new Keyframe(0f, startScale, 0f, (endScale - startScale) / duration));
            curveScaleX.AddKey(new Keyframe(duration, endScale));

            var curveScaleY = new AnimationCurve();
            curveScaleY.AddKey(new Keyframe(0f, startScale, 0f, (endScale - startScale) / duration));
            curveScaleY.AddKey(new Keyframe(duration, endScale));

            clip.SetCurve(string.Empty, typeof(Transform), "localScale.x", curveScaleX);
            clip.SetCurve(string.Empty, typeof(Transform), "localScale.y", curveScaleY);

            anim.AddClip(clip, clip.name);
            anim.Play(clip.name);
        }

        private IEnumerator SetSizeAfterSeconds(float duration)
        {
            yield return new WaitForSecondsRealtime(duration);
            SetSize();
        }

        public void OnDetailsPanelDrag(BaseEventData data)
        {
            DetailsModel.RotateModelByDrag(data as PointerEventData);
        }

        public void OnDetailsPanelPointerDown(BaseEventData data)
        {
            DetailsModel.BeginRotateModelByDrag(data as PointerEventData);
        }

        public void OnDetailsPanelEndDrag(BaseEventData data)
        {
            DetailsModel.EndRotateModelByDrag(data as PointerEventData);
        }
    }
}