using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using DirectShowLib;
using SharpDX;

namespace FDK
{
	public class CAviDS : IDisposable
	{
		const int timeOutMs = 1000; // グラフ state の遷移完了を待つタイムアウト期間 

		public uint nフレーム高さ
		{
			get
			{
				return (uint)nHeight;
			}
		}

		public uint nフレーム幅
		{
			get
			{
				return (uint)nWidth;
			}
		}
		public bool b再生中
		{
			get
			{
				return bPlaying;
			}
		}
		public bool b一時停止中
		{
			get
			{
				return bPause;
			}
		}

		int nWidth;
		int nHeight;
		long nMediaLength; // [ms]
		bool bPlaying;
		bool bPause;

		public int GetDuration()
		{
			return (int)(nMediaLength / 10000);
		}

		IGraphBuilder builder;
		VideoInfoHeader videoInfo;
		ISampleGrabber grabber;
		IMediaControl control;
		IMediaSeeking seeker;
		IMediaFilter filter;
		FilterState state;
		AMMediaType mediaType;
		IntPtr samplePtr = IntPtr.Zero;
		
		public CAviDS(string filename, double playSpeed)
		{
			int hr = 0x0;

			builder = (IGraphBuilder)new FilterGraph();

			#region [Sample Grabber]
			{
				grabber = new SampleGrabber() as ISampleGrabber;
				mediaType = new AMMediaType();
				mediaType.majorType = MediaType.Video;
				mediaType.subType = MediaSubType.RGB32;
				mediaType.formatType = FormatType.VideoInfo;
				hr = grabber.SetMediaType(mediaType);
				DsError.ThrowExceptionForHR(hr);

				hr = builder.AddFilter((IBaseFilter)grabber, "Sample Grabber");
				DsError.ThrowExceptionForHR(hr);
			}
			#endregion

			hr = builder.RenderFile(filename, null);
			DsError.ThrowExceptionForHR(hr);

			// Null レンダラに接続しないとウィンドウが表示される。
			// また、レンダリングを行わないため処理速度を向上できる。
			CDirectShow.ConnectNullRendererFromSampleGrabber(builder, grabber as IBaseFilter);
			CDirectShow.tグラフを解析しデバッグ出力する(builder);

			IVideoWindow videoWindow = builder as IVideoWindow;
			if (videoWindow != null)
			{
				videoWindow.put_AutoShow(OABool.False);
			}

			#region [Video Info]
			{
				hr = grabber.GetConnectedMediaType(mediaType);
				DsError.ThrowExceptionForHR(hr);

				videoInfo = (VideoInfoHeader)Marshal.PtrToStructure(mediaType.formatPtr, typeof(VideoInfoHeader));
				nWidth = videoInfo.BmiHeader.Width;
				nHeight = videoInfo.BmiHeader.Height;
			}
			#endregion

			#region[ Seeker ]
			{
				seeker = builder as IMediaSeeking;
				hr = seeker.GetDuration(out nMediaLength);
				DsError.ThrowExceptionForHR(hr);
				hr = seeker.SetRate(playSpeed / 20);
				DsError.ThrowExceptionForHR(hr);
			}
			#endregion

			#region [Control]
			{
				control = builder as IMediaControl;
			}
			#endregion

			#region [Filter]
			{
				filter = builder as IMediaFilter;
			}
			#endregion

			grabber.SetBufferSamples(true);
			this.Run();
			this.Pause();

			bPlaying = false;
			bPause = false;		// 外見えには演奏停止している。PAUSE中として外に見せないこと。
		}

		public void Seek(int timeInMs)
		{
			int hr = seeker.SetPositions(new DsLong(timeInMs * 10000), AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning);
			DsError.ThrowExceptionForHR(hr);
			hr = control.GetState(timeOutMs, out state); // state is Running
			DsError.ThrowExceptionForHR(hr);
		}

		public void Run()
		{
			int hr = control.Run();
			DsError.ThrowExceptionForHR(hr);
			hr = control.GetState(timeOutMs, out state);
			DsError.ThrowExceptionForHR(hr);
			bPlaying = true;
			bPause = false;
		}

		public void Stop()
		{
			int hr = control.Stop();
			DsError.ThrowExceptionForHR(hr);
			hr = control.GetState(timeOutMs, out state);
			DsError.ThrowExceptionForHR(hr);
			bPlaying = false;
			bPause = false;
		}

		public void Pause()
		{
			int hr = control.Pause();
			DsError.ThrowExceptionForHR(hr);
			hr = control.GetState(timeOutMs, out state);
			DsError.ThrowExceptionForHR(hr);
			bPause = true;
		}

		public void ToggleRun()
		{
			int hr = control.GetState(timeOutMs, out state);
			DsError.ThrowExceptionForHR(hr);
			if( state == FilterState.Paused )
			{
				Run();
			}
			else if( state == FilterState.Running )
			{
				Pause();
			}
		}

		public unsafe void tGetBitmap(SharpDX.Direct3D9.Device device, CTexture ctex, int timeMs)
		{
			int bufferSize = 0;
			int hr = 0x0;
			
			hr = grabber.GetCurrentBuffer(ref bufferSize, IntPtr.Zero);
			DsError.ThrowExceptionForHR(hr);

			if ( samplePtr == IntPtr.Zero )
			{
				samplePtr = Marshal.AllocHGlobal(bufferSize);
			}

			DataRectangle rectangle3 = ctex.texture.LockRectangle(0, SharpDX.Direct3D9.LockFlags.None);
			hr = grabber.GetCurrentBuffer(ref bufferSize, rectangle3.DataPointer);
			DsError.ThrowExceptionForHR(hr);
			ctex.texture.UnlockRectangle(0);
		}

		#region [ Dispose-Finalize パターン実装 ]
		public void Dispose()
		{
			if (!this.bDisposed)
			{
				if (null != builder)
				{
					Marshal.ReleaseComObject(builder);
					builder = null;
				}
				if( null != grabber )
				{
					Marshal.ReleaseComObject(grabber);
					grabber = null;
				}
				if (null != mediaType)
				{
					DsUtils.FreeAMMediaType(mediaType);
					mediaType = null;
				}
				if (samplePtr != IntPtr.Zero)
				{
					Marshal.FreeHGlobal(samplePtr);
				}

				GC.SuppressFinalize(this);
				this.bDisposed = true;
			}
		}

		~CAviDS()
		{
			this.Dispose();
		}
		#endregion

		private bool bDisposed = false;
	}
}
