//
// Created by sfd on 25-5-25.
//

#include "OpenGLFrameBuffer.h"
#include <glad/glad.h>
#include <Hazel/Core/Log.h>

namespace Hazel
{

    static constexpr uint32_t s_MaxFrameBufferSize = 8192;

    namespace Utils
    {

        static GLenum TextureTarget(bool multisample)
        {
            return multisample ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
        }

        static void CreateTextures(const bool multisample, uint32_t* outID, const uint32_t count)
        {
            glCreateTextures(TextureTarget(multisample), count, outID);
        }

        static void BindTexture(bool multisampleID, uint32_t id)
        {
            glBindTexture(TextureTarget(multisampleID), id);
        }

        static void AtttachColorTexture(uint32_t id, const int samples, const GLenum internalFormat,const GLenum format, const uint32_t width, const uint32_t height, int index)
        {
           const bool multisample = samples > 1;
            if (multisample)
            {
                glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, internalFormat, width, height, GL_FALSE);
            }else
            {
                glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr);

                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            }

            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + index, TextureTarget(multisample), id, 0);
        }

        static void AtttachDepthTexture(uint32_t id, const int samples, const GLenum format, const GLenum attachmentType, const uint32_t width, const uint32_t height)
        {
           const bool multisample = samples > 1;
            if (multisample)
            {
                glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, format, width, height, GL_FALSE);
            }else
            {
                glTexStorage2D(GL_TEXTURE_2D, 1, format, width, height);

                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            }

            glFramebufferTexture2D(GL_FRAMEBUFFER, attachmentType, TextureTarget(multisample), id, 0);
        }

        static bool IsDepthFormat(const FrameBufferTextureFormat format)
        {
            switch (format)
            {
            case FrameBufferTextureFormat::DEPTH24STENCIL8: return true;
            }

            return false;
        }

        static GLenum TextureFormatToGL(const FrameBufferTextureFormat format)
        {
            switch (format)
            {
                case FrameBufferTextureFormat::RGBA8: return GL_RGBA8;
                case FrameBufferTextureFormat::RED_INTEGER: return GL_RED_INTEGER;
            }

            HZ_CORE_WARN("Unknown framebuffer texture format!");
            return 0;
        }

        static GLenum GLDataType(const FrameBufferTextureFormat format)
        {
            switch (format)
            {
                case FrameBufferTextureFormat::RGBA8: return GL_FLOAT;
                case FrameBufferTextureFormat::RED_INTEGER: return GL_INT;
            }

            HZ_CORE_WARN("Unknown framebuffer texture format!");
            return 0;
        }

    }

    OpenGLFrameBuffer::OpenGLFrameBuffer(const FrameBufferSpecification& spec)
        : m_Specification(spec)
    {
        for (auto spec : m_Specification.Attachments.Attachments)
        {
            if (!Utils::IsDepthFormat(spec.TextureFormat))
                m_ColorAttachmentsSpecifications.emplace_back(spec);
            else
                m_DepthAttachmentSpecification = spec;
        }

        Invalidata();
    }

    OpenGLFrameBuffer::~OpenGLFrameBuffer()
    {
        glDeleteFramebuffers(1, &m_RendererID);
        glDeleteTextures(m_ColorAttachmentsSpecifications.size(), m_ColorAttachments.data());
        glDeleteTextures(1, &m_DepthAttachment);
    }

    void OpenGLFrameBuffer::Bind()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, m_RendererID);

        constexpr int value = -1;
        glClearTexImage(m_ColorAttachments[1], 0,GL_RED_INTEGER, GL_INT, &value);
    }

    void OpenGLFrameBuffer::UnBind()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    void OpenGLFrameBuffer::ClearAttachment(const uint32_t attachmentIndex, const int value)
    {
        if (attachmentIndex >= m_ColorAttachments.size())
        {
            HZ_CORE_ERROR("Attempting to clear an invalid attachment index");
        }

        const auto& spec = m_ColorAttachmentsSpecifications[attachmentIndex];

        glClearTexImage(m_ColorAttachments[attachmentIndex], 0,Utils::TextureFormatToGL(spec.TextureFormat), GL_INT, &value);
    }

    int OpenGLFrameBuffer::ReadPixel(uint32_t attachmentIndex, int x, int y)
    {
        if (attachmentIndex >= m_ColorAttachmentsSpecifications.size())
        {
            HZ_CORE_ERROR("Attempting to read pixel from non-existant attachment");
        }

        glReadBuffer(GL_COLOR_ATTACHMENT0 + attachmentIndex);
        int pixelData;
        glReadPixels(x, y, 1, 1, GL_RED_INTEGER, GL_INT, &pixelData);

        return pixelData;
    }

    void OpenGLFrameBuffer::Resize(const uint32_t width, const uint32_t height)
    {
        if (width == 0 || height == 0)
        {
            HZ_CORE_WARN("attempting to resize framebuffer to {0}x{1}", width, height);
        }

        m_Specification.Width = width > s_MaxFrameBufferSize ? s_MaxFrameBufferSize : width;
        m_Specification.Height = height > s_MaxFrameBufferSize ? s_MaxFrameBufferSize : height;

        Invalidata();
    }

    void OpenGLFrameBuffer::Invalidata()
    {

        if (m_RendererID)
        {
            glDeleteFramebuffers(1, &m_RendererID);
            glDeleteTextures(m_ColorAttachmentsSpecifications.size(), m_ColorAttachments.data());
            glDeleteTextures(1, &m_DepthAttachment);

            m_ColorAttachments.clear();
            m_DepthAttachment = 0;

            glViewport(0, 0, m_Specification.Width, m_Specification.Height);
        }

        bool multisample = m_Specification.Samples > 1;
 glCreateFramebuffers(1, &m_RendererID); glBindFramebuffer(GL_FRAMEBUFFER, m_RendererID);
        // Attachments
        if (m_ColorAttachmentsSpecifications.size() > 0)
        {
            m_ColorAttachments.resize(m_ColorAttachmentsSpecifications.size());
            Utils::CreateTextures(multisample, m_ColorAttachments.data(), m_ColorAttachments.size());

            for (size_t i = 0; i < m_ColorAttachmentsSpecifications.size(); i++)
            {
                Utils::BindTexture(multisample, m_ColorAttachments[i]);
                switch (m_ColorAttachmentsSpecifications[i].TextureFormat)
                {
                    case FrameBufferTextureFormat::RGBA8:
                        Utils::AtttachColorTexture(m_ColorAttachments[i], m_Specification.Samples, GL_RGBA8, GL_RGBA, m_Specification.Width, m_Specification.Height, i);
                        break;
                    case FrameBufferTextureFormat::RED_INTEGER:
                        Utils::AtttachColorTexture(m_ColorAttachments[i], m_Specification.Samples, GL_R32I, GL_RED_INTEGER, m_Specification.Width, m_Specification.Height, i);
                        break;
                }
            }
        }

        if (m_DepthAttachmentSpecification.TextureFormat != FrameBufferTextureFormat::NONE)
        {
            Utils::CreateTextures(multisample, &m_DepthAttachment, 1);
            Utils::BindTexture(multisample, m_DepthAttachment);
            switch (m_DepthAttachmentSpecification.TextureFormat)
            {
                case FrameBufferTextureFormat::DEPTH24STENCIL8:
                    Utils::AtttachDepthTexture(m_DepthAttachment, m_Specification.Samples, GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, m_Specification.Width, m_Specification.Height);
                    break;
            }
        }

        if (m_ColorAttachments.size() > 1)
        {
            if (m_ColorAttachments.size() > 4)
            {
                HZ_CORE_ERROR("Too many color attachments!");
            }

            constexpr GLenum buffers[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3};
            glDrawBuffers(m_ColorAttachments.size(), buffers);
        }
        else if (m_ColorAttachments.empty())
        {
            // Only depth pass
            glDrawBuffer(GL_NONE);
        }


        /*
        glCreateTextures(GL_TEXTURE_2D, 1, &m_ColorAttachment);
        glBindTexture(GL_TEXTURE_2D, m_ColorAttachment);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_Specification.Width, m_Specification.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_ColorAttachment, 0);


        glCreateTextures(GL_TEXTURE_2D, 1, &m_DepthAttachment);
        glBindTexture(GL_TEXTURE_2D, m_DepthAttachment);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, m_Specification.Width, m_Specification.Height);
        // glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_Specification.Width, m_Specification.Height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthAttachment, 0);
        */

        {
            const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
            switch(status) {
            case GL_FRAMEBUFFER_COMPLETE:
                break;
            case GL_FRAMEBUFFER_UNDEFINED: /* 默认帧缓冲不存在 */
                HZ_CORE_ERROR("默认缓冲不存在!"); break;
            case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: /* 附件不完整 */
                HZ_CORE_ERROR("附件不完整!"); break;
            case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: /* 缺少颜色附件 */
                HZ_CORE_ERROR("附件不完整!"); break;
            case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: /* 颜色附件索引无效 */
                HZ_CORE_ERROR("颜色附件索引无效!"); break;
            case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: /* 读取缓冲区设置错误 */
                HZ_CORE_ERROR("读取缓冲区设置错误!"); break;
            case GL_FRAMEBUFFER_UNSUPPORTED: /* 格式组合不支持 */
                HZ_CORE_ERROR("格式组合不支持!"); break;
            case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: /* 多重采样不一致 */
                HZ_CORE_ERROR("多重采样不一致!"); break;
            case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: /* 图层目标不完整 */
                HZ_CORE_ERROR("图层目标不完整!"); break;
            default: /* 未知错误 */
                HZ_CORE_ERROR("未知错误!"); break;

            }
        }

        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
}
