#include <unordered_map>
#include <chrono>
#include <string>
#include <array>
#include "commoninclude.h"
#include "napi-thread-safe-callback.hpp"
#include "help.h"
#include "logwrapper.h"
#include "asynchelper.h"
#include "bt.h"
//#include <D:/Users/yl0903.YEALINK/AppData/Local/node-gyp/Cache/14.16.1/include/node/node_api.h>

#define FUNC_ENV_SIGN                           const char * pFuncName = __func__;\
                                                Napi::Env objEnv = objInfo.Env();

#define TRY_SECTION_BEGIN   try {
#define TRY_SECTION_END     }\
                            catch (const std::exception &e)\
                            {\
                                INFO_TRACE << "catch a exception:" << std::string(e.what());\
                            }\
                            catch (...)\
                            {\
                                INFO_TRACE << "catch a exception, unknown exception";\
                            }


/**
 * Memory managed version of PairingList that can be safely moved around and inspected (no C-pointers, no nulls).
 */
struct PairedDevice
{
    std::string strName = "";
    std::array<uint8_t, 6> arrBtAddr;
    bool bIsConnected = false;

    explicit PairedDevice() {}
    explicit PairedDevice(const Yealink_PairedDevice& stPairingList)
        : strName(stPairingList.deviceName), arrBtAddr(), bIsConnected(stPairingList.isConnected)
    {
        std::copy(stPairingList.deviceBTAddr, stPairingList.deviceBTAddr + 6, arrBtAddr.begin() );
    }
};

struct PairingDeviceList
{
    Yealink_DeviceListType eListType;
    std::vector<PairedDevice> vecDevice;

    explicit PairingDeviceList() {}
    explicit PairingDeviceList(const Yealink_PairingList& stPairingList)
        : eListType(stPairingList.listType), vecDevice(InitVector(stPairingList.count, stPairingList.pairedDevice))
    {}

private:
    static std::vector<PairedDevice> InitVector(unsigned short count, Yealink_PairedDevice* pDeviceList)
    {
        auto vec = std::vector<PairedDevice>();
        if (pDeviceList != nullptr)
        {
            for (int i = 0; i<count; ++i)
            {
                PairedDevice stDev(pDeviceList[i]);
                vec.push_back(stDev);
            }
        }

        return vec;
    }
};



class InitState
{
public:

    void Clear()
    {
        m_pCallbackInitialized = nullptr;
        m_pCallbackFirstScanDone = nullptr;
        m_pCallbackDevAttached = nullptr;
        m_pCallbackDevDetached = nullptr;
        m_pCallbackButtonInDataTranslated = nullptr;
        m_pCallbackDevLog = nullptr;
        m_pCallbackBatteryStatus = nullptr;
        m_pCallbackRemoteMmi = nullptr;
        m_pCallbackDownloadFwProgress = nullptr;
        m_pCallbackUploadProgress = nullptr;
        m_pCallbackRegisterPairingList = nullptr;
        m_pCallbackgHidBtnEvent = nullptr;
        m_pCallbackgButtonInDataTranslatedWithCid = nullptr;
        m_pCallbackgCallAction = nullptr;
        m_pCallbackgDialNumChanged = nullptr;

        m_bInited = false;
    }

public:
    std::string             m_strAppId = "";
    bool                    m_bInited = false;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackInitialized = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackFirstScanDone = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackDevAttached = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackDevDetached = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackButtonInDataTranslated = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackDevLog = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackBatteryStatus = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackRemoteMmi = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackDownloadFwProgress = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackUploadProgress = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackRegisterPairingList = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackgHidBtnEvent = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackgButtonInDataTranslatedWithCid = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackgCallAction = nullptr;
    std::shared_ptr<ThreadSafeCallback>     m_pCallbackgDialNumChanged = nullptr;
};

/**
 * Get ms since epoc.
 */
static std::uint64_t GetTimeSinceEpoc()
{
    return std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
}

static InitState g_objInitState;

