using Engine.Graphics.Pixel;
using Engine.Graphics.Texture;
using OpenTK.Graphics.OpenGL;

namespace Engine.Graphics.Framebuffer;

public class FramebufferBuilder(int parWidth, int parHeight)
{
  private readonly List<AttachmentSpecification> _attachments = [];
  private readonly HashSet<FramebufferAttachment> _usedAttachments = [];

  private FramebufferAttachment _currentColorAttachment = FramebufferAttachment.ColorAttachment0;

  public FramebufferBuilder AddColorAttachment<T>()
    where T : struct, IPixel
  {
    if (_currentColorAttachment > FramebufferAttachment.ColorAttachment15)
    {
      throw new ArgumentException("Framebuffer already has 16 color attachments");
    }

    var pixel = default(T);

    return AddAttachment(new TextureAttachmentSpecification
    {
      FramebufferAttachment = _currentColorAttachment++,
      PixelFormat = pixel.Format,
      PixelType = pixel.Type,
      PixelInternalFormat = pixel.InternalFormat
    });
  }

  public FramebufferBuilder AddDepthAttachment()
  {
    return AddAttachment(new RenderbufferAttachmentSpecification
    {
      FramebufferAttachment = FramebufferAttachment.DepthAttachment,
      RenderbufferStorage = RenderbufferStorage.DepthComponent
    });
  }

  private FramebufferBuilder AddAttachment(AttachmentSpecification parAttachment)
  {
    if (_usedAttachments.Contains(parAttachment.FramebufferAttachment))
    {
      throw new ArgumentException("Framebuffer already has attachment of type " +
                                  parAttachment.FramebufferAttachment);
    }

    _attachments.Add(parAttachment);
    _usedAttachments.Add(parAttachment.FramebufferAttachment);

    return this;
  }

  public Framebuffer Build()
  {
    var attachments = _attachments
      .ToDictionary(
        parAttachment => parAttachment.FramebufferAttachment,
        parAttachment => parAttachment.Create(parWidth, parHeight)
      );

    return new Framebuffer(parWidth, parHeight, attachments);
  }

  private class TextureAttachmentSpecification : AttachmentSpecification
  {
    public PixelFormat PixelFormat { get; init; }
    public PixelType PixelType { get; init; }
    public PixelInternalFormat PixelInternalFormat { get; init; }

    public override DynamicTexture Create(int parWidth, int parHeight)
    {
      return new DynamicTexture(parWidth, parHeight, PixelFormat, PixelType, PixelInternalFormat);
    }
  }

  private class RenderbufferAttachmentSpecification : AttachmentSpecification
  {
    public RenderbufferStorage RenderbufferStorage { get; init; }

    public override Renderbuffer Create(int parWidth, int parHeight)
    {
      return new Renderbuffer(parWidth, parHeight, RenderbufferStorage);
    }
  }

  private abstract class AttachmentSpecification
  {
    public FramebufferAttachment FramebufferAttachment { get; init; }

    public abstract IFramebufferAttachment Create(int parWidth, int parHeight);
  }
}