// 
// CVDisplayLink.cs: Implements the managed CVDisplayLink
//
// Authors: Kenneth J. Pouncey
//     
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Drawing;
using System.Runtime.InteropServices;

using MonoMac.ObjCRuntime;
using MonoMac.Foundation;
using MonoMac.OpenGL;

namespace MonoMac.CoreVideo {
	public class CVDisplayLink : INativeObject, IDisposable {
		internal IntPtr handle;
		GCHandle callbackHandle;
		
		public CVDisplayLink (IntPtr handle)
		{
			if (handle == IntPtr.Zero)
				throw new Exception ("Invalid parameters to display link creation");

			CVDisplayLinkRetain (handle);
			this.handle = handle;
		}

		[Preserve (Conditional=true)]
		internal CVDisplayLink (IntPtr handle, bool owns)
		{
			if (!owns)
				CVDisplayLinkRetain (handle);

			this.handle = handle;
		}

		~CVDisplayLink ()
		{
			Dispose (false);
		}

		public void Dispose ()
		{
			Dispose (true);
			GC.SuppressFinalize (this);
		}

		public IntPtr Handle {
			get { return handle; }
		}
	
		[DllImport (Constants.CoreVideoLibrary)]
		extern static void CVDisplayLinkRetain (IntPtr handle);
		
		[DllImport (Constants.CoreVideoLibrary)]
		extern static void CVDisplayLinkRelease (IntPtr handle);
		
		protected virtual void Dispose (bool disposing)
		{
			if (callbackHandle.IsAllocated) {
				callbackHandle.Free();
			}

			if (handle != IntPtr.Zero){
				CVDisplayLinkRelease (handle);
				handle = IntPtr.Zero;
			}
		}

	[DllImport (Constants.CoreVideoLibrary)]
	extern static CVReturn CVDisplayLinkCreateWithActiveCGDisplays (IntPtr displayLinkOut);
	public CVDisplayLink ()
	{
		IntPtr displayLinkOut = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (IntPtr)));
	
		try {
			CVReturn ret = CVDisplayLinkCreateWithActiveCGDisplays (displayLinkOut);
	
			if (ret != CVReturn.Success)
				throw new Exception ("CVDisplayLink returned: " + ret);
	
			this.handle = Marshal.ReadIntPtr (displayLinkOut);
		} finally {
			Marshal.FreeHGlobal (displayLinkOut);
		}
	}		

	[DllImport (Constants.CoreVideoLibrary)]
	extern static CVReturn CVDisplayLinkSetCurrentCGDisplay (IntPtr displayLink, int displayId);
	public CVReturn SetCurrentDisplay (int displayId)
	{
		return CVDisplayLinkSetCurrentCGDisplay (this.handle, displayId);
	}     
		
	[DllImport (Constants.CoreVideoLibrary)]
	extern static CVReturn CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext (IntPtr displayLink, IntPtr cglContext, IntPtr cglPixelFormat);
	public CVReturn SetCurrentDisplay (CGLContext cglContext, CGLPixelFormat cglPixelFormat)
	{
		return CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext (this.handle, cglContext.Handle, cglPixelFormat.Handle);
	}     

	[DllImport (Constants.CoreVideoLibrary)]
	extern static int CVDisplayLinkGetCurrentCGDisplay (IntPtr displayLink);
	public int GetCurrentDisplay ()
	{
		return CVDisplayLinkGetCurrentCGDisplay (this.handle);
	}
		
	[DllImport (Constants.CoreVideoLibrary)]
	extern static CVReturn CVDisplayLinkStart (IntPtr displayLink);
	public CVReturn Start ()
	{
		return CVDisplayLinkStart (this.handle);
	}		     
		
	[DllImport (Constants.CoreVideoLibrary)]
	extern static CVReturn CVDisplayLinkStop (IntPtr displayLink);
	public CVReturn Stop ()
	{
		return CVDisplayLinkStop (this.handle);
	}		     
		
	[DllImport (Constants.CoreVideoLibrary)]
	extern static CVTime CVDisplayLinkGetNominalOutputVideoRefreshPeriod (IntPtr displayLink);
	public CVTime NominalOutputVideoRefreshPeriod {
		get {
			return CVDisplayLinkGetNominalOutputVideoRefreshPeriod (this.handle);
		}
	}

	[DllImport (Constants.CoreVideoLibrary)]
	extern static CVTime CVDisplayLinkGetOutputVideoLatency (IntPtr displayLink);
	public CVTime OutputVideoLatency {
		get {
			return CVDisplayLinkGetOutputVideoLatency (this.handle);
		}
	}

	[DllImport (Constants.CoreVideoLibrary)]
	extern static double CVDisplayLinkGetActualOutputVideoRefreshPeriod (IntPtr displayLink);
	public double ActualOutputVideoRefreshPeriod {
		get {
			return CVDisplayLinkGetActualOutputVideoRefreshPeriod (this.handle);
		}
	}
		
	[DllImport (Constants.CoreVideoLibrary)]
	extern static bool CVDisplayLinkIsRunning (IntPtr displayLink);
	public bool IsRunning {
		get {
			return CVDisplayLinkIsRunning (this.handle);
		}
	}
		
	[DllImport (Constants.CoreVideoLibrary)]
	extern static CVReturn CVDisplayLinkGetCurrentTime (IntPtr displayLink, out CVTimeStamp outTime);
	public CVReturn GetCurrentTime (out CVTimeStamp outTime)
	{
		CVReturn ret = CVDisplayLinkGetCurrentTime (this.Handle, out outTime);
			
		return ret;
	}
	
	public delegate CVReturn DisplayLinkOutputCallback (CVDisplayLink displayLink, ref CVTimeStamp inNow, ref CVTimeStamp inOutputTime, CVOptionFlags flagsIn, ref CVOptionFlags flagsOut);	
	delegate CVReturn CVDisplayLinkOutputCallback (IntPtr displayLink, ref CVTimeStamp inNow, ref CVTimeStamp inOutputTime, CVOptionFlags flagsIn, ref CVOptionFlags flagsOut, IntPtr displayLinkContext);		
  
	static CVDisplayLinkOutputCallback static_OutputCallback = new CVDisplayLinkOutputCallback (OutputCallback);
		
#if !MONOMAC
	[MonoPInvokeCallback (typeof (CVDisplayLinkOutputCallback))]
#endif
	static CVReturn OutputCallback (IntPtr displayLink, ref CVTimeStamp inNow, ref CVTimeStamp inOutputTime, CVOptionFlags flagsIn, ref CVOptionFlags flagsOut, IntPtr displayLinkContext)
	{
		GCHandle callbackHandle = GCHandle.FromIntPtr (displayLinkContext);
		DisplayLinkOutputCallback func = (DisplayLinkOutputCallback) callbackHandle.Target;
		CVDisplayLink delegateDisplayLink = new CVDisplayLink(displayLink, false);
		return func (delegateDisplayLink, ref inNow, ref inOutputTime, flagsIn, ref flagsOut);
	}
  
	[DllImport (Constants.CoreVideoLibrary)]
	extern static CVReturn CVDisplayLinkSetOutputCallback (IntPtr displayLink, CVDisplayLinkOutputCallback function, IntPtr userInfo);
	public CVReturn SetOutputCallback (DisplayLinkOutputCallback callback)
	{
		callbackHandle = GCHandle.Alloc (callback);
		CVReturn ret = CVDisplayLinkSetOutputCallback (this.Handle, static_OutputCallback, GCHandle.ToIntPtr (callbackHandle));
			
		return ret;
	}
    }
}
