/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */
using Live2D.Cubism.Core;
using UnityEngine;
namespace Live2D.Cubism.Framework.HarmonicMotion
{
    /// 
    /// Holds data for controlling the output of simple harmonic motions.
    /// 
    /// 
    /// This type of motion can be very useful for faking breathing, for example.
    /// 
    public sealed class CubismHarmonicMotionParameter : MonoBehaviour
    {
        /// 
        /// Timescale channel.
        /// 
        [SerializeField]
        public int Channel;
        /// 
        /// Motion direction.
        /// 
        [SerializeField]
        public CubismHarmonicMotionDirection Direction;
        /// 
        /// Normalized origin of motion.
        /// 
        /// 
        /// The actual origin used for evaluating the motion depends limits of the .
        /// 
        [SerializeField, Range(0f, 1f)]
        public float NormalizedOrigin = 0.5f;
        /// 
        /// Normalized range of motion.
        /// 
        /// 
        /// The actual origin used for evaluating the motion depends limits of the .
        /// 
        [SerializeField, Range(0f, 1f)]
        public float NormalizedRange = 0.5f;
        /// 
        /// Duration of one motion cycle in seconds.
        /// 
        [SerializeField, Range(0.01f, 10f)]
        public float Duration = 3f;
        /// 
        ///  if  is initialized.
        /// 
        private bool IsInitialized
        {
            get { return Mathf.Abs(ValueRange) >= Mathf.Epsilon; }
        }
        /// 
        /// Initializes instance.
        /// 
        private void Initialize()
        {
            // Initialize value fields.
            var parameter = GetComponent();
            MaximumValue = parameter.MaximumValue;
            MinimumValue = parameter.MinimumValue;
            ValueRange = MaximumValue - MinimumValue;
        }
        #region Interface for Controller
        /// 
        /// Cached .
        /// 
        private float MaximumValue { get; set; }
        /// 
        /// Cached .
        /// 
        private float MinimumValue { get; set; }
        /// 
        /// Range of  and .
        /// 
        private float ValueRange { get; set; }
        /// 
        /// Current time.
        /// 
        private float T { get; set; }
        /// 
        /// Proceeds time.
        /// 
        /// 
        internal void Play(float[] channelTimescales)
        {
            T += (Time.deltaTime * channelTimescales[Channel]);
            // Make sure time stays within duration.
            while (T > Duration)
            {
                T -= Duration;
            }
        }
        /// 
        /// Evaluates the parameter.
        /// 
        /// Parameter value.
        internal float Evaluate()
        {
            // Lazily initialize.
            if (!IsInitialized)
            {
                Initialize();
            }
            // Restore origin and range.
            var origin = MinimumValue + (NormalizedOrigin * ValueRange);
            var range  = NormalizedRange * ValueRange;
            // Clamp the range so that it stays within the limits.
            Clamp(ref origin, ref range);
            // Return result.
            return origin + (range * Mathf.Sin(T * (2 * Mathf.PI) / Duration));
        }
        #endregion
        #region Helper Methods
        /// 
        /// Clamp origin and range based on .
        /// 
        /// Origin to clamp.
        /// Range to clamp.
        private void Clamp(ref float origin, ref float range)
        {
            switch (Direction)
            {
                case CubismHarmonicMotionDirection.Left:
                {
                    if ((origin - range) >= MinimumValue)
                    {
                        range /= 2;
                        origin -= range;
                    }
                    else
                    {
                        range           = (origin - MinimumValue) / 2f;
                        origin          = MinimumValue + range;
                        NormalizedRange = (range * 2f)/ValueRange;
                    }
                    break;
                }
                case CubismHarmonicMotionDirection.Right:
                {
                    if ((origin + range) <= MaximumValue)
                    {
                        range  /= 2f;
                        origin += range;
                    }
                    else
                    {
                        range           = (MaximumValue - origin) / 2f;
                        origin          = MaximumValue - range;
                        NormalizedRange = (range * 2f)/ValueRange;
                    }
                    break;
                }
                default:
                {
                    break;
                }
            }
            // Clamp both range and NormalizedRange.
            if ((origin - range) < MinimumValue)
            {
                range           = origin - MinimumValue;
                NormalizedRange = range / ValueRange;
            }
            else if ((origin + range) > MaximumValue)
            {
                range           = MaximumValue - origin;
                NormalizedRange = range / ValueRange;
            }
        }
        #endregion
    }
}