#include "material.h"
#include <iostream>
#include "shader.h"


namespace {
std::unordered_map<std::string, Material *> g_map_Materals{};
}



Material *Material::S_CreateMaterial(const std::string &name, const Shader *shader,
                                     ID3D11Device *device, PBRMaterialDesc desc) {
    auto it = g_map_Materals.find(name);
    if (it != g_map_Materals.end()) {
        // [ADD] TODO 防止同名材质静默复用到不兼容 Shader。
        if (it->second->m_shader != shader) {
            MessageBoxW(nullptr, L"同名 Material 已绑定到其他 Shader",
                        L"Material::S_CreateMaterial",MB_OK);
            return nullptr;
        }
        std::cout << "查询Material,找到了:" << name << std::endl;
        return it->second;
    }

    std::cout << "查询Material,没找着,现在创建:" << name << std::endl;
    Material *mat = new Material();
    mat->m_name = name;
    mat->m_shader = shader;
    // 找到自己的 permat cb 并保存描述符(可能为空)
    mat->m_perMatDesc = shader->GetPerMatCBuffer();

    // 如果有 CBPerMat(b1) 构建它的desc和gpu资源
    if (mat->m_perMatDesc) {
        // [PBR-ADD] 固定框架约定：CBPerMat 必须位于 b1。
        if (mat->m_perMatDesc->bindSlot != Z::RenderBindings::MaterialCB) {
            MessageBoxW(nullptr, L"CBPerMat 必须绑定到 b1", L"Material::S_CreateMaterial",MB_OK);
            delete mat;
            return nullptr;
        }
        mat->m_perMatCBData.assign(mat->m_perMatDesc->byteSize, std::byte{0});
        if (!mat->InitGpuResources(device)) {
            delete mat;
            return nullptr;
        }
    }

    // [MOD] 保存实际 bindPoint，只接管 t0~t7。
    // t8 是 DirectionalShadow 的全局资源，不能由 Material 覆盖。
    for (const Shader::TexBufDesc &texDesc: *shader->GetTexBufferDescs()) {
        if (texDesc.bindPoint > Z::RenderBindings::MaterialTextureLast) {
            continue;
        }

        if (texDesc.bindCount != 1) {
            MessageBoxW(nullptr, L"当前 Material 暂不支持 Texture Array",
                        L"Material::S_CreateMaterial",MB_OK);
            delete mat;
            return nullptr;
        }

        const std::size_t index = mat->m_textureBindings.size();
        mat->m_textureBindings.push_back(TextureBinding{texDesc, nullptr, nullptr});
        mat->m_textureName2Index.emplace(texDesc.name, index);
    }

    // [add]
    // 先按照名字的可能性绑定缺省纹理
    for (auto &item: mat->m_textureBindings) {
        if (item.desc.name.contains("base") || item.desc.name.contains("Base")) {
            mat->AssignTextureSRV(item.desc.name, Texture2D::S_WhiteSRGB());
            continue;
        }
        if (item.desc.name.contains("diffuse") || item.desc.name.contains("Diffuse")) {
            mat->AssignTextureSRV(item.desc.name, Texture2D::S_WhiteSRGB());
            continue;
        }
        if (item.desc.name.contains("normal") || item.desc.name.contains("Normal")) {
            mat->AssignTextureSRV(item.desc.name, Texture2D::S_FlatNormalLinear());
            continue;
        }
        if (item.desc.name.contains("Orm")) {
            mat->AssignTextureSRV(item.desc.name, Texture2D::S_DefaultOrmLinear());
            continue;
        }
        if (item.desc.name.contains("emissive") || item.desc.name.contains("Emissive")) {
            mat->AssignTextureSRV(item.desc.name, Texture2D::S_BlackSRGB());
            continue;
        }
    }

    // 先按照名字的可能性指定缺省 CBuffer prop
    const DirectX::XMFLOAT4 emissiveFactor{
        desc.emissiveFactor.x,
        desc.emissiveFactor.y,
        desc.emissiveFactor.z,
        0.0f
    };

    const DirectX::XMFLOAT4 pbrParams{
        std::clamp(desc.metallicFactor, 0.0f, 1.0f),
        std::clamp(desc.roughnessFactor, 0.045f, 1.0f),
        std::max(desc.normalScale, 0.0f),
        std::clamp(desc.occlusionStrength, 0.0f, 1.0f)
    };



    mat->SetPermatCbProp("gBaseColorFactor", desc.baseColorFactor);
    mat->SetPermatCbProp("gEmissiveFactor", emissiveFactor);
    mat->SetPermatCbProp("gPbrParams", pbrParams);


    g_map_Materals.emplace(std::pair{name, mat});
    return mat;
}

Material *Material::S_GetMaterial(const std::string &name) {
    auto it = g_map_Materals.find(name);
    if (it != g_map_Materals.end()) {
        return it->second;
    }
    return nullptr;
}

