using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd.  All rights reserved.
//-----------------------------------------------------------------------------

namespace RenderHeads.Media.AVProVideo
{
	/// <summary>
	/// Attempts to give insight into video playback presentation smoothness quality
	/// Keeps track of skipped and duplicated frames and warns about suboptimal setup
	/// such as no vsync enabled or video frame rate not being a multiple of the display frame rate
	/// </summary>
	public class PlaybackQualityStats
	{
		public int SkippedFrames { get; private set; }
		public int DuplicateFrames { get; private set; }
		public int UnityDroppedFrames { get; private set; }
		public float PerfectFramesT { get; private set; }
		public string VSyncStatus { get; private set; }
		private int PerfectFrames { get; set; }
		private int TotalFrames { get; set; }

		public bool LogIssues { get; set; }

		private int _sameFrameCount;
		private long _lastTimeStamp;
		private BaseMediaPlayer _player;

		public void Reset()
		{
			_sameFrameCount = 0;
			if (_player != null)
			{
				_lastTimeStamp = _player.GetTextureTimeStamp();
			}

			SkippedFrames = 0;
			DuplicateFrames = 0;
			UnityDroppedFrames = 0;
			TotalFrames = 0;
			PerfectFrames = 0;
			PerfectFramesT = 0f;
		}

		internal void Start(BaseMediaPlayer player)
		{
			_player = player;
			Reset();

			bool vsyncEnabled = true;
			if (QualitySettings.vSyncCount == 0)
			{
				vsyncEnabled = false;
				if (LogIssues)
				{
					Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in Quality Settings");
				}
			}
			if (!IsGameViewVSyncEnabled())
			{
				vsyncEnabled = false;
				if (LogIssues)
				{
					Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in the Game View");
				}
			}

			float frameRate = _player.GetVideoFrameRate();
			float frameMs = (1000f / frameRate);
			if (LogIssues)
			{
				Debug.Log(string.Format("[AVProVideo][Quality] Video: {0}fps {1}ms", frameRate, frameMs));
			}

			if (vsyncEnabled)
			{
#if UNITY_2022_2_OR_NEWER
				float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
#else
				float refreshRate = (float)( Screen.currentResolution.refreshRate );
#endif

				float vsyncRate = refreshRate / QualitySettings.vSyncCount;
				float vsyncMs = (1000f / vsyncRate);

				if (LogIssues)
				{
					Debug.Log(string.Format("[AVProVideo][Quality] VSync: {0}fps {1}ms", vsyncRate, vsyncMs));
				}

				float framesPerVSync = frameMs / vsyncMs;
				float fractionalframesPerVsync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
				if (fractionalframesPerVsync > 0.0001f && LogIssues)
				{
					Debug.LogWarning("[AVProVideo][Quality] Video is not a multiple of VSync so playback cannot be perfect");
				}
				VSyncStatus = "VSync " + framesPerVSync;
			}
			else
			{
				if (LogIssues)
				{
					Debug.LogWarning("[AVProVideo][Quality] Running without VSync enabled");
				}
				VSyncStatus = "No VSync";
			}
		}

		internal void Update()
		{
			if (_player == null) return;

			// Don't analyse stats unless real playback is happening
			if (_player.IsPaused() || _player.IsSeeking() || _player.IsFinished()) return;

			long timeStamp = _player.GetTextureTimeStamp();
			long frameDuration = (long)(Helper.SecondsToHNS / _player.GetVideoFrameRate());

			bool isPerfectFrame = true;

			// Check for skipped frames
			long d = (timeStamp - _lastTimeStamp);
			if (d > 0)
			{
				const long threshold = 10000;
				d -= frameDuration;
				if (d > threshold)
				{
					int skippedFrames = Mathf.FloorToInt((float)d / (float)frameDuration);
					if (LogIssues)
					{
						Debug.LogWarning("[AVProVideo][Quality] Possible frame skip, at " + timeStamp + " delta " + d + " = " + skippedFrames + " frames");
					}
					SkippedFrames += skippedFrames;
					isPerfectFrame = false;
				}
			}

			if (QualitySettings.vSyncCount != 0)
			{
#if UNITY_2022_2_OR_NEWER
				float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
#else
				float refreshRate = (float)( Screen.currentResolution.refreshRate );
#endif

				long vsyncDuration = (long)((QualitySettings.vSyncCount * Helper.SecondsToHNS) / refreshRate);
				if (timeStamp != _lastTimeStamp)
				{
					float framesPerVSync = (float)frameDuration / (float)vsyncDuration;
					//Debug.Log((float)frameDuration + " " +  (float)vsyncDuration);
					float fractionalFramesPerVSync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);

					//Debug.Log(framesPerVSync + " " + fractionalFramesPerVSync);
					// VSync rate is a multiple of the video rate so we should be able to get perfectly smooth playback
					if (fractionalFramesPerVSync <= 0.0001f)
					{
						// Check for duplicate frames
						if (!Mathf.Approximately(_sameFrameCount, (int)framesPerVSync))
						{
							if (LogIssues)
							{
								Debug.LogWarning("[AVProVideo][Quality] Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + framesPerVSync);
							}
							DuplicateFrames++;
							isPerfectFrame = false;
						}
					}

					_sameFrameCount = 1;
				}
				else
				{
					// Count the number of Unity-frames the video-frame is displayed for
					_sameFrameCount++;
				}

				// Check for Unity dropping frames
				{
					long frameTime = (long)(Time.deltaTime * Helper.SecondsToHNS);
					if (frameTime > (vsyncDuration + (vsyncDuration / 3)))
					{
						if (LogIssues)
						{
							Debug.LogWarning("[AVProVideo][Quality] Possible Unity dropped frame, delta time: " + (Time.deltaTime * 1000f) + "ms");
						}
						UnityDroppedFrames++;
						isPerfectFrame = false;
					}
				}
			}

			if (_lastTimeStamp != timeStamp)
			{
				if (isPerfectFrame)
				{
					PerfectFrames++;
				}
				TotalFrames++;
				PerfectFramesT = (float)PerfectFrames / (float)TotalFrames;
			}

			_lastTimeStamp = timeStamp;
		}

		private static bool IsGameViewVSyncEnabled()
		{
			bool result = true;
#if UNITY_EDITOR && UNITY_2019_1_OR_NEWER
			System.Reflection.Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly;
			System.Type type = assembly.GetType("UnityEditor.GameView");
			UnityEditor.EditorWindow window = UnityEditor.EditorWindow.GetWindow(type);
			System.Reflection.PropertyInfo prop = type.GetProperty("vSyncEnabled");
			if (prop != null)
			{
				result = (bool)prop.GetValue(window);
			}
#endif
			return result;
		}
	}
}