using System;
using System.Collections;
using UnityEngine;

[DisallowMultipleComponent]
[DefaultExecutionOrder(ORDER_EXECUTION)]
public class InterpolationObjectController : MonoBehaviour
{
    [Flags]
    public enum InterpolateType
    {
        None,
        Position = 1, 
        Rotation = 1 << 1,  
        Scale = 1 << 2, 
    }

    public InterpolateType type;

    private const int ORDER_EXECUTION = InterpolationFactorController.ORDER_EXECUTION - 1;

    private TransformData[] _transforms;
    private int _index;

    private void Awake()
    {
        StartCoroutine(WaitForEndOfFrame());
        StartCoroutine(WaitForFixedUpdate());
    }

    private void OnEnable()
    {
        ResetTransforms();
    }

    private void BeforeFixedUpdate()
    {
        // Restoring actual transform for the FixedUpdate() cal where it could be change by the user.
        RestoreActualTransform();
    }

    private void AfterFixedUpdate()
    {
        // Saving actual transform for being restored in the BeforeFixedUpdate() method.
        SaveActualTransform();
    }

    private void Update()
    {
        // Set interpolated transform for being rendered.
        SetInterpolatedTransform();
    }

    #region Helpers

    private void RestoreActualTransform()
    {
        var latest = _transforms[_index];

        if((type & InterpolateType.Position) != 0)
            transform.localPosition = latest.position;
        if ((type & InterpolateType.Scale) != 0)
            transform.localScale = latest.scale;
        if ((type & InterpolateType.Rotation) != 0)
            transform.localRotation = latest.rotation;
    }

    private void SaveActualTransform()
    {
        _index = NextIndex();
        _transforms[_index] = CurrentTransformData();
    }

    private void SetInterpolatedTransform()
    {
        var prev = _transforms[NextIndex()];
        float factor = InterpolationFactorController.Factor;
        if ((type & InterpolateType.Position) != 0)
            transform.localPosition = Vector3.Lerp(prev.position, transform.localPosition, factor);
        if ((type & InterpolateType.Rotation) != 0)
            transform.localRotation = Quaternion.Slerp(prev.rotation, transform.localRotation, factor);
        if ((type & InterpolateType.Scale) != 0)
            transform.localScale = Vector3.Lerp(prev.scale, transform.localScale, factor);
    }

    public void ResetTransforms()
    {
        _index = 0;
        var td = CurrentTransformData();
        _transforms = new TransformData[2] { td, td };
    }

    private TransformData CurrentTransformData()
    {
        return new TransformData(transform.localPosition, transform.localRotation, transform.localScale);
    }

    private int NextIndex()
    {
        return (_index == 0) ? 1 : 0;
    }

    private IEnumerator WaitForEndOfFrame()
    {
        while (true)
        {
            yield return new WaitForEndOfFrame();
            BeforeFixedUpdate();
        }
    }

    private IEnumerator WaitForFixedUpdate()
    {
        while (true)
        {
            yield return new WaitForFixedUpdate();
            AfterFixedUpdate();
        }
    }

    private struct TransformData
    {
        public Vector3 position;
        public Quaternion rotation;
        public Vector3 scale;

        public TransformData(Vector3 position, Quaternion rotation, Vector3 scale)
        {
            this.position = position;
            this.rotation = rotation;
            this.scale = scale;
        }
    }

    #endregion
}
