using System.Runtime.InteropServices;
using Engine.Graphics.Buffer.Vertex;
using OpenTK.Graphics.OpenGL;
using Serilog;

namespace Engine.Graphics.Buffer;

public class VertexBuffer<T> : OpenGlObject
  where T : struct, IVertex
{
  internal int Count { get; }

  private readonly int _stride = Marshal.SizeOf<T>();

  public VertexBuffer(int parCount, BufferStorageFlags parFlags = BufferStorageFlags.None)
  {
    if (!IVertex.IsValid(typeof(T)))
    {
      throw new ArgumentException($"Type {typeof(T).Name} is not a valid vertex type");
    }

    if (parCount <= 0)
    {
      throw new ArgumentException("Count must be greater than 0");
    }

    Count = parCount;

    GL.CreateBuffers(1, out int handle);
    Handle = handle;

    GL.NamedBufferStorage(Handle, Count * _stride, IntPtr.Zero, parFlags);

    Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name);
  }

  public VertexBuffer(T[] parData, BufferStorageFlags parFlags = BufferStorageFlags.None)
  {
    if (!IVertex.IsValid(typeof(T)))
    {
      throw new ArgumentException($"Type {typeof(T).Name} is not a valid vertex type");
    }

    if (parData.Length <= 0)
    {
      throw new ArgumentException("Count must be greater than 0");
    }

    Count = parData.Length;

    GL.CreateBuffers(1, out int handle);
    Handle = handle;

    GL.NamedBufferStorage(Handle, Count * _stride, parData, parFlags);

    Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name);
  }

  public void UploadData(T[] parData)
  {
    UploadData(0, parData, parData.Length);
  }

  public void UploadData(T[] parData, int parCount)
  {
    UploadData(0, parData, parCount);
  }

  public void UploadData(int parOffset, T[] parData, int parCount)
  {
    if (parOffset < 0)
    {
      throw new ArgumentException("Offset must be greater than 0");
    }

    if (parCount <= 0)
    {
      throw new ArgumentException("Count must be greater than 0");
    }

    if (parData.Length < parCount)
    {
      throw new ArgumentException("Data array is too small");
    }

    if (parCount + parOffset > Count)
    {
      throw new ArgumentException("Data array is too large");
    }

    GL.NamedBufferSubData(Handle, parOffset * _stride, parCount * _stride, parData);
  }

  internal override void Bind()
  {
    GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
  }

  internal override void Unbind()
  {
    GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
  }

  protected override void Destroy()
  {
    GL.DeleteBuffer(Handle);
  }
}