using System;
using System.Diagnostics;
using UnityEngine;
using Unity.Profiling;

namespace FishROV.AvoidanceBenchmark
{
    public sealed class AvoidanceMetricsRecorder : IDisposable
    {
        private readonly float[] frameTimes = new float[240];
        private readonly Stopwatch simulationTimer = new Stopwatch();
        private int frameIndex;
        private int frameCount;
        private double simulationMs;
        private ProfilerRecorder gcRecorder;

        public float AverageFps { get; private set; }
        public float OnePercentLowFps { get; private set; }
        public double SimulationMs => simulationMs;
        public long GcAllocBytes => gcRecorder.Valid ? gcRecorder.LastValue : 0;

        public void Start()
        {
            if (!gcRecorder.Valid)
            {
                gcRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "GC Allocated In Frame");
            }
        }

        public void BeginSimulation()
        {
            simulationTimer.Restart();
        }

        public void EndSimulation()
        {
            simulationTimer.Stop();
            simulationMs = simulationTimer.Elapsed.TotalMilliseconds;
        }

        public void RecordFrame(float deltaTime)
        {
            if (deltaTime <= 0f)
            {
                return;
            }

            frameTimes[frameIndex] = deltaTime;
            frameIndex = (frameIndex + 1) % frameTimes.Length;
            frameCount = Mathf.Min(frameCount + 1, frameTimes.Length);

            var sum = 0f;
            for (var i = 0; i < frameCount; i++)
            {
                sum += frameTimes[i];
            }

            AverageFps = frameCount > 0 ? frameCount / sum : 0f;
            OnePercentLowFps = CalculateOnePercentLow();
        }

        public void Dispose()
        {
            if (gcRecorder.Valid)
            {
                gcRecorder.Dispose();
            }
        }

        private float CalculateOnePercentLow()
        {
            if (frameCount == 0)
            {
                return 0f;
            }

            var worstCount = Mathf.Max(1, Mathf.CeilToInt(frameCount * 0.01f));
            var copy = new float[frameCount];
            Array.Copy(frameTimes, copy, frameCount);
            Array.Sort(copy);

            var sum = 0f;
            for (var i = frameCount - worstCount; i < frameCount; i++)
            {
                sum += copy[i];
            }

            return sum > 0f ? worstCount / sum : 0f;
        }
    }
}
