#include "resource.h"
#include <utility>
#include <vector>
#include <string>
#include <DirectXMath.h>
#include <dxgi.h>
#include <d3d11.h>
#include <wrl/client.h>
#include <wincodec.h>
#include <zassimp/importer.h>
#include <iostream>
#include <render/task.h>
#include <queue>
#include <mutex>
#include <math.hpp>

namespace CB {
// callback wait
std::mutex mtx_cb{};
std::condition_variable cv_cb{};
Z::ResultMessage g_CallbackMessage{};

void CallBack_WakeThreadUp(Z::ResultMessage msg) { // 被Renderer回调
    g_CallbackMessage = msg;
    cv_cb.notify_all();
}
void WaitForResult() {
    std::unique_lock lk(mtx_cb);
    cv_cb.wait(lk);
}

}

namespace LoadedResource {
std::list<Z::Model *> g_LoadedModels{};
Z::Model *GetLoadedModel(std::filesystem::path &path) {
    auto it = std::ranges::find_if(g_LoadedModels,
                                   [&path](auto &i) { return i->path == path; });
    if (it == g_LoadedModels.end()) return nullptr;
    return *it;
}
void RemoveLoadedModel(std::filesystem::path &path) {
    size_t ret = g_LoadedModels.remove_if([&path](auto &i) {
        if (i->path == path) {
            Importer imp{};
            imp.ReleaseLoadedModel(i);
            return true;
        } else {
            return false;
        }
    });
    std::cout << "RemoveLoadedModel count = " << ret << std::endl;
}

std::list<Z::TextureData *> g_LoadedTexture{};
Z::TextureData *GetLoadedTexture(std::filesystem::path &path) {
    auto it = std::ranges::find_if(g_LoadedTexture,
                                   [&path](auto &item) { return item->path == path; });
    if (it == g_LoadedTexture.end()) {
        return nullptr;
    }
    return *it;
}
void RemoveLoadedTexture(std::filesystem::path &path) {
    size_t ret = g_LoadedTexture.remove_if([&path](auto &i) {
        if (i->path == path) {
            delete i;
            return true;
        } else {
            return false;
        }
    });
    std::cout << "RemoveLoadedTexture count = " << ret << std::endl;
}

std::list<SHADER::ShaderInfo *> g_LoadedShaderDesc{};
SHADER::ShaderInfo *GetLoadedShader(std::string &name) {
    auto it = std::ranges::find_if(g_LoadedShaderDesc, [&name](auto &item) {
        return item->name == name;
    });
    if (it != g_LoadedShaderDesc.end()) return *it;
    return nullptr;
}
void RemoveLoadedShader(std::string &name) {
    size_t ret = g_LoadedTexture.remove_if([&name](auto &i) {
        if (i->name == name) {
            delete i;
            return true;
        } else {
            return false;
        }
    });
    std::cout << "RemoveLoadedShader count = " << ret << std::endl;
}


std::list<MAT::MatInstance *> g_LoadedMatInstance{};
MAT::MatInstance *GetLoadedMaterial(std::string &name) {
    auto it = std::ranges::find_if(g_LoadedMatInstance, [&name](auto &i) {
        return i->name == name;
    });
    if (it != g_LoadedMatInstance.end()) return *it;
    return nullptr;
}
void RemoveLoadedMaterial(std::string &name) {
    size_t ret = g_LoadedMatInstance.remove_if([&name](auto &i) {
        if (i->name == name) {
            delete i;
            return true;
        } else {
            return false;
        }
    });
    std::cout << "RemoveLoadedMaterial count = " << ret << std::endl;
}

}


//region MAT
MAT::MatInstance::MatInstance(std::string name, const SHADER::ShaderInfo *shader) :
name(std::move(name)), info(*shader) {}
bool MAT::MatInstance::Update(int index) {
    std::cout << "app Resource:: MatName = " << this->name << " PropIndex = " << index << std::endl;
    Z::Task task{};
    task.type = Z::CmdType::SetMaterialProp;
    task.cmd.set_material_prop.matName = name.c_str();
    task.cmd.set_material_prop.propIndex = index;
    SHADER::PropertyInfo &propinfo = info.cbuffer[info.idx_cbPerMat].props[index]; //
    task.cmd.set_material_prop.value = propinfo.value;
    task.fpCallBack = CB::CallBack_WakeThreadUp;
    Z::Task::AddCommand(task);
    CB::WaitForResult();
    return CB::g_CallbackMessage.res == Z::ResultType::OK;
};

