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

namespace RenderHeads.Media.AVProVideo
{
	public class Resampler
	{
		public Resampler(MediaPlayer player, string name, int bufferSize = 2, Resampler.ResampleMode resampleMode = Resampler.ResampleMode.LINEAR)
		{
			this._bufferSize = Mathf.Max(2, bufferSize);
			if (player.Info != null)
			{
				this._elapsedTimeSinceBase = (float)this._bufferSize / player.Info.GetVideoFrameRate();
			}
			player.Events.AddListener(new UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode>(this.OnVideoEvent));
			this._mediaPlayer = player;
			Shader shader = Shader.Find("AVProVideo/BlendFrames");
			if (shader != null)
			{
				this._blendMat = new Material(shader);
				this._propT = Shader.PropertyToID("_t");
				this._propAfterTex = Shader.PropertyToID("_AfterTex");
			}
			else
			{
				Debug.LogError("[AVProVideo] Failed to find BlendFrames shader");
			}
			this._resampleMode = resampleMode;
			this._name = name;
			Debug.Log("[AVProVideo] Resampler " + this._name + " started");
		}

		public int DroppedFrames
		{
			get
			{
				return this._droppedFrames;
			}
		}

		public int FrameDisplayedTimer
		{
			get
			{
				return this._frameDisplayedTimer;
			}
		}

		public long BaseTimestamp
		{
			get
			{
				return this._baseTimestamp;
			}
			set
			{
				this._baseTimestamp = value;
			}
		}

		public float ElapsedTimeSinceBase
		{
			get
			{
				return this._elapsedTimeSinceBase;
			}
			set
			{
				this._elapsedTimeSinceBase = value;
			}
		}

		public float LastT { get; private set; }

		public long TextureTimeStamp { get; private set; }

		public void OnVideoEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
		{
			if (et != MediaPlayerEvent.EventType.MetaDataReady)
			{
				if (et == MediaPlayerEvent.EventType.Closing)
				{
					this.Reset();
				}
			}
			else
			{
				this._elapsedTimeSinceBase = (float)this._bufferSize / this._mediaPlayer.Info.GetVideoFrameRate();
			}
		}

		public Texture[] OutputTexture
		{
			get
			{
				return this._outputTexture;
			}
		}

		public void Reset()
		{
			this._lastTimeStamp = -1L;
			this._baseTimestamp = 0L;
			this.InvalidateBuffer();
		}

		public void Release()
		{
			this.ReleaseRenderTextures();
			if (this._blendMat != null)
			{
				UnityEngine.Object.Destroy(this._blendMat);
			}
		}

		private void ReleaseRenderTextures()
		{
			for (int i = 0; i < this._buffer.Count; i++)
			{
				for (int j = 0; j < this._buffer[i].Length; j++)
				{
					if (this._buffer[i][j].texture != null)
					{
						RenderTexture.ReleaseTemporary(this._buffer[i][j].texture);
						this._buffer[i][j].texture = null;
					}
				}
				if (this._outputTexture != null && this._outputTexture[i] != null)
				{
					RenderTexture.ReleaseTemporary(this._outputTexture[i]);
				}
			}
			this._outputTexture = null;
		}

		private void ConstructRenderTextures()
		{
			this.ReleaseRenderTextures();
			this._buffer.Clear();
			this._outputTexture = new RenderTexture[this._mediaPlayer.TextureProducer.GetTextureCount()];
			for (int i = 0; i < this._mediaPlayer.TextureProducer.GetTextureCount(); i++)
			{
				Texture texture = this._mediaPlayer.TextureProducer.GetTexture(i);
				this._buffer.Add(new Resampler.TimestampedRenderTexture[this._bufferSize]);
				for (int j = 0; j < this._bufferSize; j++)
				{
					this._buffer[i][j] = new Resampler.TimestampedRenderTexture();
				}
				for (int k = 0; k < this._buffer[i].Length; k++)
				{
					this._buffer[i][k].texture = RenderTexture.GetTemporary(texture.width, texture.height, 0);
					this._buffer[i][k].timestamp = 0L;
					this._buffer[i][k].used = false;
				}
				this._outputTexture[i] = RenderTexture.GetTemporary(texture.width, texture.height, 0);
			}
		}

		private bool CheckRenderTexturesValid()
		{
			for (int i = 0; i < this._mediaPlayer.TextureProducer.GetTextureCount(); i++)
			{
				Texture texture = this._mediaPlayer.TextureProducer.GetTexture(i);
				for (int j = 0; j < this._buffer.Count; j++)
				{
					if (this._buffer[i][j].texture == null || this._buffer[i][j].texture.width != texture.width || this._buffer[i][j].texture.height != texture.height)
					{
						return false;
					}
				}
				if (this._outputTexture == null || this._outputTexture[i] == null || this._outputTexture[i].width != texture.width || this._outputTexture[i].height != texture.height)
				{
					return false;
				}
			}
			return true;
		}

