#include "GLCanvasManager.h"

#include <cstdint>
#include <hilog/log.h>
#include <string>
#include <nlohmann/json.hpp> 
#include <vector>

#include "common/common.h"
#include "RNGLContext/RNGLContext.h"

using json = nlohmann::json;

std::unordered_map<int64_t, GLCanvas *> GLCanvasManager::glCanvasMap_;
std::unordered_map<int64_t, OHNativeWindow *> GLCanvasManager::windowMap_;
std::shared_ptr<RNGLContext> GLCanvasManager::rnglContext_ = std::make_shared<RNGLContext>();

GLCanvasManager::~GLCanvasManager() {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "GLCanvasManager", "~GLCanvasManager");
    for (auto iter = glCanvasMap_.begin(); iter != glCanvasMap_.end(); ++iter) {
        if (iter->second != nullptr) {
            delete iter->second;
            iter->second = nullptr;
        }
    }
    glCanvasMap_.clear();

    for (auto iter = windowMap_.begin(); iter != windowMap_.end(); ++iter) {
        if (iter->second != nullptr) {
            delete iter->second;
            iter->second = nullptr;
        }
    }
    windowMap_.clear();

    rnglContext_.reset();
}

// 解析从ArkTS侧传入的surfaceId，此处surfaceId是一个64位int值
int64_t GLCanvasManager::ParseId(napi_env env, napi_callback_info info) {
    if ((env == nullptr) || (info == nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ParseId", "env or info is null");
        return -1;
    }
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ParseId", "GetContext napi_get_cb_info failed");
        return -1;
    }
    int64_t value = 0;
    bool lossless = true;
    if (napi_ok != napi_get_value_bigint_int64(env, args[0], &value, &lossless)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ParseId", "Get value failed");
        return -1;
    }
    return value;
}

GLCanvas *GLCanvasManager::GetGLCanvas(int64_t &id) {
    if (glCanvasMap_.find(id) != glCanvasMap_.end()) {
        return glCanvasMap_[id];
    }
    return nullptr;
} 

// 设置SurfaceId，基于SurfaceId完成对NativeWindow的初始化
napi_value GLCanvasManager::SetSurfaceId(napi_env env, napi_callback_info info) {
    int64_t surfaceId = ParseId(env, info);
    OHNativeWindow *nativeWindow;
    GLCanvas *glCanvas;
    if (windowMap_.find(surfaceId) == windowMap_.end()) {
        OH_NativeWindow_CreateNativeWindowFromSurfaceId(surfaceId, &nativeWindow);
        windowMap_[surfaceId] = nativeWindow;
    }
    if (glCanvasMap_.find(surfaceId) == glCanvasMap_.end()) {
        std::string surfaceIdStr = std::to_string(surfaceId);
        glCanvas = new GLCanvas(rnglContext_);
        glCanvasMap_[surfaceId] = glCanvas;
    }
    bool ret = glCanvas->OnSurfaceCreated(nativeWindow);
    napi_value result;
    napi_create_int32(env, ret, &result);
    return result;
}

// 根据传入的surfaceId、width、height实现surface大小的变动
napi_value GLCanvasManager::ChangeSurface(napi_env env, napi_callback_info info) {
    if ((env == nullptr) || (info == nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "ChangeSurface: OnLoad env or info is null");
        return nullptr;
    }
    int64_t surfaceId = 0;
    size_t argc = 3;
    napi_value args[3] = {nullptr};

    if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "ChangeSurface: GetContext napi_get_cb_info failed");
        return nullptr;
    }
    bool lossless = true;
    int index = 0;
    if (napi_ok != napi_get_value_bigint_int64(env, args[index++], &surfaceId, &lossless)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "ChangeSurface: Get value failed");
        return nullptr;
    }
    double width;
    if (napi_ok != napi_get_value_double(env, args[index++], &width)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "ChangeSurface: Get width failed");
        return nullptr;
    }
    double height;
    if (napi_ok != napi_get_value_double(env, args[index++], &height)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "ChangeSurface: Get height failed");
        return nullptr;
    }
    auto glCanvas = GetGLCanvas(surfaceId);
    if (glCanvas == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "ChangeSurface: Get glCanvas failed");
        return nullptr;
    }
    glCanvas->OnSurfaceChanged(width, height);
    //glCanvas->SetRNGLContext(rnglContext_);
    return nullptr;
}

// 销毁surface
napi_value GLCanvasManager::DestroySurface(napi_env env, napi_callback_info info) {
    int64_t surfaceId = ParseId(env, info);
    auto glCanvasMapIter = glCanvasMap_.find(surfaceId);
    if (glCanvasMapIter != glCanvasMap_.end()) {
        delete glCanvasMapIter->second;
        glCanvasMap_.erase(glCanvasMapIter);
    }
    auto windowMapIter = windowMap_.find(surfaceId);
    if (windowMapIter != windowMap_.end()) {
        OH_NativeWindow_DestroyNativeWindow(windowMapIter->second);
        windowMap_.erase(windowMapIter);
    }
    rnglContext_ = nullptr;
    return nullptr;
}

