using CPF.Mac.Foundation;
using CPF.Mac.ObjCRuntime;
using System;
using System.Runtime.InteropServices;

namespace CPF.Mac.CoreGraphics
{
	public class CGDataProvider : INativeObject, IDisposable
	{
		internal IntPtr handle;

		private IntPtr buffer;

		private byte[] reference;

		public IntPtr Handle => handle;

		[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/CoreGraphics.framework/CoreGraphics")]
		private static extern IntPtr CGDataProviderCopyData(IntPtr handle);

		public NSData CopyData()
		{
			IntPtr intPtr = CGDataProviderCopyData(handle);
			NSData result = new NSData(intPtr);
			CGDataProviderRelease(intPtr);
			return result;
		}

		public CGDataProvider(IntPtr handle)
			: this(handle, owns: false)
		{
			this.handle = handle;
		}

		[Preserve(Conditional = true)]
		internal CGDataProvider(IntPtr handle, bool owns)
		{
			this.handle = handle;
			if (!owns)
			{
				CGDataProviderRetain(handle);
			}
		}

		~CGDataProvider()
		{
			Dispose(disposing: false);
		}

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

		[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/CoreGraphics.framework/CoreGraphics")]
		private static extern void CGDataProviderRelease(IntPtr handle);

		[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/CoreGraphics.framework/CoreGraphics")]
		private static extern void CGDataProviderRetain(IntPtr handle);

		protected virtual void Dispose(bool disposing)
		{
			if (handle != IntPtr.Zero)
			{
				if (buffer != IntPtr.Zero)
				{
					Marshal.FreeHGlobal(buffer);
				}
				buffer = IntPtr.Zero;
				CGDataProviderRelease(handle);
				handle = IntPtr.Zero;
			}
			reference = null;
		}

		[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/CoreGraphics.framework/CoreGraphics")]
		private static extern IntPtr CGDataProviderCreateWithFilename(string filename);

		public static CGDataProvider FromFile(string file)
		{
			if (file == null)
			{
				throw new ArgumentNullException("file");
			}
			IntPtr value = CGDataProviderCreateWithFilename(file);
			if (value == IntPtr.Zero)
			{
				return null;
			}
			return new CGDataProvider(value, owns: true);
		}

		public CGDataProvider(string file)
		{
			if (file == null)
			{
				throw new ArgumentNullException("file");
			}
			handle = CGDataProviderCreateWithFilename(file);
			if (handle == IntPtr.Zero)
			{
				throw new ArgumentException("Could not create provider from the specified file");
			}
		}

		[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/CoreGraphics.framework/CoreGraphics")]
		private static extern IntPtr CGDataProviderCreateWithData(IntPtr info, IntPtr data, IntPtr size, IntPtr releaseData);

		public CGDataProvider(IntPtr memoryBlock, int size)
		{
			handle = CGDataProviderCreateWithData(IntPtr.Zero, memoryBlock, new IntPtr(size), IntPtr.Zero);
		}

		public CGDataProvider(IntPtr memoryBlock, int size, bool ownBuffer)
		{
			handle = CGDataProviderCreateWithData(IntPtr.Zero, memoryBlock, new IntPtr(size), IntPtr.Zero);
			if (ownBuffer)
			{
				buffer = memoryBlock;
			}
		}

		public unsafe CGDataProvider(byte[] buffer, int offset, int count)
		{
			if (buffer == null)
			{
				throw new ArgumentNullException("buffer");
			}
			if (offset < 0 || offset > buffer.Length)
			{
				throw new ArgumentException("offset");
			}
			if (offset + count > buffer.Length)
			{
				throw new ArgumentException("offset");
			}
			reference = buffer;
			fixed (byte* value = &buffer[offset])
			{
				handle = CGDataProviderCreateWithData(IntPtr.Zero, (IntPtr)(void*)value, new IntPtr(count), IntPtr.Zero);
			}
		}
	}
}
