
#include <atomic>
#include <arkui/native_node_napi.h>
#include "common.h"
#include "Utils.h"
#include "include/oh/OHRenderNode.h"

typedef void (*RenderNodeDrawCallback)(KInt, KNativePointer);

static RenderNodeDrawCallback drawCallback = nullptr;

static std::atomic<int32_t> renderNodeID(0);

class IRenderNode {
public:
    virtual void notifyRedraw() = 0;
    virtual int32_t getID() = 0;
};

class JsRenderNode : IRenderNode {
private:
    const napi_env env;
    const int32_t id;
    napi_ref nodeRef = nullptr;

public:
    JsRenderNode(napi_env env, int32_t id) : env(env), id(id) {};

    ~JsRenderNode() {
        if (nodeRef != nullptr) {
            napi_delete_reference(env, nodeRef);
            nodeRef = nullptr;
        }
    }

    static void freeJsRenderNode(JsRenderNode* node) {
        delete node;
    }

    static void jsNodeDrawCallback(void *context, void *canvas) {
        if (drawCallback == nullptr) {
            Error("jsNodeDrawCallback: empty drawCallback");
            return;
        }
        JsRenderNode* renderNode = reinterpret_cast<JsRenderNode*>(context);
        drawCallback(renderNode->getID(), canvas);
    }

    void createJsNode() {
        if (nodeRef == nullptr) {
            napi_value jsNode = OHRenderNode::CreateRootRenderNode(this, &jsNodeDrawCallback);
            napi_status status = napi_create_reference(env, jsNode, 1, &nodeRef);
            if (status != napi_ok) {
                Error("JsRenderNode createJsNode failed: createReference failed");
                nodeRef = nullptr;
            }
        }
    }

    napi_value getJsNode() {
        if (nodeRef == nullptr) {
            Error("JsRenderNode getJsNode failed: empty nodeRef");
            return nullptr;
        }
        napi_value jsNode;
        napi_status status = napi_get_reference_value(env, nodeRef, &jsNode);
        if (status != napi_ok) {
            Error("JsRenderNode getJsNode failed: getReference failed");
            return nullptr;
        }
        return jsNode;
    }

    void notifyRedraw() override {
        napi_handle_scope scope = nullptr;
        napi_open_handle_scope(env, &scope);
        const napi_value jsNode = getJsNode();
        if (jsNode == nullptr) {
            Error("JsRenderNode notifyRedraw failed: empty jsNode");
            return;
        }
        OHRenderNode::RenderNodeNotifyRedraw(env, jsNode);
        napi_close_handle_scope(env, scope);
    }

    int32_t getID() override {
        return id;
    }

    void draw(napi_value canvas) {
        napi_handle_scope scope = nullptr;
        napi_open_handle_scope(env, &scope);
        const napi_value jsNode = getJsNode();
        if (jsNode == nullptr) {
            Error("JsRenderNode draw failed: empty jsNode");
            return;
        }
        OHRenderNode::RenderNodeDraw(env, canvas, jsNode);
        napi_close_handle_scope(env, scope);
    }
};

class CRenderNode : IRenderNode {
private:
    const int32_t id;
    const ArkUI_NodeHandle node;

public:
    static void cRenderNodeDraw(int32_t id, void* canvas) {
        if (drawCallback == nullptr) {
            Error("cNodeDrawCallback: empty drawCallback");
            return;
        }
        drawCallback(id, canvas);
    }

    CRenderNode(int32_t id): id(id), node(OHRenderNode::CreateRootRenderNodeWithoutEvent(id, &cRenderNodeDraw)) {};

    ~CRenderNode() {
        RootRenderNodeManager::Instance().UnregisterNode(id);
    }

    void notifyRedraw() override {
        RootRenderNodeManager::Instance().NotifyRedraw(id);
    }

    int32_t getID() override {
        return id;
    }

    int32_t attach(ArkUI_NodeContentHandle parent) {
        return OH_ArkUI_NodeContent_AddNode(parent, node);
    }

