using System.Diagnostics;
using System.Timers;

namespace Ryujinx.HLE
{
    public class PerformanceStatistics
    {
        private const double FrameRateWeight = 0.5;

        private const int FrameTypeSystem = 0;
        private const int FrameTypeGame   = 1;

        private double[] AverageFrameRate;
        private double[] AccumulatedFrameTime;
        private double[] PreviousFrameTime;

        private long[] FramesRendered;

        private object[] FrameLock;

        private double TicksToSeconds;

        private Stopwatch ExecutionTime;

        private Timer ResetTimer;

        public PerformanceStatistics()
        {
            AverageFrameRate     = new double[2];
            AccumulatedFrameTime = new double[2];
            PreviousFrameTime    = new double[2];

            FramesRendered = new long[2];

            FrameLock = new object[] { new object(), new object() };

            ExecutionTime = new Stopwatch();

            ExecutionTime.Start();

            ResetTimer = new Timer(1000);

            ResetTimer.Elapsed += ResetTimerElapsed;

            ResetTimer.AutoReset = true;

            ResetTimer.Start();

            TicksToSeconds = 1.0 / Stopwatch.Frequency;
        }

        private void ResetTimerElapsed(object sender, ElapsedEventArgs e)
        {
            CalculateAverageFrameRate(FrameTypeSystem);
            CalculateAverageFrameRate(FrameTypeGame);
        }

        private void CalculateAverageFrameRate(int FrameType)
        {
            double FrameRate = 0;

            if (AccumulatedFrameTime[FrameType] > 0)
            {
                FrameRate = FramesRendered[FrameType] / AccumulatedFrameTime[FrameType];
            }

            lock (FrameLock[FrameType])
            {
                AverageFrameRate[FrameType] = LinearInterpolate(AverageFrameRate[FrameType], FrameRate);

                FramesRendered[FrameType] = 0;

                AccumulatedFrameTime[FrameType] = 0;
            }
        }

        private double LinearInterpolate(double Old, double New)
        {
            return Old * (1.0 - FrameRateWeight) + New * FrameRateWeight;
        }

        public void RecordSystemFrameTime()
        {
            RecordFrameTime(FrameTypeSystem);
        }

        public void RecordGameFrameTime()
        {
            RecordFrameTime(FrameTypeGame);
        }

        private void RecordFrameTime(int FrameType)
        {
            double CurrentFrameTime = ExecutionTime.ElapsedTicks * TicksToSeconds;

            double ElapsedFrameTime = CurrentFrameTime - PreviousFrameTime[FrameType];

            PreviousFrameTime[FrameType] = CurrentFrameTime;

            lock (FrameLock[FrameType])
            {
                AccumulatedFrameTime[FrameType] += ElapsedFrameTime;

                FramesRendered[FrameType]++;
            }
        }

        public double GetSystemFrameRate()
        {
            return AverageFrameRate[FrameTypeSystem];
        }

        public double GetGameFrameRate()
        {
            return AverageFrameRate[FrameTypeGame];
        }
    }
}