//endregion


//region RENDEROBJ
bool RENDEROBJ::Object::Update(float *pos, float *rot, float *scale) {
    auto quatrot = math::quaternion_yaw_pitch_roll(math::radians(rot[1]),
                                                   math::radians(rot[0]), math::radians(rot[2]));
    auto matrix = math::compose_RowVec({pos[0], pos[1], pos[2]},
                                       quatrot,
                                       {scale[0], scale[1], scale[2]});
    // 向renderer提交修改
    Z::Task task{};
    task.type = Z::CmdType::SetRenderObjMatrix;
    task.fpCallBack = CB::CallBack_WakeThreadUp;
    task.cmd.set_render_obj_matrix.id = this->id;
    std::memcpy(&task.cmd.set_render_obj_matrix.matrix, &matrix, 64);
    Z::Task::AddCommand(task);
    CB::WaitForResult();
    if (CB::g_CallbackMessage.res != Z::ResultType::OK) {
        return false;
    }
    std::memcpy(position, pos, 12);
    std::memcpy(rotation, rot, 12);
    std::memcpy(this->scale, scale, 12);
    return true;
}

//endregion


//region MODEL
bool ModelImporter::LoadFromFile(const std::filesystem::path &path, Z::Model **out) {

    Importer imp{};
    Z::Model *model{};
    if (imp.ImportFromFile(path, &model)) {
        auto name = model->name;
        auto it = std::ranges::find_if(
            LoadedResource::g_LoadedModels, [&name](auto &item) {
                return name == item->name;
            });
        if (it != LoadedResource::g_LoadedModels.end()) {
            MessageBoxW(nullptr, L"模型name重复,导入失败",
                        L"zassimp::LoadFromFile",MB_OK);
            imp.ReleaseLoadedModel(model);
            return false;
        }

        LoadedResource::g_LoadedModels.push_back(model);
        *out = model;
        return true;
    } else {
        MessageBoxW(nullptr, L"载入模型失败", L"app resource.cpp LoadFromFile()",MB_OK);
        return false;
    }
}

//endregion


//region TEXTURE

namespace {
[[maybe_unused]] HRESULT LoadTextureFromWIC(
    ID3D11Device *device,
    const wchar_t *filename,
    ID3D11ShaderResourceView **outSRV,
    ID3D11Texture2D **outTexture = nullptr,
    bool srgb = false
) {
    using namespace Microsoft::WRL;

    if (!device || !filename || !outSRV) return E_INVALIDARG;

    *outSRV = nullptr;
    if (outTexture) *outTexture = nullptr;

    ComPtr<IWICImagingFactory> factory;
    HRESULT hr = CoCreateInstance(
        CLSID_WICImagingFactory,
        nullptr,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&factory)
    );
    if (FAILED(hr)) return hr;

    ComPtr<IWICBitmapDecoder> decoder;
    hr = factory->CreateDecoderFromFilename(
        filename,
        nullptr,
        GENERIC_READ,
        WICDecodeMetadataCacheOnLoad,
        &decoder
    );
    if (FAILED(hr)) return hr;

    ComPtr<IWICBitmapFrameDecode> frame;
    hr = decoder->GetFrame(0, &frame);
    if (FAILED(hr)) return hr;

    UINT width = 0;
    UINT height = 0;
    hr = frame->GetSize(&width, &height);
    if (FAILED(hr)) return hr;

    ComPtr<IWICFormatConverter> converter;
    hr = factory->CreateFormatConverter(&converter);
    if (FAILED(hr)) return hr;

    hr = converter->Initialize(
        frame.Get(),
        GUID_WICPixelFormat32bppRGBA,
        WICBitmapDitherTypeNone,
        nullptr,
        0.0,
        WICBitmapPaletteTypeCustom
    );
    if (FAILED(hr)) return hr;

    const UINT bytesPerPixel = 4;
    const UINT rowPitch = width * bytesPerPixel;
    const UINT imageSize = rowPitch * height;

    std::vector<unsigned char> pixels(imageSize);

    hr = converter->CopyPixels(
        nullptr,
        rowPitch,
        imageSize,
        pixels.data()
    );
    if (FAILED(hr)) return hr;

    // 写入 DirectX 资源

    DXGI_FORMAT format = srgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;