/**
 * Implements a combination of Yealink_Initialize, Yealink_SetAppID and all Yealink_RegisterXXX
 * event handler setup functions. The implementation creates it's own thread to call
 * all Yealink SDK functions.
 */

Napi::Value napi_Initialize(const Napi::CallbackInfo& objInfo) {
    const char * const pFuncName = __func__;
    const Napi::Env env = objInfo.Env();

    if (g_objInitState.m_bInited) {
        LOG_ERROR_(PLOG_DEFAULT_INSTANCE) << "yl sdk already inited";
        return env.Null();
    }

    if (!NS_HELP::VerifyArgs(__func__, objInfo, {NS_HELP::STRING,
                             NS_HELP::OBJECT, NS_HELP::OBJECT, NS_HELP::FUNCTION, NS_HELP::FUNCTION,
                             NS_HELP::FUNCTION, NS_HELP::FUNCTION, NS_HELP::FUNCTION, NS_HELP::FUNCTION,
                             NS_HELP::FUNCTION, NS_HELP::FUNCTION, NS_HELP::FUNCTION, NS_HELP::FUNCTION,
                             NS_HELP::FUNCTION, NS_HELP::FUNCTION, NS_HELP::FUNCTION, NS_HELP::OBJECT}))
    {
        LOG_ERROR_(PLOG_DEFAULT_INSTANCE) << "verify init args failed";
        return env.Null();
    }

    // for (int i = 0; i < objInfo.Length(); ++ i)
    // {
    //     Napi::Value val = objInfo[i];
    //     printf("is string:%d, is object:%d, is function:%d\n", val.IsString(), val.IsObject(), val.IsFunction());
    // }

    int nArg = 0;
    g_objInitState.m_strAppId = objInfo[nArg++].As<Napi::String>();
    g_objInitState.m_pCallbackInitialized =  std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackFirstScanDone =  std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackDevAttached = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackDevDetached = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackButtonInDataTranslated = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackDevLog = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackBatteryStatus = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackRemoteMmi = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackDownloadFwProgress = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackUploadProgress = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackRegisterPairingList = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackgHidBtnEvent = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackgButtonInDataTranslatedWithCid = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackgCallAction = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    g_objInitState.m_pCallbackgDialNumChanged = std::shared_ptr<ThreadSafeCallback>(new ThreadSafeCallback(objInfo[nArg++].As<Napi::Function>()));
    
    
    Napi::Object configParams = objInfo[nArg++].As<Napi::Object>();

    INFO_TRACE << "start init yl sdk";
    std::thread objInitThread([]() {
        try {
            Yealink_SetAppID(g_objInitState.m_strAppId.c_str());

            if (Yealink_Initialize([]() {  // first scan call back
            TRY_SECTION_BEGIN
            INFO_TRACE << "first scan call back";
            auto ullEventTime = GetTimeSinceEpoc();
                if (g_objInitState.m_pCallbackFirstScanDone)
                {
                    g_objInitState.m_pCallbackFirstScanDone->call([ullEventTime](Napi::Env objEnv, std::vector<napi_value>& vecArgs) {
                        vecArgs = { Napi::Number::New(objEnv, ullEventTime) };
                        //const napi_extended_error_objInfo ** pInfo = NULL;
                        //napi_get_last_error_objInfo(g_objInitState.m_objEnv, pInfo);
                        //printf("cc first scan call back:%s\n", (*pInfo)->error_message);
                    });
                }
                TRY_SECTION_END
            }, [](Yealink_DeviceInfo stDevInfo) { // device attached
                TRY_SECTION_BEGIN
                INFO_TRACE << "device attached" << " dev id:" << stDevInfo.deviceID << " name:" << stDevInfo.deviceName << " sn:" << stDevInfo.serialNumber;
                auto eventTime = GetTimeSinceEpoc();

                VERBOSE_TRACE << "attached callback: " << g_objInitState.m_pCallbackDevAttached;
                if (g_objInitState.m_pCallbackDevAttached)
                {
                    g_objInitState.m_pCallbackDevAttached->call([stDevInfo, eventTime](Napi::Env env, std::vector<napi_value>& vecArgs) {
                        Napi::Object objResult = Napi::Object::New(env);

//#define NAPI_NEW_STRING(env, ptr)    (ptr ? Napi::String::New(env, ptr) : Napi::String::New(env, ""))

                        objResult.Set(Napi::String::New(env, "deviceId"), (Napi::Number::New(env, stDevInfo.deviceID)));
                        objResult.Set(Napi::String::New(env, "productId"), (Napi::Number::New(env, stDevInfo.productID)));
                        objResult.Set(Napi::String::New(env, "vendorId"), (Napi::Number::New(env, stDevInfo.vendorID)));
                        objResult.Set(Napi::String::New(env, "deviceName"), NAPI_NEW_STRING(env, stDevInfo.deviceName));
                        objResult.Set(Napi::String::New(env, "devicePath"), NAPI_NEW_STRING(env, stDevInfo.usbDevicePath));
                        objResult.Set(Napi::String::New(env, "parentInstanceId"), NAPI_NEW_STRING(env, stDevInfo.parentInstanceId));

                        objResult.Set(Napi::String::New(env, "errStatus"), (Napi::Number::New(env, stDevInfo.errStatus)));
                        objResult.Set(Napi::String::New(env, "isDongle"), (Napi::Boolean::New(env, stDevInfo.isDongle)));
                        objResult.Set(Napi::String::New(env, "dongleName"), NAPI_NEW_STRING(env, stDevInfo.dongleName));
                        objResult.Set(Napi::String::New(env, "variant"), NAPI_NEW_STRING(env, stDevInfo.variant));
                        objResult.Set(Napi::String::New(env, "serialNumber"), NAPI_NEW_STRING(env, stDevInfo.serialNumber));

                        objResult.Set(Napi::String::New(env, "isInFirmwareUpdateMode"), (Napi::Boolean::New(env, stDevInfo.isInFirmwareUpdateMode)));
                        objResult.Set(Napi::String::New(env, "deviceconnection"), (Napi::Number::New(env, stDevInfo.deviceconnection)));
                        objResult.Set(Napi::String::New(env, "connectionId"), (Napi::Number::New(env, stDevInfo.connectionId)));
                        objResult.Set(Napi::String::New(env, "parentDeviceId"), (Napi::Number::New(env, stDevInfo.parentDeviceId)));

                        vecArgs = { objResult, Napi::Number::New(env, eventTime) };
                    });
                }
                TRY_SECTION_END
            }, [](unsigned short uDevId) { // device detached
                TRY_SECTION_BEGIN
                INFO_TRACE << "device detached" << " dev #:" << uDevId;
                auto eventTime = GetTimeSinceEpoc();
                if (g_objInitState.m_pCallbackDevDetached) {
                    g_objInitState.m_pCallbackDevDetached->call([uDevId, eventTime](Napi::Env env, std::vector<napi_value>& args) {
                        args = { Napi::Number::New(env, uDevId),
                                 Napi::Number::New(env, eventTime)
                               };
                    });
                }
                TRY_SECTION_END
            }, [](unsigned short uDevId, unsigned short uUsagePage, unsigned short uUsage, bool bButtonInData) {  // button raw
                TRY_SECTION_BEGIN
                TRY_SECTION_END
            }, [](unsigned short uDevId, Yealink_HidInput eBtnInput, bool bButtonInData) { // button translated
                TRY_SECTION_BEGIN
                INFO_TRACE<< "device #" << uDevId << " button press " << eBtnInput << "[" << bButtonInData << "]";
                if (g_objInitState.m_pCallbackButtonInDataTranslated)
                {
                    g_objInitState.m_pCallbackButtonInDataTranslated->call([uDevId, eBtnInput, bButtonInData](Napi::Env env, std::vector<napi_value>& vecArgs) {
                        vecArgs = { Napi::Number::New(env, uDevId),
                                    Napi::Number::New(env, (int)eBtnInput),
                                    Napi::Boolean::New(env, bButtonInData)
                                  };
                    });
                }
                TRY_SECTION_END
            }, nullptr)) {
                // yealink sdk initialized success
                // register callback for device battery status changed
                Yealink_RegisterBatteryStatusUpdateCallback([](unsigned short uDevId, int iLevelInPercent, bool bCharging, bool bBatteryLow) {
                    TRY_SECTION_BEGIN
                    INFO_TRACE << "Yealink_RegisterBatteryStatusUpdateCallback callback called with " "uDevId#" << uDevId
                               << ", percent:"<< iLevelInPercent << ", bCharging" << bCharging << ", battery low" << bBatteryLow;
                    if (g_objInitState.m_pCallbackBatteryStatus)
                    {
                        g_objInitState.m_pCallbackBatteryStatus->call([uDevId, iLevelInPercent, bCharging, bBatteryLow](Napi::Env env, std::vector<napi_value>& args) {
                            args = { Napi::Number::New(env, uDevId),
                                     Napi::Number::New(env, iLevelInPercent),
                                     Napi::Boolean::New(env, bCharging),
                                     Napi::Boolean::New(env, bBatteryLow)
                                   };
                        });
                    }
                    TRY_SECTION_END
                });

                Yealink_RegisterRemoteMmiCallback([](unsigned short uDevId, RemoteMmiType eType, RemoteMmiInput eAction) {
                    TRY_SECTION_BEGIN
                    INFO_TRACE << "Yealink_RegisterRemoteMmiCallback callback called with " "uDevId#" << uDevId << ", event type:" << eType << ", input:" << eAction;
                    if (g_objInitState.m_pCallbackRemoteMmi)
                    {
                        g_objInitState.m_pCallbackRemoteMmi->call([uDevId, eType, eAction](Napi::Env env, std::vector<napi_value>& vecArgs) {
                            vecArgs = { Napi::Number::New(env, uDevId),
                                        Napi::Number::New(env, eType),
                                        Napi::Number::New(env, eAction)
                                      };
                        });
                    }
                    TRY_SECTION_END
                });

                Yealink_RegisterLogProgressCallBack([](unsigned short uDevId, Yealink_LogEventType eType, bool bResult) {
                    TRY_SECTION_BEGIN
                    INFO_TRACE << "Yealink_RegisterLogProgressCallBack callback called with " "uDevId#" << uDevId << ", event type:" << eType << ", ret:" << bResult;
                    if (g_objInitState.m_pCallbackDevLog)
                    {
                        g_objInitState.m_pCallbackDevLog->call([uDevId, eType, bResult](Napi::Env env, std::vector<napi_value>& vecArgs) {
                            vecArgs = {Napi::Number::New(env, uDevId),
                                       Napi::Number::New(env, (int)eType),
                                       Napi::Boolean::New(env, bResult),
                                      };
                        });
                    }
                    TRY_SECTION_END
                });

                // register callback for fw download progress
                Yealink_RegisterFirmwareProgressCallBack([](unsigned short uDevId, Yealink_FirmwareEventType eFwEventType, Yealink_FirmwareEventStatus eFwEventStatus, unsigned short uPercent) {
                    TRY_SECTION_BEGIN
                    INFO_TRACE << "Yealink_RegisterFirmwareProgressCallBack callback called with " << eFwEventType << " " << eFwEventStatus << " " << uPercent;

                    if (g_objInitState.m_pCallbackDownloadFwProgress)
                    {
                        g_objInitState.m_pCallbackDownloadFwProgress->call([uDevId, eFwEventType, eFwEventStatus, uPercent](Napi::Env env, std::vector<napi_value>& vecArgs) {
                            vecArgs = {Napi::Number::New(env, uDevId),
                                       Napi::Number::New(env, (int)eFwEventType),
                                       Napi::Number::New(env, (int)eFwEventStatus),
                                       Napi::Number::New(env, uPercent)
                                      };
                        });
                    }
                    TRY_SECTION_END
                });

                Yealink_RegisterPairingListCallback([](unsigned short uDevId, Yealink_PairingList *pDeviceList) {
                    TRY_SECTION_BEGIN
                    INFO_TRACE << "Yealink_RegisterPairingListCallback callback called with " << (pDeviceList!=nullptr ? std::to_string(pDeviceList->count) : "null") << " pairings";
                    if (pDeviceList == nullptr)
                    {
                        return;
                    }

                    PairingDeviceList stListDevice(*pDeviceList);

                    // call async
                    if (g_objInitState.m_pCallbackRegisterPairingList)
                    {
                        g_objInitState.m_pCallbackRegisterPairingList->call([uDevId, stListDevice](Napi::Env env, std::vector<napi_value>& vecArgs) {
                            Napi::Object objList = Napi::Object::New(env);

                            objList.Set(Napi::String::New(env, "listType"), Napi::Number::New(env, stListDevice.eListType));
                            Napi::Array objPairedDevice = Napi::Array::New(env);

                            int i = 0;
                            for (auto it = stListDevice.vecDevice.begin(); it != stListDevice.vecDevice.end(); it ++)
                            {
                                const PairedDevice& stDevice =  *it;

                                Napi::Object objDev = Napi::Object::New(env);

                                objDev.Set(Napi::String::New(env, "deviceName"), Napi::String::New(env, stDevice.strName));
                                std::string strAddr = toBTAddrString(stDevice.arrBtAddr.data(), stDevice.arrBtAddr.size());
                                objDev.Set(Napi::String::New(env, "deviceBTAddr"), Napi::String::New(env, strAddr));
                                objDev.Set(Napi::String::New(env, "isConnected"), Napi::Boolean::New(env, stDevice.bIsConnected));

                                objPairedDevice.Set(i++, objDev);
                            }

                            objList.Set(Napi::String::New(env, "deviceList"), objPairedDevice);
                            vecArgs = { Napi::Number::New(env, uDevId), objList };
                        });
                    }
                    Yealink_FreePairingList(pDeviceList);

                    TRY_SECTION_END
                });

                Yealink_RegButtonInDataTranslatedWithCid([](unsigned short uDevId, int iCid, Yealink_HidInput eBtnInput, bool bButtonInData) { // button translated
                    TRY_SECTION_BEGIN
                    INFO_TRACE<< "device #" << uDevId << "cid" << iCid << " button press " << eBtnInput << "[" << bButtonInData << "]";
                    if (g_objInitState.m_pCallbackgButtonInDataTranslatedWithCid)
                    {
                        g_objInitState.m_pCallbackgButtonInDataTranslatedWithCid->call([uDevId, iCid, eBtnInput, bButtonInData](Napi::Env env, std::vector<napi_value>& vecArgs) {
                            vecArgs = { Napi::Number::New(env, uDevId),
                                        Napi::Number::New(env, iCid),
                                        Napi::Number::New(env, (int)eBtnInput),
                                        Napi::Boolean::New(env, bButtonInData)
                                    };
                        });
                    }
                    TRY_SECTION_END
                });

                Yealink_RegisterCallAction([](unsigned short uDevId, int iCid, Yealink_CallAction eCallAction){
                    TRY_SECTION_BEGIN
                    INFO_TRACE << "Yealink_RegisterCallAction callback called with " "uDevId#" << uDevId << ", cid:" << iCid << ", call action:" << eCallAction;
                    if (g_objInitState.m_pCallbackgCallAction)
                    {
                        g_objInitState.m_pCallbackgCallAction->call([uDevId, iCid, eCallAction](Napi::Env env, std::vector<napi_value>& vecArgs) {
                            vecArgs = { Napi::Number::New(env, uDevId),
                                        Napi::Number::New(env, iCid),
                                        Napi::Number::New(env, eCallAction)
                                      };
                        });
                    }
                    TRY_SECTION_END
                });

                Yealink_RegisterDialNumChanged([](unsigned short uDevId, int iCid, char* pDialNum){
                    TRY_SECTION_BEGIN
                    INFO_TRACE << "Yealink_RegisterDialNumChanged callback called with " "uDevId#" << uDevId << ", cid:" << iCid << ", dial number:" << pDialNum;
                    if (g_objInitState.m_pCallbackgDialNumChanged)
                    {
                        std::string strDialNum = pDialNum;
                        g_objInitState.m_pCallbackgDialNumChanged->call([uDevId, iCid, strDialNum](Napi::Env env, std::vector<napi_value>& vecArgs) {
                            vecArgs = { Napi::Number::New(env, uDevId),
                                        Napi::Number::New(env, iCid),
                                        Napi::String::New(env, strDialNum.c_str())
                                      };
                        });
                    }
                    TRY_SECTION_END
                });

                if (g_objInitState.m_pCallbackInitialized)
                {
                    g_objInitState.m_pCallbackInitialized->call([](Napi::Env env, std::vector<napi_value>& args) {
                        args = { };
                    });
                }
            }
            else
            {
                ERROR_TRACE << "cc init failed";
                if (g_objInitState.m_pCallbackInitialized)
                {
                    g_objInitState.m_pCallbackInitialized->error("init yealink sdk failed");
                }
            }
        }
        catch (const std::exception &e)
        {
            ERROR_TRACE << "worker thread failed:" << std::string(e.what());
        }
        catch (...)
        {
            ERROR_TRACE << "worker thread failed with unknown exception";
        }
    });
    objInitThread.detach();

    return env.Null();
}