napi_value GLCanvasManager::AddShader(napi_env env, napi_callback_info info) {
    if ((env == nullptr) || (info == nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: OnLoad env or info is null");
        return nullptr;
    }
    
    size_t argc = 2;
    napi_value args[2];
    if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: GetContext napi_get_cb_info failed");
        return nullptr;
    }
    
    int shaderId = 0;
    if (napi_ok != napi_get_value_int32(env, args[0], &shaderId)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: Get shaderId failed");
        return nullptr;
    }
    
    napi_value config = args[1];
    
    napi_value nameValue;
    if (napi_ok != napi_get_named_property(env, config, "name", &nameValue)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: Get nameValue failed");
        return nullptr;
    }
    napi_value fragValue;
    if (napi_ok != napi_get_named_property(env, config, "frag", &fragValue)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: Get fragValue failed");
        return nullptr;
    }
    
    size_t nameSize, fragSize;
    if (napi_ok != napi_get_value_string_utf8(env, nameValue, nullptr, 0, &nameSize)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: Get name failed");
        return nullptr;
    }
    if (napi_ok != napi_get_value_string_utf8(env, fragValue, nullptr, 0, &fragSize)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: Get frag failed");
        return nullptr;
    }
    
    std::string name, frag;
    name.resize(nameSize + 1);
    if (napi_ok != napi_get_value_string_utf8(env, nameValue, &name[0], nameSize + 1, &nameSize)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: Get name failed");
        return nullptr;
    }
    frag.resize(fragSize + 1);
    if (napi_ok != napi_get_value_string_utf8(env, fragValue, &frag[0], fragSize + 1, &fragSize)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: Get frag failed");
        return nullptr;
    }
    name.resize(nameSize);
    frag.resize(fragSize);
    
    if (rnglContext_) {
        rnglContext_->addShader(shaderId, name, frag);
    }

    return nullptr;
}

napi_value GLCanvasManager::RemoveShader(napi_env env, napi_callback_info info) {
    if ((env == nullptr) || (info == nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ParseId", "env or info is null");
        return nullptr;
    }
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ParseId", "GetContext napi_get_cb_info failed");
        return nullptr;
    }
    int shaderId = 0;
    if (napi_ok != napi_get_value_int32(env, args[0], &shaderId)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ParseId", "Get value failed");
        return nullptr;
    }
    
    if (rnglContext_) {
        rnglContext_->removeShader(shaderId);
    }
    
    return nullptr;
}

napi_value GLCanvasManager::SetData(napi_env env, napi_callback_info info) {
    if ((env == nullptr) || (info == nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "SetUniform: OnLoad env or info is null");
        return nullptr;
    }
    int64_t surfaceId = 0;
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "SetUniform: GetContext napi_get_cb_info failed");
        return nullptr;
    }
    
    bool lossless = true;
    if (napi_ok != napi_get_value_bigint_int64(env, args[0], &surfaceId, &lossless)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "SetUniform: Get surfaceId failed");
        return nullptr;
    }
    size_t dataSize;
    if (napi_ok != napi_get_value_string_utf8(env, args[1], nullptr, 0, &dataSize)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: Get name failed");
        return nullptr;
    }
    
    std::string data;
    data.resize(dataSize + 1);
    if (napi_ok != napi_get_value_string_utf8(env, args[1], &data[0], dataSize + 1, &dataSize)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "AddShader: Get name failed");
        return nullptr;
    }
    data.resize(dataSize);
    
    auto glCanvas = GetGLCanvas(surfaceId);
    if (glCanvas == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "SetUniform: Get glRender failed");
        return nullptr;
    }
    glCanvas->SetRNGLContext(rnglContext_);
    glCanvas->SetData(data);
    
    return nullptr;
}

napi_value GLCanvasManager::DrawFrame(napi_env env, napi_callback_info info) {
     if ((env == nullptr) || (info == nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "DrawFrame: OnLoad env or info is null");
        return nullptr;
    }
    
    int64_t surfaceId = 0;
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "DrawFrame: GetContext napi_get_cb_info failed");
        return nullptr;
    }
    bool lossless = true;
    if (napi_ok != napi_get_value_bigint_int64(env, args[0], &surfaceId, &lossless)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "DrawFrame: Get surfaceId failed");
        return nullptr;
    }
    
    auto glCanvas = GetGLCanvas(surfaceId);
    if (glCanvas == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "DrawFrame: Get glCanvas failed");
        return nullptr;
    }
    glCanvas->SetRNGLContext(rnglContext_);
    glCanvas->DrawFrame();
    
    return nullptr;
}

