#include "ibl_resources.h"

#include <algorithm>

bool IBLResources::Initialize(ID3D11Device *device) {
    if (!device) return false;
    if (m_constantBuffer.Get() && m_sampler.Get()) return true;

    D3D11_BUFFER_DESC bufferDesc{};
    bufferDesc.ByteWidth = static_cast<UINT>(sizeof(ConstantBufferData));
    bufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    bufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

    if (FAILED(device->CreateBuffer(
        &bufferDesc, nullptr, m_constantBuffer.ReleaseAndGetAddressOf()))) {
        return false;
    }

    D3D11_SAMPLER_DESC samplerDesc{};
    samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
    samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
    samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
    samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    samplerDesc.MinLOD = 0.0f;
    samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

    if (FAILED(device->CreateSamplerState(
        &samplerDesc, m_sampler.ReleaseAndGetAddressOf()))) {
        m_constantBuffer.Reset();
        return false;
    }

    return true;
}

bool IBLResources::SetTextures(const TextureCube *irradiance,
                               const TextureCube *prefilteredEnvironment,
                               const Texture2D *brdfLut) {
    ClearTextures();

    if (irradiance && irradiance->SRV()) {
        m_irradianceSRV = irradiance->SRV();
    }
    if (prefilteredEnvironment && prefilteredEnvironment->SRV()) {
        m_prefilteredSRV = prefilteredEnvironment->SRV();
        m_prefilterMaxMip = prefilteredEnvironment->MipLevels() > 0
                                ? static_cast<float>(prefilteredEnvironment->MipLevels() - 1)
                                : 0.0f;
    }
    if (brdfLut && brdfLut->SRV()) {
        m_brdfLutSRV = brdfLut->SRV();
    }

    return IsReady();
}

void IBLResources::ClearTextures() noexcept {
    m_irradianceSRV.Reset();
    m_prefilteredSRV.Reset();
    m_brdfLutSRV.Reset();
    m_prefilterMaxMip = 0.0f;
}

void IBLResources::SetIntensity(const float intensity) noexcept {
    m_settings.intensity = std::max(intensity, 0.0f);
}

void IBLResources::SetRotationRadians(const float radians) noexcept {
    m_settings.rotationRadians = radians;
}

void IBLResources::SetEnabled(const bool enabled) noexcept {
    m_settings.enabled = enabled;
}

void IBLResources::SetSettings(const Settings &settings) noexcept {
    m_settings = settings;
    m_settings.intensity = std::max(m_settings.intensity, 0.0f);
}

bool IBLResources::IsReady() const noexcept {
    return m_irradianceSRV.Get() != nullptr &&
           m_prefilteredSRV.Get() != nullptr &&
           m_brdfLutSRV.Get() != nullptr;
}

bool IBLResources::BindMainPass(ID3D11DeviceContext *context) const {
    if (!context || !m_constantBuffer.Get() || !m_sampler.Get()) return false;

    const ConstantBufferData data{
        DirectX::XMFLOAT4{
            m_settings.intensity,
            m_prefilterMaxMip,
            m_settings.rotationRadians,
            m_settings.enabled && IsReady() ? 1.0f : 0.0f
        }
    };

    D3D11_MAPPED_SUBRESOURCE mapped{};
    if (FAILED(context->Map(
        m_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) {
        return false;
    }

    std::memcpy(mapped.pData, &data, sizeof(data));
    context->Unmap(m_constantBuffer.Get(), 0);

    ID3D11Buffer *constantBuffer = m_constantBuffer.Get();
    context->PSSetConstantBuffers(Z::RenderBindings::IBL_CB, 1, &constantBuffer);

    ID3D11ShaderResourceView *resources[TextureCount] = {
        m_irradianceSRV.Get(),
        m_prefilteredSRV.Get(),
        m_brdfLutSRV.Get()
    };
    context->PSSetShaderResources(Z::RenderBindings::IrradianceTexture, TextureCount, resources);

    ID3D11SamplerState *sampler = m_sampler.Get();
    context->PSSetSamplers(Z::RenderBindings::IBLSamplerSlot, 1, &sampler);
    return true;
}

void IBLResources::UnbindShaderResources(ID3D11DeviceContext *context) const {
    if (!context) return;

    ID3D11ShaderResourceView *nullResources[TextureCount] = {};
    context->PSSetShaderResources(Z::RenderBindings::IrradianceTexture, TextureCount, nullResources);
}










//
