using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics
{
	/// 
	/// Buffers are generic data containers that can be used by the GPU.
	/// 
	public class Buffer : GraphicsResource
	{
		protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
		/// 
		/// Size in bytes.
		/// 
		public uint Size { get; }
		/// 
		/// Creates a buffer of appropriate size given a type and element count.
		/// 
		/// The type that the buffer will contain.
		/// The GraphicsDevice.
		/// Specifies how the buffer will be used.
		/// How many elements of type T the buffer will contain.
		/// 
		public unsafe static Buffer Create(
			GraphicsDevice device,
			BufferUsageFlags usageFlags,
			uint elementCount
		) where T : unmanaged
		{
			return new Buffer(
				device,
				usageFlags,
				(uint) Marshal.SizeOf() * elementCount
			);
		}
		/// 
		/// Creates a buffer.
		/// 
		/// An initialized GraphicsDevice.
		/// Specifies how the buffer will be used.
		/// The length of the array. Cannot be resized.
		public Buffer(
			GraphicsDevice device,
			BufferUsageFlags usageFlags,
			uint sizeInBytes
		) : base(device)
		{
			Handle = Refresh.Refresh_CreateBuffer(
				device.Handle,
				(Refresh.BufferUsageFlags) usageFlags,
				sizeInBytes
			);
			Size = sizeInBytes;
		}
		/// 
		/// Reads data out of a buffer and into a span.
		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait is called first.
		/// 
		/// The span that data will be copied to.
		/// The length of the data to read.
		public unsafe void GetData(
			Span data,
			uint dataLengthInBytes
		) where T : unmanaged
		{
#if DEBUG
			if (dataLengthInBytes > Size)
			{
				Logger.LogWarn("Requested too many bytes from buffer!");
			}
#endif
			fixed (T* ptr = data)
			{
				Refresh.Refresh_GetBufferData(
					Device.Handle,
					Handle,
					(IntPtr) ptr,
					dataLengthInBytes
				);
			}
		}
		/// 
		/// Reads data out of a buffer and into an array.
		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait is called first.
		/// 
		/// The span that data will be copied to.
		/// The length of the data to read.
		public unsafe void GetData(
			T[] data,
			uint dataLengthInBytes
		) where T : unmanaged
		{
			GetData(new Span(data), dataLengthInBytes);
		}
		/// 
		/// Reads data out of a buffer and into a span.
		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait is called first.
		/// Fills the span with as much data from the buffer as it can.
		/// 
		/// The span that data will be copied to.
		public unsafe void GetData(
			Span data
		) where T : unmanaged
		{
			var lengthInBytes = System.Math.Min(data.Length * Marshal.SizeOf(), Size);
			GetData(data, (uint) lengthInBytes);
		}
		/// 
		/// Reads data out of a buffer and into an array.
		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait is called first.
		/// Fills the array with as much data from the buffer as it can.
		/// 
		/// The span that data will be copied to.
		public unsafe void GetData(
			T[] data
		) where T : unmanaged
		{
			var lengthInBytes = System.Math.Min(data.Length * Marshal.SizeOf(), Size);
			GetData(new Span(data), (uint) lengthInBytes);
		}
		public static implicit operator BufferBinding(Buffer b)
		{
			return new BufferBinding(b, 0);
		}
	}
}