#include "renderer.h"
#include "scene.h"
#include "graphic.h"
#include "directional_shadow.h"
#include "material.h"
#include "shader.h"
#include "states_render.h"
#include "skybox_renderer.h"
#include "ibl_baker.h"

#include <iostream>

namespace {
// [新增] WIC 不依赖扩展名判断，但这里用扩展名枚举帮助自动查找 C:/temp 下的六张图。
std::optional<std::filesystem::path> FindCubeFaceFile(
    const std::filesystem::path &directory,
    const std::wstring_view stem) {
    static constexpr std::array<std::wstring_view, 6> extensions{
        L".png", L".jpg", L".jpeg", L".bmp", L".tif", L".tiff"
    };

    for (const std::wstring_view extension: extensions) {
        std::wstring fileName{stem};
        fileName.append(extension);
        std::filesystem::path candidate = directory / fileName;

        std::error_code error;
        if (std::filesystem::is_regular_file(candidate, error) && !error) {
            return candidate;
        }
    }

    return std::nullopt;
}
}


Renderer::Renderer(Graphic &graphic) : m_graphic(graphic) {
    // // [PBR-ADD] 在任何 PBR Material 创建前准备默认纹理。
    // if (!PBRResources::S_Initialize(graphic.GetDevice())) {
    //     throw std::runtime_error{"初始化 PBR 默认资源失败"};
    // }

    m_directionalShadow = std::make_unique<DirectionalShadow>();
    DirectionalShadow::Settings settings{};
    m_directionalShadow->Initialize(graphic.GetDevice(), settings);

    //[add]ibl
    m_iblResources = std::make_unique<IBLResources>();
    if (!m_iblResources->Initialize(graphic.GetDevice())) {
        throw std::runtime_error{"Initialize IBL resources failed"};
    }

    // [新增] 新 Shader 与默认 PBR Shader 放在同一目录，避免额外修改 StatesRender。
    const std::filesystem::path shaderDirectory =
            StatesRender::S_GetDefaultShaderPath().parent_path();

    m_iblBaker = std::make_unique<IBLBaker>();
    if (!m_iblBaker->Initialize(
        graphic.GetDevice(), shaderDirectory / L"ibl_bake.hlsl")) {
        throw std::runtime_error{"初始化 IBL 烘焙器失败"};
    }

    m_skyboxRenderer = std::make_unique<SkyboxRenderer>();
    if (!m_skyboxRenderer->Initialize(graphic.GetDevice(), shaderDirectory / L"skybox.hlsl")) {
        throw std::runtime_error{"初始化天空盒渲染器失败"};
    }



    // todo 暂时在这里初始化shadow管线资源,以后分离pass资源 ↓↓↓
    // 建立shadow depth VS
    m_VS_ShadowDepth = Shader::S_CompileShader(StatesRender::S_GetShadowDepthShaderPath(),
                                               Shader::Entry::VertexShader,
                                               graphic.GetDevice());
    if (!m_VS_ShadowDepth) {
        throw std::runtime_error{"编译ShadowDepth Shader失败"};
    }

    // [新增] 默认尝试读取 C:/temp。文件不存在时不阻止引擎启动，PBR 会使用旧环境项回退。
    if (!LoadEnvironmentCubeDirectory(L"C:/temp")) {
        OutputDebugStringW(L"未加载默认 TextureCube。请在 C:/temp 放置 "
            L"posx/negx/posy/negy/posz/negz 六张同尺寸正方形图片。\n");
    }
}
Renderer::~Renderer() = default;

