using System.Collections.ObjectModel;
using Engine.Graphics.Texture;
using OpenTK.Graphics.OpenGL;
using Serilog;

namespace Engine.Graphics.Framebuffer;

public class Framebuffer : OpenGlObject
{
  public int Width
  {
    get => _width;
    private set
    {
      ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);

      _width = value;
    }
  }

  public int Height
  {
    get => _height;
    private set
    {
      ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);

      _height = value;
    }
  }

  internal Texture.Texture? TextureInternal => GetAttachment<DynamicTexture>(FramebufferAttachment.ColorAttachment0);

  private readonly IDictionary<FramebufferAttachment, IFramebufferAttachment> _attachments;
  private int _width;
  private int _height;

  internal Framebuffer(int parWidth, int parHeight,
    IDictionary<FramebufferAttachment, IFramebufferAttachment> parAttachments)
  {
    Width = parWidth;
    Height = parHeight;
    _attachments = new ReadOnlyDictionary<FramebufferAttachment, IFramebufferAttachment>(parAttachments);

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

    foreach (var attachment in _attachments)
    {
      attachment.Value.Attach(this, attachment.Key);
    }

    var status = GL.CheckNamedFramebufferStatus(Handle, FramebufferTarget.Framebuffer);
    if (status != FramebufferStatus.FramebufferComplete)
    {
      throw new Exception($"Framebuffer is not complete: {status}");
    }
  }

  public static FramebufferBuilder Builder(int parWidth, int parHeight)
  {
    return new FramebufferBuilder(parWidth, parHeight);
  }

  internal T? GetAttachment<T>(FramebufferAttachment parAttachment) where T : IFramebufferAttachment
  {
    if (!_attachments.TryGetValue(parAttachment, out var attachmentValue))
    {
      return default;
    }

    if (attachmentValue is T t)
    {
      return t;
    }

    return default;
  }

  public void Resize(int parWidth, int parHeight)
  {
    if (Width == parWidth && Height == parHeight)
    {
      return;
    }

    Width = parWidth;
    Height = parHeight;

    foreach (var attachment in _attachments)
    {
      attachment.Value.Resize(parWidth, parHeight);
    }

    Log.Debug("Framebuffer {Handle} resized to {Width}x{Height}", Handle, parWidth, parHeight);
  }

  internal override void Bind()
  {
    GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle);
  }

  internal override void Unbind()
  {
    GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
  }

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