    D3D11_TEXTURE2D_DESC texDesc = {};
    texDesc.Width = width;
    texDesc.Height = height;
    texDesc.MipLevels = 1;
    texDesc.ArraySize = 1;
    texDesc.Format = format;
    texDesc.SampleDesc.Count = 1;
    texDesc.SampleDesc.Quality = 0;
    texDesc.Usage = D3D11_USAGE_DEFAULT;
    texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    texDesc.CPUAccessFlags = 0;
    texDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA initData = {};
    initData.pSysMem = pixels.data();
    initData.SysMemPitch = rowPitch;
    initData.SysMemSlicePitch = imageSize;

    ComPtr<ID3D11Texture2D> texture;
    hr = device->CreateTexture2D(&texDesc, &initData, &texture);
    if (FAILED(hr)) return hr;

    D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
    srvDesc.Format = format;
    srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    srvDesc.Texture2D.MostDetailedMip = 0;
    srvDesc.Texture2D.MipLevels = 1;

    hr = device->CreateShaderResourceView(
        texture.Get(),
        &srvDesc,
        outSRV
    );
    if (FAILED(hr)) return hr;

    if (outTexture) {
        *outTexture = texture.Detach();
    }

    return S_OK;
}



}

//分SRGB(待处理，在render模块）和 REVY(OK)重新导入
Z::TextureData *ImgImporter::ImportFromFile(const std::filesystem::path &path, bool revG,
                                            Z::TextureType type) {
    using namespace Microsoft::WRL;

    if (!std::filesystem::exists(path)) return nullptr;

    ComPtr<IWICImagingFactory> factory;
    HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr,
                                  CLSCTX_INPROC_SERVER,
                                  IID_PPV_ARGS(&factory));
    if (FAILED(hr)) { return nullptr; }

    ComPtr<IWICBitmapDecoder> decoder;
    hr = factory->CreateDecoderFromFilename(path.c_str(), nullptr,
                                            GENERIC_READ,
                                            WICDecodeMetadataCacheOnLoad,
                                            &decoder);
    if (FAILED(hr)) { return nullptr; }

    ComPtr<IWICBitmapFrameDecode> frame;
    hr = decoder->GetFrame(0, &frame);
    if (FAILED(hr)) { return nullptr; }

    UINT width;
    UINT height;
    hr = frame->GetSize(&width, &height);
    if (FAILED(hr)) { return nullptr; }

    ComPtr<IWICFormatConverter> converter;
    hr = factory->CreateFormatConverter(&converter);
    if (FAILED(hr)) { return nullptr; }

    std::unique_ptr<Z::TextureData> _tex = std::make_unique<Z::TextureData>();
    auto u8str = path.stem().u8string();
    _tex->name.assign(reinterpret_cast<const char *>(u8str.data()), u8str.size());
    _tex->path = path;
    _tex->type = type;

    //1 初始化,统一成RGBA8
    _tex->bytesPerPixel = 4;
    hr = converter->Initialize(frame.Get(), GUID_WICPixelFormat32bppRGBA,
                               WICBitmapDitherTypeNone, nullptr, 0.0,
                               WICBitmapPaletteTypeCustom);
    if (FAILED(hr)) { return nullptr; }

    //2 计算CPU内存布局
    _tex->rowPitch = width * _tex->bytesPerPixel;
    _tex->size = _tex->rowPitch * height;
    _tex->data.resize(_tex->size);

    //3 解码,写入像素
    hr = converter->CopyPixels(nullptr, _tex->rowPitch, _tex->size,
                               reinterpret_cast<BYTE *>(_tex->data.data()));
    if (FAILED(hr))return nullptr;

    // 当前引擎需要 DirectX 风格的 -G，
    // 如果源法线贴图使用 OpenGL 风格的 +G，就反转绿色通道。
    if (type == Z::TextureType::NORMALS && revG) {
        auto *pixels = reinterpret_cast<std::uint8_t *>(_tex->data.data());
        for (std::size_t i = 0; i < _tex->data.size(); i += 4) {
            pixels[i + 1] = static_cast<std::uint8_t>(255u - pixels[i + 1]);
        }
    }


    Z::TextureData *result = _tex.release();
    LoadedResource::g_LoadedTexture.push_back(result);

    return result;
}

//endregion