//= public
bool Material::Bind(ID3D11DeviceContext *ctx) {
    // 若该material存在CBPerMat, 更新 CBPerMat 的数据
    if (m_perMatDesc) {
        if (b_dirty) {
            D3D11_MAPPED_SUBRESOURCE mapped{};
            HRESULT hr = ctx->Map(m_perMatCbuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD,
                                  0, &mapped);
            if (SUCCEEDED(hr)) {
                std::memcpy(mapped.pData, m_perMatCBData.data(), m_perMatDesc->byteSize);
                ctx->Unmap(m_perMatCbuffer.Get(), 0);
                b_dirty = false;
            } else {
                MessageBoxW(nullptr, L"contex->Map failed", L"Material::Draw_UpdateCB",MB_OK);
                return false;
            }
        }
        ctx->VSSetConstantBuffers(1, 1, m_perMatCbuffer.GetAddressOf());
        ctx->PSSetConstantBuffers(1, 1, m_perMatCbuffer.GetAddressOf());
    }
    // 指派texture 的srv资源
    for (const TextureBinding &binding: m_textureBindings) {
        ID3D11ShaderResourceView *srv = binding.srv;
        ctx->PSSetShaderResources(binding.desc.bindPoint, 1, &srv);
    }
    // 设置Sampler
    ID3D11SamplerState *sampler = TexSampler::S_DefaultColorSamper();
    ctx->PSSetSamplers(Z::RenderBindings::MaterialSampler, 1, &sampler);

    return true;
}
bool Material::SetPermatCbProp(int id, const Z::ShaderInfo::Value &value) {
    if (!m_perMatDesc || id < 0 || static_cast<std::size_t>(id) >= m_perMatDesc->properties.size()) {
        return false;
    }
    std::cout << "r:Material设置CBPerMatProp;index=" << id << std::endl;
    const Shader::PropertyDesc &prop = m_perMatDesc->properties[id];
    if (prop.size > sizeof(value) || prop.offset + prop.size > m_perMatCBData.size()) {
        return false;
    }
    std::memcpy(&m_perMatCBData[prop.offset], &value, prop.size);
    b_dirty = true;
    return true;
}
//[add]
bool Material::SetPermatCbPropRaw(std::string_view propName, const void *data, std::size_t byteSize) {
    if (!data) return false;
    const Shader::PropertyDesc *prop = FindPerMatProperty(propName);
    if (!prop || byteSize != prop->size || prop->offset + prop->size > m_perMatCBData.size()) {
        return false;
    }
    std::memcpy(m_perMatCBData.data() + prop->offset, data, byteSize);
    b_dirty = true;
    return true;
}
//[add]
bool Material::AssignTexture(Texture2D *tex, int index) {
    if (index < 0 || index >= m_textureBindings.size()) {
        MessageBoxW(nullptr, L"Texture index不合法", L"Material::AssignTexture",MB_OK);
        return false;
    }
    m_textureBindings[index].texture2D = tex;
    m_textureBindings[index].srv = tex->cp_srv.Get();

    return true;
}
bool Material::AssignTexture(TextureCube *tex, int index) {
    if (index < 0 || static_cast<std::size_t>(index) >= m_textureBindings.size()) {
        MessageBoxW(nullptr, L"Texture index is invalid", L"Material::AssignTexture", MB_OK);
        return false;
    }

    TextureBinding &binding = m_textureBindings[static_cast<std::size_t>(index)];
    if (binding.desc.dimension != D3D_SRV_DIMENSION_TEXTURECUBE) return false;

    binding.texture2D = nullptr;
    binding.textureCube = tex;
    binding.srv = tex ? tex->SRV() : nullptr;
    return true;
}
//[add]
bool Material::AssignTexture(std::string_view shaderTextureName, Texture2D *tex) {

    TextureBinding *binding = FindTextureBinding(shaderTextureName);
    if (!binding) return false;

    binding->texture2D = tex;
    binding->srv = tex ? tex->cp_srv.Get() : nullptr;
    return true;
}
bool Material::AssignTexture(std::string_view shaderTextureName, TextureCube *tex) {
    TextureBinding *binding = FindTextureBinding(shaderTextureName);
    if (!binding || binding->desc.dimension != D3D_SRV_DIMENSION_TEXTURECUBE) return false;

    binding->texture2D = nullptr;
    binding->textureCube = tex;
    binding->srv = tex ? tex->SRV() : nullptr;
    return true;
}
//[add]
bool Material::AssignTextureSRV(std::string_view shaderTextureName, ID3D11ShaderResourceView *srv) {

    TextureBinding *binding = FindTextureBinding(shaderTextureName);
    if (!binding) return false;

    binding->texture2D = nullptr;
    binding->srv = srv;
    return true;
}

//= private
bool Material::InitGpuResources(ID3D11Device *device) {
    if (m_perMatDesc == nullptr) return false;
    D3D11_BUFFER_DESC d{};
    d.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    d.Usage = D3D11_USAGE_DYNAMIC;
    d.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    // ReSharper disable once CppDFANullDereference
    d.ByteWidth = (m_perMatDesc->byteSize + 15u) & ~15u;
    HRESULT hr = device->CreateBuffer(&d, nullptr, m_perMatCbuffer.GetAddressOf());
    if (FAILED(hr)) {
        MessageBoxW(nullptr, L"Create PerMat Cbuffer failed", L"Material",MB_OK);
        return false;
    }
    return true;
}

const Shader::PropertyDesc *Material::FindPerMatProperty(std::string_view propName) const {
    if (!m_perMatDesc) return nullptr;
    for (const Shader::PropertyDesc &prop: m_perMatDesc->properties) {
        if (prop.name == propName) return &prop;
    }
    return nullptr;
}

Material::TextureBinding *Material::FindTextureBinding(std::string_view shaderTextureName) {
    const auto it = m_textureName2Index.find(std::string(shaderTextureName));
    if (it == m_textureName2Index.end()) return nullptr;
    return &m_textureBindings[it->second];
}
















//