/* obsolete
void Renderer::RenderFrame_Old(Scene &scene) const {
    ID3D11DeviceContext *context = m_graphic.GetDeviceContext();

    // 更新 SceneCB：相机 View/Projection、主光源方向。
    scene.UpdateCBuffer(context);


    // * 阴影只覆盖 shadowDistance 范围。
    // * 使用与主相机相同的 FOV、Aspect、Near，
    // * 但 Far 改为 shadowDistance。

    // 仅用于确定需要覆盖的相机视锥区域的proj矩阵
    const DirectX::XMMATRIX shadowCameraProjection =
            scene.BuildShadowCameraProj(m_directionalShadow->ShadowDistance());


    m_directionalShadow->UpdateMatrices(
        scene.GetCameraView(),
        shadowCameraProjection,
        scene.GetSceneState()->lightDir);


    // ------------------------------------------------------------
    // Shadow Pass
    // ------------------------------------------------------------

    m_directionalShadow->BeginDepthPass(context);
    context->IASetInputLayout(m_VS_ShadowDepth->m_input_layout.Get());
    context->VSSetShader(m_VS_ShadowDepth->m_vertex_shader.Get(), nullptr, 0);

    int meshc = 0;
    for (RenderObject *obj: scene.GetRenderObjects()) {
        for (RenderMesh &mesh: obj->GetRenderMeshes()) {
            if (!mesh.CastShadow()) { continue; }

            // 第一版可以全部绘制。
            // 后续应使用光源正交视锥进行剔除。
            mesh.Update_CBPerObj(context);
            mesh.Draw_Shadow(context);
        }
    }

    m_directionalShadow->EndDepthPass(context);

    // ------------------------------------------------------------
    // Main Pass
    // ------------------------------------------------------------

    // 必须重新设置主 RTV、主 DSV、主 Viewport、
    // RasterizerState、DepthStencilState。
    m_graphic.BeginMainPass();

    // 明确重绑 b0，避免后续 Pass 重构时依赖遗留状态。
    scene.UpdateCBuffer(context); // b0

    m_directionalShadow->BindMainPass(context); // b3、t8、s2

    for (RenderObject *obj: scene.GetRenderObjects()) {
        for (RenderMesh &mesh: obj->GetRenderMeshes()) {
            // Material 只能绑定 b1、t0~t7、s0~s1。
            mesh.Update_BindMat(context);
            mesh.Update_CBPerObj(context);
            mesh.Draw_Main(context);
        }
    }

    // for (RenderMesh *renderMesh: renderMeshes) {
    //     // Material 只能绑定 b1、t0~t7、s0~s1。
    //     renderMesh->GetMaterial()->Bind(context);
    //
    //     renderMesh->Draw_Main(context);
    // }

    m_graphic.EndMainPass();

    // ------------------------------------------------------------
    // Present
    // ------------------------------------------------------------
    m_graphic.Present();
}
*/