//region Shader
namespace {
// callback wait
std::mutex mtx_cb{};
std::condition_variable cv_cb{};
Z::ResultMessage g_CallbackMessage{};

std::queue<SHADER::ShaderInfo> g_WriteBackShaderInfo;

SHADER::CbufferInfo BuildCbufferDesc(const Z::ShaderInfo::CbufferInfo &c, int index) {
    SHADER::CbufferInfo cbuf{index, c.name, {}};
    cbuf.props.reserve(c.c_prop);
    for (int i = 0; i < c.c_prop; ++i) {
        SHADER::PropertyInfo prop{};
        prop.index = c.ar_prop[i].index;
        prop.type = c.ar_prop[i].type;
        prop.name = c.ar_prop[i].name;
        std::memcpy(&prop.value, &c.ar_prop[i].value, sizeof(Z::ShaderInfo::Value));
        cbuf.props.push_back(std::move(prop));
    }
    return cbuf;
}
SHADER::TextureInfo BuildTexDesc(const Z::ShaderInfo::TexInfo &t, int index) {
    return SHADER::TextureInfo{
        t.index,
        std::string(t.name)
    };
}

void BuildShaderDesc() { // 在renderer线程被调用,在此构造APP端使用的shader描述
    Z::ShaderInfo::ShaderInfo *s;
    std::memcpy(&s, g_CallbackMessage.data, sizeof(g_CallbackMessage.data));
    if (s == nullptr) return;

    SHADER::ShaderInfo shader{};
    shader.name = s->name;



    shader.cbuffer.reserve(s->c_cbuffer);
    for (int i = 0; i < s->c_cbuffer; ++i) {
        shader.cbuffer.push_back(BuildCbufferDesc(s->ar_cbuffer[i], i));
    }

    shader.idx_cbPerMat = -1;
    for (auto &item: shader.cbuffer) {
        if (item.name == "CBPerMat") {
            shader.idx_cbPerMat = item.index;
            break;
        }
    }

    shader.textures.reserve(s->c_tex);
    for (int i = 0; i < s->c_tex; ++i) {
        shader.textures.push_back(BuildTexDesc(s->ar_tex[i], i));
    }

    g_WriteBackShaderInfo.push(std::move(shader));
}

void CallBack_CompileShaderWakeThreadUp(Z::ResultMessage msg) { // 被Renderer回调
    g_CallbackMessage = msg;

    BuildShaderDesc();

    cv_cb.notify_all();
}
void WaitForResult() {
    std::unique_lock lk(mtx_cb);
    cv_cb.wait(lk);
}
}

SHADER::ShaderInfo *ShaderImporter::ImportFromFile(const std::filesystem::path &path) {
    if (!std::filesystem::exists(path)) {
        MessageBoxW(nullptr, L"文件路径不存在", L"resource ShaderImporter",MB_OK);
        return nullptr;
    }

    //让renderer去编译,解释返回的DESC构成
    Z::Task::Command cmd{};
    std::u8string u8path = path.u8string();
    std::string pathstr = {reinterpret_cast<const char *>(u8path.c_str()), u8path.size()};
    cmd.compile_shader.path = pathstr.c_str();

    Z::Task task{};
    task.cmd = cmd;
    task.type = Z::CmdType::CompileShader;
    task.fpCallBack = CallBack_CompileShaderWakeThreadUp;

    Z::Task::AddCommand(task);
    WaitForResult();
    if (g_CallbackMessage.res == Z::ResultType::FAILED) {
        MessageBoxW(nullptr, L"编译shader返回错误", L"resource ShaderImporter",MB_OK);
        return nullptr;
    }

    // RENDER线程已返回,数据在g_WriteBackShaderInfo的第一个元素内
    auto *sdesc = new SHADER::ShaderInfo(std::move(g_WriteBackShaderInfo.front()));
    g_WriteBackShaderInfo.pop();

    sdesc->path = path;

    // std::cout << "APP线程::返回成功\n" <<
    //         "shader name = " << sdesc->name <<
    //         "\n c_cbuffer = " << sdesc->cbuffer.size() <<
    //         "\n cbuf[0].name = " << sdesc->cbuffer[0].name <<
    //         "\n c_tex = " << sdesc->textures.size() <<
    //         "\n tex[0].name = " << sdesc->textures[0].name << std::endl;

    LoadedResource::g_LoadedShaderDesc.push_back(sdesc);
    return sdesc;
}


//endregion


//region MatCreater
MAT::MatInstance *MatCreater::CreateMat(const std::string &name, const SHADER::ShaderInfo *shaderInfo) {
    MAT::MatInstance *m = new MAT::MatInstance(name, shaderInfo);
    LoadedResource::g_LoadedMatInstance.push_back(m);
    return m;
}
//endregion


















//