Napi::Value napi_UnInitialize(const Napi::CallbackInfo& objInfo)
{
    const char * pFuncName = __func__;
    Napi::Env env = objInfo.Env();

    return SyncWrapper<Napi::Value>(pFuncName, objInfo, [](const char * const pFuncName, const Napi::CallbackInfo& objInfo) -> Napi::Value {
        Napi::Env objEnv = objInfo.Env();
        bool bRet = Yealink_Uninitialize();
        if (bRet) {
            // Properly need to be called from main thread - so not sure this can be async if we should want this ?
            g_objInitState.Clear();
        }
        return Napi::Boolean::New(objEnv, bRet);
    });
}

Napi::Value napi_ConnectToYealinkApplication(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::STRING, NS_HELP::STRING, NS_HELP::FUNCTION}))
    {
        return objEnv.Undefined();
    }

    const std::string strGuid = objInfo[0].As<Napi::String>();
    const std::string strSoftphoneName = objInfo[1].As<Napi::String>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<bool, Napi::Boolean>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, strGuid, strSoftphoneName]() {
        return Yealink_ConnectToYealinkConnecter(strGuid.c_str(), strSoftphoneName.c_str());
    },
    [](const Napi::Env& objEnv, bool result) {
        return Napi::Boolean::New(objEnv, result);
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return objEnv.Undefined();
}