void Renderer::RenderFrame(Scene &scene) const {
    ID3D11DeviceContext *context = m_graphic.GetDeviceContext();

    // 填充 b0，同时确保相机矩阵是本帧最新值。
    scene.UpdateCBuffer(context);

    /*
     * 阴影只覆盖 shadowDistance 范围。
     * 使用与主相机相同的 FOV、Aspect、Near，
     * 但 Far 改为 shadowDistance。
     */
    const DirectX::XMMATRIX shadowCameraProjection =
            scene.BuildShadowCameraProj(m_directionalShadow->ShadowDistance());

    m_directionalShadow->UpdateMatrices(
        scene.GetCameraView(),
        shadowCameraProjection,
        scene.GetSceneState()->lightDir);

    // ------------------------------------------------------------
    // Shadow Pass：PBR 不需要改变现有 pass，只需要主 PS 消费阴影结果。
    // ------------------------------------------------------------
    m_directionalShadow->BeginDepthPass(context);
    context->IASetInputLayout(m_VS_ShadowDepth->m_input_layout.Get());
    context->VSSetShader(m_VS_ShadowDepth->m_vertex_shader.Get(), nullptr, 0);

    for (RenderObject *obj: scene.GetRenderObjects()) {
        for (RenderMesh &mesh: obj->GetRenderMeshes()) {
            if (!mesh.CastShadow()) continue;

            // 第一版可以全部绘制。
            // 后续应使用光源正交视锥进行剔除。
            mesh.Update_CBPerObj(context);
            mesh.Draw_Shadow(context);
        }
    }

    // [修改] 每个阴影 Pass 只结束一次；上一版补丁中的重复调用已移除。
    m_directionalShadow->EndDepthPass(context);

    // ------------------------------------------------------------
    // Forward PBR Main Pass
    // ------------------------------------------------------------
    // 必须重新设置主 RTV、主 DSV、主 Viewport、
    // RasterizerState、DepthStencilState。
    m_graphic.BeginMainPass();

    // 明确重绑 b0，避免后续 Pass 重构时依赖遗留状态。
    scene.UpdateCBuffer(context); // b0
    m_directionalShadow->BindMainPass(context); // b3, t8, s2
    (void)m_iblResources->BindMainPass(context); // b4, t9-t11, s3 [add]ibl


    for (RenderObject *obj: scene.GetRenderObjects()) {
        for (RenderMesh &mesh: obj->GetRenderMeshes()) {
            // Material 只能绑定 b1、t0~t7、s0~s1。
            mesh.Update_BindMat(context); // b1, t0~t7, s0
            mesh.Update_CBPerObj(context); // b2
            mesh.Draw_Main(context);
        }
    }
    m_graphic.EndMainPass();

    // ------------------------------------------------------------
    // Sky Box
    // ------------------------------------------------------------
    // [新增] 最后绘制天空盒。深度固定为远平面，因此只填充没有物体的背景像素。
    if (m_skyboxRenderer) {
        m_skyboxRenderer->Draw(context); // t12, s4
    }

    // ------------------------------------------------------------
    // Present
    // ------------------------------------------------------------
    m_graphic.Present();
}
//[ibl-add]
bool Renderer::LoadEnvironmentCube(const std::array<std::filesystem::path, 6> &facePaths) {
    if (!m_iblBaker || !m_skyboxRenderer || !m_iblResources) return false;

    ID3D11Device *device = m_graphic.GetDevice();
    ID3D11DeviceContext *context = m_graphic.GetDeviceContext();

    // [新增] 普通 LDR 六面图按 sRGB 导入；采样时硬件自动转换到线性空间。
    // 同时生成普通 mip，供后续 GGX 烘焙时减少高频走样。
    std::unique_ptr<TextureCube> environment = TextureCube::LoadFromFiles(
        device, context, "EnvironmentCube", facePaths, true, true);
    if (!environment) return false;

    IBLBaker::Result baked{};
    IBLBaker::Settings bakeSettings{};
    if (!m_iblBaker->Bake(context, *environment, baked, bakeSettings)) {
        OutputDebugStringW(L"TextureCube 导入成功，但 IBL 烘焙失败。\n");
        return false;
    }

    if (!m_iblResources->SetTextures(
        baked.irradiance.get(),
        baked.prefiltered.get(),
        baked.brdfLut.get())) {
        return false;
    }

    // [新增] 天空背景使用原始环境 Cube；PBR 物体使用烘焙后的三个 IBL 资源。
    m_skyboxRenderer->SetTexture(environment.get());

    // 所有步骤成功后再替换旧资源，运行时换环境失败时仍可继续使用旧环境。
    m_environmentCube = std::move(environment);
    m_iblIrradianceCube = std::move(baked.irradiance);
    m_iblPrefilteredCube = std::move(baked.prefiltered);
    m_iblBrdfLut = std::move(baked.brdfLut);
    return true;
}
//[ibl-add]
bool Renderer::LoadEnvironmentCubeDirectory(const std::filesystem::path &directory) {
    // [新增] 数组顺序必须与 TextureCube::Face / D3D11 数组切片顺序一致。
    static constexpr std::array<std::wstring_view, 6> faceStems{
        L"posx", L"negx", L"posy", L"negy", L"posz", L"negz"
    };

    std::array<std::filesystem::path, 6> facePaths{};
    for (std::size_t faceIndex = 0; faceIndex < faceStems.size(); ++faceIndex) {
        const auto resolved = FindCubeFaceFile(directory, faceStems[faceIndex]);
        if (!resolved) return false;
        facePaths[faceIndex] = *resolved;
    }

    return LoadEnvironmentCube(facePaths);
}
bool Renderer::SetIBLTextures(const TextureCube *irradiance,
                              const TextureCube *prefilteredEnvironment,
                              const Texture2D *brdfLut) const {
    return m_iblResources &&
           m_iblResources->SetTextures(irradiance, prefilteredEnvironment, brdfLut);
}
void Renderer::SetIBLIntensity(float intensity) const noexcept {
    if (m_iblResources) m_iblResources->SetIntensity(intensity);
}
void Renderer::SetIBLRotationRadians(float radians) const noexcept {
    if (m_iblResources) m_iblResources->SetRotationRadians(radians);
}
void Renderer::SetIBLEnabled(bool enabled) const noexcept {
    if (m_iblResources) m_iblResources->SetEnabled(enabled);
}

















//
