// FlexiMotion // https://kybernetik.com.au/flexi-motion // Copyright 2023-2025 Kybernetik //

#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.

using System;
using System.Text;
using UnityEngine;
using UnityEngine.UI;

namespace FlexiMotion.Samples
{
    /// <summary>A simble benchmark which records the average frame rate of an instances prefab over time.</summary>
    /// https://kybernetik.com.au/flexi-motion/api/FlexiMotion.Samples/AverageFrameRateBenchmark
    /// 
    [AddComponentMenu(FMStrings.SamplesMenuPrefix + "Average Frame Rate Benchmark")]
    [HelpURL(FMStrings.DocsURLs.SamplesAPIDocumentation + "/" + nameof(AverageFrameRateBenchmark))]
    public class AverageFrameRateBenchmark : MonoBehaviour, IOnValidate
    {
        /************************************************************************************************************************/

        [SerializeField] private GameObject[] _Prefabs;
        [SerializeField] private Text _Text;
        [SerializeField] private int _InstanceCount = 100;
        [SerializeField] private float _Delay = 1;
        [SerializeField] private float _Duration = 10;
        [SerializeField] private int _Iterations = 1;
        [SerializeField] private bool _DisableCamera = true;
        [SerializeField] private bool _QuitAfterTest;

        private int _CurrentPrefab;
        private int _CurrentIteration;
        private bool _IsBenchmarking;
        private int _FrameCount;
        private float _StartTime;
        private Camera _Camera;
        private float[] _TotalFPS;

        /************************************************************************************************************************/

        protected virtual void OnValidate()
        {
            _InstanceCount = Math.Max(1, _InstanceCount);
            _Delay = Math.Max(0, _Delay);
            _Duration = Math.Max(0, _Duration);
            _Iterations = Math.Max(1, _Iterations);
        }

        void IOnValidate.OnValidate()
            => OnValidate();

        /************************************************************************************************************************/

        protected virtual void Awake()
        {
            _Text.text = string.Format(_Text.text,
                _InstanceCount,
                _Delay,
                _DisableCamera ? "yes" : "no",
                _Duration,
                _Prefabs.Length,
                _Iterations,
                _Prefabs.Length * _Iterations * (_Delay + _Duration));
            Debug.Log(_Text.text, this);

            _Camera = Camera.main;
            _TotalFPS = new float[_Prefabs.Length];

#if UNITY_EDITOR
            UnityEditor.Selection.activeObject = null;
#endif
        }

        /************************************************************************************************************************/

        private readonly StringBuilder
            StringBuilder = new StringBuilder();

        protected virtual void Update()
        {
            var elapsedTime = Time.timeSinceLevelLoad - _StartTime;

            if (!_IsBenchmarking)
            {
                if (transform.childCount == 0)
                {
                    _StartTime = Time.timeSinceLevelLoad;
                    Instaitiate(_Prefabs[_CurrentPrefab], _InstanceCount, transform);
                    GC.Collect();
                }
                else if (elapsedTime < _Delay)// Wait for performance to stabilize after startup.
                {
                    GC.Collect();
                }
                else// Start the timer.
                {
                    _Camera.enabled = !_DisableCamera;
                    _IsBenchmarking = true;
                    _FrameCount = 0;
                    _StartTime = Time.timeSinceLevelLoad;
                }

                return;
            }
            else// Benchmark Running.
            {
                if (elapsedTime < _Delay + _Duration)// Count frames during the timer.
                {
                    _FrameCount++;
                }
                else// Present the results after the time ends.
                {
                    var testNumber = _CurrentIteration * _Prefabs.Length + _CurrentPrefab + 1;
                    var testCount = _Iterations * _Prefabs.Length;
                    var timePerTest = _Delay + _Duration;
                    var frameRate = _FrameCount / elapsedTime;
                    var name = _Prefabs[_CurrentPrefab].name;

                    _TotalFPS[_CurrentPrefab] += frameRate;

                    StringBuilder.Length = 0;
                    StringBuilder
                        .Append(FMStrings.Bullet + " ")
                        .Append(testNumber)
                        .Append(" / ")
                        .Append(testCount)
                        .Append(" (")
                        .Append(testNumber * timePerTest)
                        .Append("s / ")
                        .Append(testCount * timePerTest)
                        .Append("s) ")
                        .Append(name)
                        .Append(": ")
                        .Append(frameRate)
                        .Append(" FPS");

                    var result = StringBuilder.ToString();
                    _Text.text += "\n" + result;

                    Debug.Log($"{result}\n\nAll results copied to clipboard:\n{_Text.text}", this);
                    GUIUtility.systemCopyBuffer = _Text.text;

                    _IsBenchmarking = false;
                    _CurrentPrefab++;
                    if (_CurrentPrefab >= _Prefabs.Length)
                    {
                        _CurrentPrefab = 0;
                        _CurrentIteration++;
                    }

                    if (_CurrentIteration >= _Iterations)
                    {
                        var results = BuildFinalResults();
                        Debug.Log("Testing Complete. " + results, this);
                        GUIUtility.systemCopyBuffer = results;

                        if (_QuitAfterTest)
                        {
                            Quit();
                        }
                        else
                        {

                            _Text.text = results;

                            enabled = false;
                            _Camera.enabled = true;
                        }

                    }
                    else
                    {
                        _Camera.enabled = true;

                        for (int i = transform.childCount - 1; i >= 0; i--)
                            Destroy(transform.GetChild(i).gameObject);
                    }
                }
            }
        }

        /************************************************************************************************************************/

        private static void Instaitiate(GameObject prefab, int count, Transform parent)
        {
            for (int i = 0; i < count; i++)
            {
                var angle = i;
                var radius = Mathf.Sqrt(i);
                var position = new Vector3(
                    Mathf.Cos(angle) * radius,
                    0,
                    Mathf.Sin(angle) * radius);

                var instance = Instantiate(prefab, position, Quaternion.identity, parent);

                // Make sure Animators aren't culled for being off-camera.
                if (instance.TryGetComponent<Animator>(out var animator))
                    animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
            }
        }

        /************************************************************************************************************************/

        private string BuildFinalResults()
        {
            StringBuilder.Length = 0;
            StringBuilder.Append(_Text.text)
                .Append("\nTesting Complete. Average Results:");

            for (int i = 0; i < _Prefabs.Length; i++)
            {
                StringBuilder
                    .Append(FMStrings.NewBullet)
                    .Append(_Prefabs[i].name)
                    .Append(": ")
                    .Append(_TotalFPS[i] / _Iterations)
                    .Append(" FPS");
            }

            StringBuilder.Append("\nAll results copied to clipboard.");

            return StringBuilder.ToString();
        }

        /************************************************************************************************************************/

        private static void Quit()
        {
            Application.Quit();

#if UNITY_EDITOR
            UnityEditor.EditorApplication.ExitPlaymode();
#endif
        }

        /************************************************************************************************************************/
    }
}