		private int FindBeforeFrameIndex(int frameIdx)
		{
			if (frameIdx >= this._buffer.Count)
			{
				return -1;
			}
			int num = -1;
			float num2 = float.MaxValue;
			int num3 = -1;
			float num4 = float.MaxValue;
			for (int i = 0; i < this._buffer[frameIdx].Length; i++)
			{
				if (this._buffer[frameIdx][i].used)
				{
					float num5 = (float)(this._buffer[frameIdx][i].timestamp - this._baseTimestamp) / 1E+07f;
					if (num5 < num4)
					{
						num3 = i;
						num4 = num5;
					}
					float num6 = this._elapsedTimeSinceBase - num5;
					if (num6 >= 0f && num6 < num2)
					{
						num2 = num6;
						num = i;
					}
				}
			}
			if (num >= 0)
			{
				return num;
			}
			if (num3 < 0)
			{
				return -1;
			}
			return num3;
		}

		private int findClosestFrame(int frameIdx)
		{
			if (frameIdx >= this._buffer.Count)
			{
				return -1;
			}
			int result = -1;
			float num = float.MaxValue;
			for (int i = 0; i < this._buffer[frameIdx].Length; i++)
			{
				if (this._buffer[frameIdx][i].used)
				{
					float num2 = (float)(this._buffer[frameIdx][i].timestamp - this._baseTimestamp) / 1E+07f;
					float num3 = Mathf.Abs(this._elapsedTimeSinceBase - num2);
					if (num3 < num)
					{
						result = i;
						num = num3;
					}
				}
			}
			return result;
		}

		private void PointUpdate()
		{
			for (int i = 0; i < this._buffer.Count; i++)
			{
				int num = this.findClosestFrame(i);
				if (num >= 0)
				{
					this._outputTexture[i].DiscardContents();
					Graphics.Blit(this._buffer[i][num].texture, this._outputTexture[i]);
					this._currentDisplayedTimestamp = this._buffer[i][num].timestamp;
				}
			}
		}

		private void SampleFrame(int frameIdx, int bufferIdx)
		{
			this._outputTexture[bufferIdx].DiscardContents();
			Graphics.Blit(this._buffer[bufferIdx][frameIdx].texture, this._outputTexture[bufferIdx]);
			this.TextureTimeStamp = this._buffer[bufferIdx][frameIdx].timestamp;
			this._currentDisplayedTimestamp = this._buffer[bufferIdx][frameIdx].timestamp;
		}

		private void SampleFrames(int bufferIdx, int frameIdx1, int frameIdx2, float t)
		{
			this._blendMat.SetFloat(this._propT, t);
			this._blendMat.SetTexture(this._propAfterTex, this._buffer[bufferIdx][frameIdx2].texture);
			this._outputTexture[bufferIdx].DiscardContents();
			Graphics.Blit(this._buffer[bufferIdx][frameIdx1].texture, this._outputTexture[bufferIdx], this._blendMat);
			this.TextureTimeStamp = (long)Mathf.Lerp((float)this._buffer[bufferIdx][frameIdx1].timestamp, (float)this._buffer[bufferIdx][frameIdx2].timestamp, t);
			this._currentDisplayedTimestamp = this._buffer[bufferIdx][frameIdx1].timestamp;
		}

		private void LinearUpdate()
		{
			for (int i = 0; i < this._buffer.Count; i++)
			{
				int num = this.FindBeforeFrameIndex(i);
				if (num >= 0)
				{
					float num2 = (float)(this._buffer[i][num].timestamp - this._baseTimestamp) / 1E+07f;
					if (num2 > this._elapsedTimeSinceBase)
					{
						this.SampleFrame(num, i);
						this.LastT = -1f;
					}
					else
					{
						int num3 = (num + 1) % this._buffer[i].Length;
						float num4 = (float)(this._buffer[i][num3].timestamp - this._baseTimestamp) / 1E+07f;
						if (num4 < num2)
						{
							this.SampleFrame(num, i);
							this.LastT = 2f;
						}
						else
						{
							float num5 = num4 - num2;
							float num6 = (this._elapsedTimeSinceBase - num2) / num5;
							this.SampleFrames(i, num, num3, num6);
							this.LastT = num6;
						}
					}
				}
			}
		}

		private void InvalidateBuffer()
		{
			this._elapsedTimeSinceBase = (float)(this._bufferSize / 2) / this._mediaPlayer.Info.GetVideoFrameRate();
			for (int i = 0; i < this._buffer.Count; i++)
			{
				for (int j = 0; j < this._buffer[i].Length; j++)
				{
					this._buffer[i][j].used = false;
				}
			}
			this._start = (this._end = 0);
		}

