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

using System.Collections.Generic;
using UnityEngine;

namespace HoloToolkit.Unity.UX
{
    public class LineObjectCollection : MonoBehaviour
    {
        public List<Transform> Objects = new List<Transform>();

        [Range(-2f, 2f)]
        public float DistributionOffset = 0f;
        [Range(0f, 2f)]
        public float LengthOffset = 0f;
        [Range(0f, 2f)]
        public float ScaleOffset = 0f;
        [Range(0.001f, 2f)]
        public float ScaleMultiplier = 1f;
        [Range(0.001f, 2f)]
        public float PositionMultiplier = 1f;

        public float DistributionOffsetPerObject
        {
            get
            {
                return 1f / Objects.Count;
            }
        }

        public AnimationCurve ObjectScale = AnimationCurve.Linear(0f, 1f, 1f, 1f);

        public AnimationCurve ObjectPosition = AnimationCurve.Linear(0f, 0f, 1f, 0f);

        public bool FlipRotation = false;

        public Vector3 RotationOffset = Vector3.zero;

        public Vector3 PositionOffset = Vector3.zero;

        public RotationTypeEnum RotationTypeOverride = RotationTypeEnum.None;

        public PointDistributionTypeEnum DistributionType = PointDistributionTypeEnum.None;

        [Header("Object Placement")]
        public StepModeEnum StepMode = StepModeEnum.Interpolated;

        [SerializeField]
        private LineBase source;

        [SerializeField]
        private Transform transformHelper;

        public virtual LineBase Source
        {
            get
            {
                if (source == null)
                {
                    source = GetComponent<LineBase>();
                }
                return source;
            }
            set
            {
                source = value;
                if (source == null)
                {
                    enabled = false;
                }
            }
        }

        // Convenience functions
        public float GetOffsetFromObjectIndex(int index, bool wrap = true)
        {
            if (Objects.Count == 0)
            {
                return 0;
            }

            if (wrap)
            {
                index = WrapIndex(index, Objects.Count);
            }
            else
            {
                index = Mathf.Clamp(index, 0, Objects.Count - 1);
            }

            return (1f / Objects.Count * (index + 1));
        }

        public int GetNextObjectIndex(int index, bool wrap = true)
        {
            if (Objects.Count == 0)
            {
                return 0;
            }

            index++;

            if (wrap)
            {
                return WrapIndex(index, Objects.Count);
            }
            else
            {
                return Mathf.Clamp(index, 0, Objects.Count - 1);
            }
        }

        public int GetPrevObjectIndex(int index, bool wrap = true)
        {
            if (Objects.Count == 0)
            {
                return 0;
            }

            index--;

            if (wrap)
            {
                return WrapIndex(index, Objects.Count);
            }
            else
            {
                return Mathf.Clamp(index, 0, Objects.Count - 1);
            }
        }

        public void Update()
        {
            UpdateCollection();
        }

        public void UpdateCollection()
        {
            if (Source == null)
            {
                return;
            }

            if (transformHelper == null)
            {
                transformHelper = transform.Find("TransformHelper");
                if (transformHelper == null)
                {
                    transformHelper = new GameObject("TransformHelper").transform;
                    transformHelper.parent = transform;
                }
            }

            switch (StepMode)
            {
                case StepModeEnum.FromSource:
                    break;

                case StepModeEnum.Interpolated:
                    for (int i = 0; i < Objects.Count; i++)
                    {
                        if (Objects[i] == null)
                        {
                            continue;
                        }

                        float normalizedDistance = Mathf.Repeat(((float)i / Objects.Count) + DistributionOffset, 1f);
                        Objects[i].position = Source.GetPoint(normalizedDistance);
                        Objects[i].rotation = Source.GetRotation(normalizedDistance, RotationTypeOverride);

                        transformHelper.localScale = Vector3.one;
                        transformHelper.position = Objects[i].position;
                        transformHelper.localRotation = Quaternion.identity;
                        Transform tempParent = Objects[i].parent;
                        Objects[i].parent = transformHelper;
                        transformHelper.localEulerAngles = RotationOffset;
                        Objects[i].parent = tempParent;
                        Objects[i].transform.localScale = Vector3.one * ObjectScale.Evaluate(Mathf.Repeat(ScaleOffset + normalizedDistance, 1f)) * ScaleMultiplier;
                    }
                    break;
            }
        }

#if UNITY_EDITOR
        private void OnDrawGizmos()
        {
            if (Application.isPlaying)
            {
                return;
            }

            UpdateCollection();
        }
#endif

        private static int WrapIndex(int index, int numObjects)
        {
            return ((index % numObjects) + numObjects) % numObjects;
        }
    }

#if UNITY_EDITOR
    [UnityEditor.CustomEditor(typeof(LineObjectCollection))]
    public class LineObjectCollectionEditor : UnityEditor.Editor
    {
        public void OnSceneGUI()
        {
            LineObjectCollection loc = (LineObjectCollection)target;

            for (int i = 0; i < loc.Objects.Count; i++)
            {
                if (loc.Objects[i] != null)
                {
                    UnityEditor.Handles.Label(loc.Objects[i].position, "Index: " + i.ToString("000") + "\nOffset: " + loc.GetOffsetFromObjectIndex(i).ToString("00.00"));
                }
            }
        }
    }
#endif
}