    ArkUI_NodeHandle getNodeHandle() {
        return node;
    }

    void reSize(int32_t width, int32_t height) {
        RootRenderNodeManager::Instance().Resize(id, width, height);
    }

    static void freeCRenderNode(CRenderNode* node) {
        delete node;
    }
};

SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nSetDrawCallback
    (void* callback) {
    drawCallback = reinterpret_cast<RenderNodeDrawCallback>(callback);
}

SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nSetNodeConstructor
    (napi_env env, napi_value function) {
    OHRenderNode::SetNodeConstructor(env, function);
}

SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nSetPixelRatio
    (double ratio) {
    OHRenderNode::SetPixelRatio(ratio);
}

SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nNodeStatusModifyBuildInstance
    (napi_env env, napi_value function) {
    NodeStatusModify::BuildInstance(env, function);
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_RenderNode__1nCreateJsRenderNode
    (napi_env env) {
    JsRenderNode* node = new JsRenderNode(env, ++renderNodeID);
    node->createJsNode();
    return reinterpret_cast<KNativePointer>(node);
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_RenderNode__1nGetJsNodeFinalizer() {
    return reinterpret_cast<KNativePointer>((&JsRenderNode::freeJsRenderNode));
}

SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nRenderNodeNotifyRedraw
    (void* ptr) {
    IRenderNode* node = reinterpret_cast<IRenderNode*>(ptr);
    node->notifyRedraw();
}

SKIKO_EXPORT KInt org_jetbrains_skia_RenderNode__1nRenderNodeGetNodeId
    (void* ptr) {
    IRenderNode* node = reinterpret_cast<IRenderNode*>(ptr);
    return node->getID();
}

SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nRenderNodeDraw
    (void* ptr, napi_value canvas) {
    JsRenderNode* node = reinterpret_cast<JsRenderNode*>(ptr);
    node->draw(canvas);
}

SKIKO_EXPORT napi_value org_jetbrains_skia_RenderNode__1nRenderNodeGetJsNode
    (void* ptr) {
    JsRenderNode* node = reinterpret_cast<JsRenderNode*>(ptr);
    return node->getJsNode();
}

SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nRenderNodeJsNodeDraw
    (napi_env env, napi_value node, napi_value canvas) {
    OHRenderNode::RenderNodeDraw(env, canvas, node);
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_RenderNode__1nCreateCRenderNode
    (napi_env env, napi_value content) {
    ArkUI_NodeContentHandle handle{};
    auto ret = OH_ArkUI_GetNodeContentFromNapiValue(env, content, &handle);
    if (ret != ARKUI_ERROR_CODE_NO_ERROR) {
        Error("CRenderNode create GetNodeContentFromNapiValue failed: %{public}d", ret);
        return nullptr;
    }
    CRenderNode* node = new CRenderNode(++renderNodeID);
    ret = node->attach(handle);
    if (ret != ARKUI_ERROR_CODE_NO_ERROR) {
        Error("CRenderNode create attach failed: %{public}d", ret);
        delete node;
        return nullptr;
    }
    return reinterpret_cast<KNativePointer>(node);
}

SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nRenderNodeReSize
    (void* ptr, int width, int height) {
    CRenderNode* node = reinterpret_cast<CRenderNode*>(ptr);
    node->reSize(width, height);
}

SKIKO_EXPORT ArkUI_NodeHandle org_jetbrains_skia_RenderNode__1nRenderNodeGetNodeHandle
    (void* ptr) {
    CRenderNode* node = reinterpret_cast<CRenderNode*>(ptr);
    return node->getNodeHandle();
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_RenderNode__1nGetCNodeFinalizer() {
    return reinterpret_cast<KNativePointer>((&CRenderNode::freeCRenderNode));
}

SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nSetCAPIFixed
    (bool fixed) {
    OHRenderNode::SetCAPIFixed(fixed);
}