napi_value GLCanvasManager::LoadImage(napi_env env, napi_callback_info info) {
    size_t argc = 3;
    napi_value args[3];
    napi_get_cb_info(env, info, & argc, args, nullptr, nullptr);

    int64_t surfaceId = 0;
    bool lossless = true;
    if (napi_ok != napi_get_value_bigint_int64(env, args[0], &surfaceId, &lossless)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get ParseId failed");
        return nullptr;
    }
    
    size_t srcSize;
    if (napi_ok != napi_get_value_string_utf8(env, args[1], nullptr, 0, &srcSize)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get name size failed");
        return nullptr;
    }
    std::string src;
    src.resize(srcSize + 1);
    if (napi_ok != napi_get_value_string_utf8(env, args[1], &src[0], srcSize + 1, &srcSize)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get name failed");
        return nullptr;
    }
    src.resize(srcSize);
    
    napi_value config = args[2];
    
    napi_value widthValue;
    if (napi_ok != napi_get_named_property(env, config, "width", &widthValue)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get widthValue failed");
        return nullptr;
    }
    napi_value heightValue;
    if (napi_ok != napi_get_named_property(env, config, "height", &heightValue)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get heightValue failed");
        return nullptr;
    }
    napi_value pathValue;
    if (napi_ok != napi_get_named_property(env, config, "path", &pathValue)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get pathValue failed");
        return nullptr;
    }
    napi_value dataValue;
    if (napi_ok != napi_get_named_property(env, config, "data", &dataValue)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get dataValue failed");
        return nullptr;
    }
    
    int width;
    if (napi_ok != napi_get_value_int32(env, widthValue, &width)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get width failed");
        return nullptr;
    }
    int height;
    if (napi_ok != napi_get_value_int32(env, heightValue, &height)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get height failed");
        return nullptr;
    }
    size_t pathSize;
    if (napi_ok != napi_get_value_string_utf8(env, pathValue, nullptr, 0, &pathSize)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get pathSize failed");
        return nullptr;
    }
    std::string path;
    path.resize(pathSize + 1);
    if (napi_ok != napi_get_value_string_utf8(env, pathValue, &path[0], pathSize + 1, &pathSize)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get path failed");
        return nullptr;
    }
    path.resize(pathSize);
    
    void* data;
    size_t byteLength;
    if (napi_ok != napi_get_buffer_info(env, dataValue, &data, &byteLength)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get buffer failed");
        return nullptr;
    }
    std::vector<unsigned char> dataArray = {};
    unsigned char *dataBytes = (unsigned char *)(data);
    int num = byteLength / sizeof(unsigned char);
    for (int i = 0; i < num; i++) {
        dataArray.push_back(*((unsigned char *)(dataBytes) + i));
    }
    
    auto glCanvas = GetGLCanvas(surfaceId);
    if (glCanvas == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "LoadImage: Get glCanvas failed");
        return nullptr;
    }
    auto imageData = std::make_shared<ImageData>(width, height, path, dataArray);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0xFF00, "GLCanvasManager", "LoadImage: src = %{public}s, dataSize = %{public}lu", src.c_str(), dataArray.size());
    OH_LOG_Print(LOG_APP, LOG_INFO, 0xFF00, "GLCanvasManager", "LoadImage: path = %{public}s", path.c_str());
    glCanvas->AddImageData(src, imageData);

    return nullptr;
}

napi_value GLCanvasManager::OnProgress(napi_env env, napi_callback_info info) {
    int64_t surfaceId = ParseId(env, info);
    auto glCanvas = GetGLCanvas(surfaceId);
    if (glCanvas == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "OnProgress: Get glCanvas failed");
        return nullptr;
    }
    
    auto onProgressData = glCanvas->GetOnProgressData();
    napi_value count;
    napi_value total;
    napi_value progress;
    napi_status ret = napi_create_int32(env, onProgressData->count, &(count));
    if (ret != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "OnProgress: napi_create_int32 count error");
        return nullptr;
    }
    ret = napi_create_int32(env, onProgressData->total, &(total));
    if (ret != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "OnProgress: napi_create_int32 total error");
        return nullptr;
    }
    ret = napi_create_double(env, onProgressData->progress, &(progress));
    if (ret != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "OnProgress: napi_create_double progress error");
        return nullptr;
    }
    napi_value obj;
    ret = napi_create_object(env, &obj);
    if (ret != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "OnProgress: napi_create_object error");
        return nullptr;
    }
    ret = napi_set_named_property(env, obj, "count", count);
    if (ret != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "OnProgress: napi_set_named_property count error");
        return nullptr;
    }
    ret = napi_set_named_property(env, obj, "total", total);
    if (ret != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "OnProgress: napi_set_named_property total error");
        return nullptr;
    }
    ret = napi_set_named_property(env, obj, "progress", progress);
    if (ret != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "OnProgress: napi_set_named_property total error");
        return nullptr;
    }
    return obj;
}

napi_value GLCanvasManager::GetContext(napi_env env, napi_callback_info info) {
    int64_t surfaceId = ParseId(env, info);
    auto glCanvas = GetGLCanvas(surfaceId);
    if (glCanvas == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "GLCanvasManager", "ChangeSurface: Get glCanvas failed");
        return nullptr;
    }
    napi_value result = (napi_value)glCanvas->GetEGLContext();
    if (result) {
        napi_create_object(env, &result);
        return result;
    }
    return nullptr;
}