Napi::Value napi_DisconnectFromYealinkApplication(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleAsyncFunction<Napi::Value, int>(pFuncName, objInfo, [pFuncName]() {
        Yealink_DisconnectFromYealinkConnecter();
        return 0;
    },
    [](const Napi::Env& objEnv, int iRet) {
        return objEnv.Undefined();
    });
}

Napi::Value napi_SetSoftphoneReady(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::BOOLEAN, NS_HELP::FUNCTION}))
    {
        return objEnv.Undefined();
    }

    bool bIsReady = objInfo[0].As<Napi::Boolean>().ToBoolean();
    Napi::Function funcJsResultCallback = objInfo[1].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, bIsReady]() {
        Yealink_SetSoftphoneReady(bIsReady);
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return objEnv.Undefined();
}

Napi::Value napi_IsSoftphoneInFocus(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName]() {
        return Yealink_IsSoftphoneInFocus();
    },
    [](const Napi::Env& objEnv, bool bRet) {
        return Napi::Boolean::New(objEnv, bRet);
    });
}

Napi::Value napi_GetVersion(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleAsyncFunction<Napi::String, std::string>(pFuncName, objInfo, [pFuncName]() {
        char szVersion[128] = { 0 };
        Yealink_ReturnCode eRetCode = Yealink_GetVersion(szVersion, sizeof(szVersion));
        if (Return_Ok == eRetCode)
        {
            return std::string(szVersion);
        }
        else
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            return std::string();
        }
    },
    [](const Napi::Env& objEnv, const std::string& strResult) {
        return Napi::String::New(objEnv, strResult);
    });
}