		public void Update()
		{
			if (this._mediaPlayer.TextureProducer == null)
			{
				return;
			}
			if (this._mediaPlayer.TextureProducer == null || this._mediaPlayer.TextureProducer.GetTexture(0) == null)
			{
				return;
			}
			if (!this.CheckRenderTexturesValid())
			{
				this.ConstructRenderTextures();
			}
			long textureTimeStamp = this._mediaPlayer.TextureProducer.GetTextureTimeStamp();
			if (textureTimeStamp != this._lastTimeStamp)
			{
				float num = Mathf.Abs((float)(textureTimeStamp - this._lastTimeStamp));
				float num2 = 1E+07f * (1f / this._mediaPlayer.Info.GetVideoFrameRate());
				if (num > num2 * 1.1f && num < num2 * 3.1f)
				{
					this._droppedFrames += (int)((double)((num - num2) / num2) + 0.5);
				}
				this._lastTimeStamp = textureTimeStamp;
			}
			long textureTimeStamp2 = this._mediaPlayer.TextureProducer.GetTextureTimeStamp();
			bool flag = !this._mediaPlayer.Control.IsSeeking();
			if (this._start != this._end || this._buffer[0][this._end].used)
			{
				int num3 = (this._end + this._buffer[0].Length - 1) % this._buffer[0].Length;
				if (textureTimeStamp2 == this._buffer[0][num3].timestamp)
				{
					flag = false;
				}
			}
			if (flag)
			{
				if (this._start == this._end && !this._buffer[0][this._end].used)
				{
					this._baseTimestamp = textureTimeStamp2;
				}
				if (this._end == this._start && this._buffer[0][this._end].used)
				{
					this._start = (this._start + 1) % this._buffer[0].Length;
				}
				for (int i = 0; i < this._mediaPlayer.TextureProducer.GetTextureCount(); i++)
				{
					Texture texture = this._mediaPlayer.TextureProducer.GetTexture(i);
					this._buffer[i][this._end].texture.DiscardContents();
					Graphics.Blit(texture, this._buffer[i][this._end].texture);
					this._buffer[i][this._end].timestamp = textureTimeStamp2;
					this._buffer[i][this._end].used = true;
				}
				this._end = (this._end + 1) % this._buffer[0].Length;
			}
			bool flag2 = this._start != this._end || !this._buffer[0][this._end].used;
			if (flag2)
			{
				for (int j = 0; j < this._buffer.Count; j++)
				{
					this._outputTexture[j].DiscardContents();
					Graphics.Blit(this._buffer[j][this._start].texture, this._outputTexture[j]);
					this._currentDisplayedTimestamp = this._buffer[j][this._start].timestamp;
				}
			}
			if (this._mediaPlayer.Control.IsPaused())
			{
				this.InvalidateBuffer();
			}
			if (flag2)
			{
				return;
			}
			if (this._mediaPlayer.Control.IsPlaying() && !this._mediaPlayer.Control.IsFinished())
			{
				long num4 = this._buffer[0][(this._start + this._bufferSize / 2) % this._bufferSize].timestamp - this._baseTimestamp;
				double num5 = (double)Mathf.Abs((float)((double)this._elapsedTimeSinceBase * 10000000.0) - (float)num4);
				double num6 = (double)((float)(this._buffer[0].Length / 2) / this._mediaPlayer.Info.GetVideoFrameRate() * 1E+07f);
				if (num5 > num6)
				{
					this._elapsedTimeSinceBase = (float)num4 / 1E+07f;
				}
				if (this._resampleMode == Resampler.ResampleMode.POINT)
				{
					this.PointUpdate();
				}
				else if (this._resampleMode == Resampler.ResampleMode.LINEAR)
				{
					this.LinearUpdate();
				}
				this._elapsedTimeSinceBase += Time.unscaledDeltaTime;
			}
		}

		public void UpdateTimestamp()
		{
			if (this._lastDisplayedTimestamp != this._currentDisplayedTimestamp)
			{
				this._lastDisplayedTimestamp = this._currentDisplayedTimestamp;
				this._frameDisplayedTimer = 0;
			}
			this._frameDisplayedTimer++;
		}

		private List<Resampler.TimestampedRenderTexture[]> _buffer = new List<Resampler.TimestampedRenderTexture[]>();

		private MediaPlayer _mediaPlayer;

		private RenderTexture[] _outputTexture;

		private int _start;

		private int _end;

		private int _bufferSize;

		private long _baseTimestamp;

		private float _elapsedTimeSinceBase;

		private Material _blendMat;

		private Resampler.ResampleMode _resampleMode;

		private string _name = string.Empty;

		private long _lastTimeStamp = -1L;

		private int _droppedFrames;

		private long _lastDisplayedTimestamp;

		private int _frameDisplayedTimer;

		private long _currentDisplayedTimestamp;

		private const string ShaderPropT = "_t";

		private const string ShaderPropAftertex = "_AfterTex";

		private int _propAfterTex;

		private int _propT;

		private class TimestampedRenderTexture
		{
			public RenderTexture texture;

			public long timestamp;

			public bool used;
		}

		public enum ResampleMode
		{
			POINT,
			LINEAR
		}
	}
}
