#include "device.h"
#include "asynchelper.h"
#include "help.h"
#include "commoninclude.h"


#define SAFE_DELETE(p) \
    if (p)\
    {\
        delete(p);\
        p = nullptr;\
    }

#define FUNC_ENV_SIGN                           const char * pFuncName = __func__;\
                                                Napi::Env env = objInfo.Env();

#define QUERY_ASYNC_WRAPPER_E(op, ...)          auto *const pWorker = new AsyncWorker<void, void>(\
                                                pFuncName,\
                                                funcJsResultCallback,\
                                                [pFuncName, __VA_ARGS__]() {\
                                                    Yealink_ReturnCode eRetCode = Yealink_##op(__VA_ARGS__);\
                                                    if (Return_Ok != eRetCode)\
                                                        YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);\
                                                });\
                                                if (pWorker) \
                                                    pWorker->Queue();

#define QUERY_ASYNC_CALL_WRAPPER_E(op, ...)     auto *const pWorker = new AsyncWorker<void, void>(\
                                                pFuncName,\
                                                funcJsResultCallback,\
                                                [pFuncName, __VA_ARGS__]() {\
                                                    Yealink_CallReturn eRetCode = Yealink_##op(__VA_ARGS__);\
                                                    if (CALL_RETURN_SUCCESS != eRetCode)\
                                                        YLCallReturnCodeException::LogAndThrow(pFuncName, eRetCode);\
                                                });\
                                                if (pWorker) \
                                                    pWorker->Queue();

#define QUERY_ASYNC_WRAPPER(op, ...)            auto *const pWorker = new AsyncWorker<bool, Napi::Boolean>(\
                                                pFuncName,\
                                                funcJsResultCallback,\
                                                [pFuncName, __VA_ARGS__]() {\
                                                    return Yealink_##op(__VA_ARGS__);\
                                                },\
                                                [](const Napi::Env& env, const bool result) {\
                                                    return Napi::Boolean::New(env, result);\
                                                });\
                                                if (pWorker) \
                                                    pWorker->Queue();

// Use only the simple interface query of the device id
#define SIMPLE_QUERY_ASYNC_WRAPPER(op)          FUNC_ENV_SIGN\
                                                if (!NS_HELP::VerifyArgs(pFuncName, objInfo, { NS_HELP::NUMBER, NS_HELP::FUNCTION }))\
                                                {\
                                                    return env.Undefined();\
                                                }\
                                                const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());\
                                                Napi::Function jsResultCallback = objInfo[1].As<Napi::Function>();\
                                                QUERY_ASYNC_WRAPPER(op, uDevId)



Napi::Value napi_GetSupportedFeatures(const Napi::CallbackInfo& objInfo)
{
    const char * pFuncName = __func__;
    Napi::Env env = objInfo.Env();

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, { NS_HELP::NUMBER, NS_HELP::FUNCTION }))
    {
        return env.Undefined();
    }

    struct FeatureListCountPair
    {
        const DeviceFeature* pFeatureList;
        unsigned int uCount;
    };

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    Napi::Function jsResultCallback = objInfo[1].As<Napi::Function>();

    auto *const pWorker = new AsyncWorker<FeatureListCountPair, Napi::Array>(
        pFuncName,
        jsResultCallback,
    [pFuncName, uDevId]() {
        FeatureListCountPair stPair;
        stPair.pFeatureList = Yealink_GetSupportedFeatures(uDevId, &stPair.uCount);
        return stPair;
    },
    [](const Napi::Env& env, const FeatureListCountPair& stPair) {

        Napi::Array arrElements = Napi::Array::New(env);

        if (stPair.pFeatureList != nullptr) {
            for (unsigned int i=0; i<stPair.uCount; ++i) {
                Napi::Number feature = Napi::Number::New(env, (uint32_t)stPair.pFeatureList[i]);
                arrElements.Set(i, feature);
            }
        }

        return arrElements;
    },
    [](const FeatureListCountPair& stPair) {
        if (stPair.pFeatureList != nullptr)
        {
            Yealink_FreeSupportedFeatures(stPair.pFeatureList);
        }
    });

    if (pWorker)
    {
        pWorker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_IsFeatureSupported(const Napi::CallbackInfo& objInfo)
{
    const char * pFuncName = __func__;
    Napi::Env env = objInfo.Env();

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short deviceId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    const DeviceFeature feature = (DeviceFeature)(objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    (new AsyncWorker<bool, Napi::Boolean>(
         pFuncName,
         funcJsResultCallback,
    [pFuncName, deviceId, feature]() {
        return Yealink_IsFeatureSupported(deviceId, feature);
    },
    [](const Napi::Env& env, const bool result) {
        return Napi::Boolean::New(env, result);
    }
     ))->Queue();

    return env.Undefined();
}

Napi::Value napi_IsRingSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned int uDevId) {
        return Yealink_IsRingSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_SetRing(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncBoolSetter(pFuncName, objInfo, [pFuncName](unsigned short uDevId, bool bEnable) {
        Yealink_ReturnCode eRetCode = Yealink_SetRing(uDevId, bEnable);
        if (eRetCode != Return_Ok)
        {
            throw YLReturnCodeException(pFuncName, eRetCode);
        }
    });
}

Napi::Value napi_IsOffHookSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned int uDevId) {
        return Yealink_IsOffHookSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_SetOffHook(const Napi::CallbackInfo& objInfo)
{
    const char * pFuncName = __func__;

    return SimpleDeviceAsyncBoolSetter(pFuncName, objInfo, [pFuncName](unsigned short uDevId, bool bEnable) {
        Yealink_ReturnCode eRetCode = Yealink_SetOffHook(uDevId, bEnable);
        if (eRetCode != Return_Ok)
        {
            throw YLReturnCodeException(pFuncName, eRetCode);
        }
    });
}

Napi::Value napi_IsMuteSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned int uDevId) {
        return Yealink_IsMuteSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_SetMute(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncBoolSetter(pFuncName, objInfo, [pFuncName](unsigned short uDevId, bool bEnable) {
        Yealink_ReturnCode eRetCode = Yealink_SetMute(uDevId, bEnable);
        if (eRetCode != Return_Ok)
        {
            throw YLReturnCodeException(pFuncName, eRetCode);
        }
    });
}

Napi::Value napi_IsHoldSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned int uDevId) {
        return Yealink_IsHoldSupport(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_SetHold(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleDeviceAsyncBoolSetter(pFuncName, objInfo, [pFuncName](unsigned short uDevId, bool bEnable) {
        Yealink_ReturnCode eRetCode = Yealink_SetHold(uDevId, bEnable);
        if (eRetCode != Return_Ok)
        {
            throw YLReturnCodeException(pFuncName, eRetCode);
        }
    });
}

Napi::Value napi_IsOnlineSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo,
    [pFuncName](unsigned int uDevId) {
        return Yealink_IsOnlineSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_SetOnline(const Napi::CallbackInfo& objInfo)
{
    Napi::Env env = objInfo.Env();
    return env.Undefined();
}

Napi::Value napi_IsYlHidStdHidSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned int uDevId) {
        return Yealink_IsYlHidStdHidSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_SetHidWorkingState(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    const Yealink_HidState eState = (Yealink_HidState)(objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function jsRetCallback = objInfo[2].As<Napi::Function>();

    auto *const pWorker = new AsyncWorker<void, void>(
        pFuncName,
        jsRetCallback,
    [pFuncName, uDevId, eState]() {
        Yealink_ReturnCode eRetCode = Yealink_SetHidWorkingState(uDevId, eState);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWorker)
    {
        pWorker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_GetHidWorkingState(const Napi::CallbackInfo& objInfo)
{
    const char * pFuncName = __func__;
    Napi::Env env = objInfo.Env();

    return SimpleDeviceAsyncFunction<Napi::Number, Yealink_HidState>(pFuncName, objInfo,
    [pFuncName](unsigned short uDevId) {
        Yealink_HidState eState;
        Yealink_ReturnCode eRetCode = Yealink_GetHidWorkingState(uDevId, &eState);
        if (eRetCode == Return_Ok)
        {
            return eState;
        }
        else
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            return eState; // Dummy return - avoid compiler warnings.
        }
    },
    [](const Napi::Env& env, Yealink_HidState eResult) {
        return Napi::Number::New(env, eResult);
    });
}

Napi::Value napi_SetStdHidEventsFromYealinkDevices(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, { NS_HELP::BOOLEAN, NS_HELP::FUNCTION }))
    {
        return env.Undefined();
    }

    Napi::Boolean bEnable = objInfo[1].As<Napi::Number>().ToBoolean();
    Napi::Function funcJsResultCallback = objInfo[1].As<Napi::Function>();

    auto *const pWorker = new AsyncWorker<bool, Napi::Boolean>(pFuncName, funcJsResultCallback,
    [pFuncName, bEnable]() {
        Yealink_SetStdHidEventsFromYealinkDevices(bEnable);
        return true; // default return true
    },
    [](const Napi::Env& env, const bool result) {
        return Napi::Boolean::New(env, result);
    });

    if (pWorker)
        pWorker->Queue();

    return env.Undefined();
}

Napi::Value napi_IsStdHidEventsFromYealinkDevices(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName]() {
        return Yealink_IsStdHidEventsFromYealinkDevicesEnabled();
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

#define FUNC_CALL_WITH_CID(op, objInfo)     \
    FUNC_ENV_SIGN\
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::FUNCTION}))\
    {\
        return env.Undefined();\
    }\
    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());\
    const int iCid = (int)(objInfo[1].As<Napi::Number>().Int32Value());\
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();\
    QUERY_ASYNC_CALL_WRAPPER_E(op, uDevId, iCid);\
    return env.Undefined();

std::shared_ptr<Yealink_ContactInfo> TransContact(Napi::Object objCon)
{
    std::shared_ptr<Yealink_ContactInfo> spContact = std::make_shared<Yealink_ContactInfo>();
    std::string strName(NS_HELP::getObjStringOrDefault(objCon, "name", ""));
    spContact->name = new char[strName.length() + 1];
    memset(spContact->name, 0, strName.length() + 1);
    memcpy(spContact->name, strName.c_str(), strName.length());
    std::string strNumber(NS_HELP::getObjStringOrDefault(objCon, "number", ""));
    spContact->number = new char[strNumber.length() + 1];
    memset(spContact->number, 0, strNumber.length() + 1);
    memcpy(spContact->number, strNumber.c_str(), strNumber.length());

    return spContact;
}

Napi::Value napi_EnterDialCall(const Napi::CallbackInfo& objInfo)
{
    FUNC_CALL_WITH_CID(EnterDialCall, objInfo);
}

Napi::Value napi_RingBackCall(const Napi::CallbackInfo& objInfo)
{
    FUNC_CALL_WITH_CID(RingBackCall, objInfo);
}

Napi::Value napi_IncomingCall(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::OBJECT, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    int i = 0;
    const unsigned short uDevId = (unsigned short)(objInfo[i++].As<Napi::Number>().Int32Value());
    const int iCid = (int)(objInfo[i++].As<Napi::Number>().Int32Value());
    std::shared_ptr<Yealink_ContactInfo> spContact = TransContact(objInfo[i++].As<Napi::Object>());
    Napi::Function funcJsResultCallback = objInfo[i++].As<Napi::Function>();

    INFO_TRACE << "incoming call, dev:" << uDevId << ", cid:" << iCid << 
                ", contact name:" << spContact->name << ", number:" << spContact->number;

    auto *const pWorker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
        [pFuncName, uDevId, iCid, spContact]() {
            Yealink_CallReturn eRetCode = Yealink_IncomingCall(uDevId, iCid, spContact.get());
            if (CALL_RETURN_SUCCESS != eRetCode)
            {
                YLCallReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            }
        },
        [spContact]() {
            if (spContact)
            {
                SAFE_DELETE(spContact->name);
                SAFE_DELETE(spContact->number);
            }
        }
    );

    if (pWorker) 
        pWorker->Queue();
                                                    
    return env.Undefined();
}

Napi::Value napi_OutgoingCall(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::OBJECT, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    int i = 0;
    const unsigned short uDevId = (unsigned short)(objInfo[i++].As<Napi::Number>().Int32Value());
    const int iCid = (int)(objInfo[i++].As<Napi::Number>().Int32Value());
    std::shared_ptr<Yealink_ContactInfo> spContact = TransContact(objInfo[i++].As<Napi::Object>());
    Napi::Function funcJsResultCallback = objInfo[i++].As<Napi::Function>();

    INFO_TRACE << "outgoing call, dev:" << uDevId << ", cid:" << iCid << 
                ", contact name:" << spContact->name << ", number:" << spContact->number;

    auto *const pWorker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
        [pFuncName, uDevId, iCid, spContact]() {
            Yealink_CallReturn eRetCode = Yealink_OutgoingCall(uDevId, iCid, spContact.get());
            if (CALL_RETURN_SUCCESS != eRetCode)
            {
                YLCallReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            }
        },
        [spContact]() {
            if (spContact)
            {
                SAFE_DELETE(spContact->name);
                SAFE_DELETE(spContact->number);
            }
        }
    );

    if (pWorker) 
        pWorker->Queue();
                                                    
    return env.Undefined();
}

Napi::Value napi_TerminateCall(const Napi::CallbackInfo& objInfo)
{
    FUNC_CALL_WITH_CID(TerminateCall, objInfo);
}

Napi::Value napi_AnswerCall(const Napi::CallbackInfo& objInfo)
{
    FUNC_CALL_WITH_CID(AnswerCall, objInfo);
}

Napi::Value napi_HoldCall(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::OBJECT, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    int i = 0;
    const unsigned short uDevId = (unsigned short)(objInfo[i++].As<Napi::Number>().Int32Value());
    const int iCid = (int)(objInfo[i++].As<Napi::Number>().Int32Value());
    std::shared_ptr<Yealink_ContactInfo> spContact = TransContact(objInfo[i++].As<Napi::Object>());
    Napi::Function funcJsResultCallback = objInfo[i++].As<Napi::Function>();

    INFO_TRACE << "hold call, dev:" << uDevId << ", cid:" << iCid << 
                ", contact name:" << spContact->name << ", number:" << spContact->number;

    auto *const pWorker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
        [pFuncName, uDevId, iCid, spContact]() {
            Yealink_CallReturn eRetCode = Yealink_HoldCall(uDevId, iCid, spContact.get());
            if (CALL_RETURN_SUCCESS != eRetCode)
            {
                YLCallReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            }
        },
        [spContact]() {
            if (spContact)
            {
                SAFE_DELETE(spContact->name);
                SAFE_DELETE(spContact->number);
            }
        }
    );

    if (pWorker) 
        pWorker->Queue();
                                                    
    return env.Undefined();
}

Napi::Value napi_ResumeCall(const Napi::CallbackInfo& objInfo)
{
    FUNC_CALL_WITH_CID(ResumeCall, objInfo);
}

Napi::Value napi_MuteCall(const Napi::CallbackInfo& objInfo)
{
    FUNC_CALL_WITH_CID(MuteCall, objInfo);
}

Napi::Value napi_UnmuteCall(const Napi::CallbackInfo& objInfo)
{
    FUNC_CALL_WITH_CID(UnmuteCall, objInfo);
}

Napi::Value napi_UpdateContact(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::OBJECT, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    int i = 0;
    const unsigned short uDevId = (unsigned short)(objInfo[i++].As<Napi::Number>().Int32Value());
    const int iCid = (int)(objInfo[i++].As<Napi::Number>().Int32Value());
    std::shared_ptr<Yealink_ContactInfo> spContact = TransContact(objInfo[i++].As<Napi::Object>());
    Napi::Function funcJsResultCallback = objInfo[i++].As<Napi::Function>();

    INFO_TRACE << "update contact, dev:" << uDevId << ", cid:" << iCid << 
                ", contact name:" << spContact->name << ", number:" << spContact->number;

    auto *const pWorker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
        [pFuncName, uDevId, iCid, spContact]() {
            Yealink_CallReturn eRetCode = Yealink_UpdateContact(uDevId, iCid, spContact.get());
            if (CALL_RETURN_SUCCESS != eRetCode)
            {
                YLCallReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            }
        },
        [spContact]() {
            if (spContact)
            {
                SAFE_DELETE(spContact->name);
                SAFE_DELETE(spContact->number);
            }
        }
    );

    if (pWorker) 
        pWorker->Queue();
                                                    
    return env.Undefined();
}

Napi::Value napi_IsSetDateTimeSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned int uDevId) {
        return Yealink_IsSetDateTimeSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_SetDateTime(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::OBJECT, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    Napi::Object objTime = objInfo[1].As<Napi::Object>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    timedate_t stDateTime;
    stDateTime.sec = NS_HELP::getObjInt32OrDefault(objTime, "sec", 0);
    stDateTime.min = NS_HELP::getObjInt32OrDefault(objTime, "min", 0);
    stDateTime.hour = NS_HELP::getObjInt32OrDefault(objTime, "hour", 0);
    stDateTime.mday = NS_HELP::getObjInt32OrDefault(objTime, "mday", 0);
    stDateTime.mon = NS_HELP::getObjInt32OrDefault(objTime, "mon", 0);
    stDateTime.year = NS_HELP::getObjInt32OrDefault(objTime, "year", 0);
    stDateTime.wday = NS_HELP::getObjInt32OrDefault(objTime, "wday", 0);

    auto *const pWorker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, stDateTime]() {
        Yealink_ReturnCode eRetCode = Yealink_SetDateTime(uDevId, &stDateTime);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWorker)
        pWorker->Queue();

    return env.Undefined();
}

Napi::Value napi_IsUploadRingtoneSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        return Yealink_IsUploadRingtoneSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_UploadRingtone(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::STRING, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    const std::string strFileName = objInfo[1].As<Napi::String>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    auto *const pWorker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strFileName]() {
        Yealink_ReturnCode eRetCode = Yealink_UploadRingtone(uDevId, strFileName.c_str());
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWorker)
    {
        pWorker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_PlayRingtone(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    const int iType = objInfo[1].As<Napi::Number>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    auto *const pWorker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, iType]() {
        Yealink_ReturnCode eRetCode = Yealink_PlayRingtone(uDevId, iType);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWorker)
    {
        pWorker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_IsBusylightSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        return Yealink_IsBusylightSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_SetBusylightStatus(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    UsbBusylight eStatus = (UsbBusylight)(objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(SetBusylightStatus, uDevId, eStatus);

    return env.Undefined();
}

Napi::Value napi_GetBusylightStatus(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Number, UsbBusylight>(pFuncName, objInfo,
    [pFuncName](unsigned short uDevId) {
        int iStatus;
        Yealink_ReturnCode eRetCode = Yealink_GetBusylightStatus(uDevId, &iStatus);
        if (eRetCode == Return_Ok)
        {
            return (UsbBusylight)iStatus;
        }
        else
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            return (UsbBusylight)iStatus; // Dummy return - avoid compiler warnings.
        }
    },
    [](const Napi::Env& env, UsbBusylight eStatus) {
        return Napi::Number::New(env, eStatus);
    });
}

Napi::Value napi_IsEqualizerSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        return Yealink_IsEqualizerSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_GetEqualizerMode(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Number, EqualizerMode>(pFuncName, objInfo,
    [pFuncName](unsigned short uDevId) {
        int iMode;
        Yealink_ReturnCode eRetCode = Yealink_GetEqualizerMode(uDevId, &iMode);
        if (eRetCode == Return_Ok)
        {
            return (EqualizerMode)iMode;
        }
        else
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            return (EqualizerMode)iMode; // Dummy return - avoid compiler warnings.
        }
    },
    [](const Napi::Env& env, EqualizerMode eMode) {
        return Napi::Number::New(env, eMode);
    });
}

Napi::Value napi_SetEqualizerMode(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    const EqualizerMode eMode = (EqualizerMode)(objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(SetEqualizerMode, uDevId, eMode);

    return env.Undefined();
}

Napi::Value napi_IsWizardModeSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        return Yealink_IsWizardModeSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_SetWizardMode(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::NUMBER, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    const WizardModes eMode = (WizardModes)(objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(SetWizardMode, uDevId, eMode);

    return env.Undefined();
}

Napi::Value napi_GetWizardMode(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Number, WizardModes>(pFuncName, objInfo,
    [pFuncName](unsigned short uDevId) {
        int iMode;
        Yealink_ReturnCode eRetCode = Yealink_GetWizardMode(uDevId, &iMode);
        if (eRetCode == Return_Ok)
        {
            return (WizardModes)iMode;
        }
        else
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            return (WizardModes)iMode; // Dummy return - avoid compiler warnings.
        }
    },
    [](const Napi::Env& env, WizardModes eMode) {
        return Napi::Number::New(env, eMode);
    });
}

Napi::Value napi_FactoryReset(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Value, Yealink_ReturnCode>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        Yealink_ReturnCode eRetCode = Yealink_FactoryReset(uDevId);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
        return eRetCode;
    }, [](const Napi::Env& env, Yealink_ReturnCode cppResult) {
        return env.Undefined();
    });
}

Napi::Value napi_RebootDevice(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Value, Yealink_ReturnCode>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        Yealink_ReturnCode eRetCode = Yealink_Reboot(uDevId);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
        return eRetCode;
    }, [](const Napi::Env& env, Yealink_ReturnCode cppResult) {
        return env.Undefined();
    });
}

Napi::Value napi_StartExportAudio(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::STRING, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    const std::string strPath = objInfo[1].As<Napi::String>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    auto *const pWorker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strPath]() {
        Yealink_ReturnCode eRetCode = Yealink_StartExportAudio(uDevId, strPath.c_str());
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWorker)
    {
        pWorker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_StopExportAudio(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[1].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(StopExportAudio, uDevId);

    return env.Undefined();
}

Napi::Value napi_IsAudioModeSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        return Yealink_IsAudioModeSupported(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_ChangeAudioMode(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    const AudioMode eAudioMode = (AudioMode)(objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(ChangeAudioMode, uDevId, eAudioMode);

    return env.Undefined();
}

Napi::Value napi_HasDectInDocked(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        return Yealink_HasDectInDocked(uDevId);
    },
    [](const Napi::Env& objEnv, bool bCppResult) {
        return Napi::Boolean::New(objEnv, bCppResult);
    });
}

Napi::Value napi_GetRegionVersion(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[1].As<Napi::Function>();

    auto *const pWorker = new AsyncWorker<char*, Napi::String>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId]() {
        char* pVersion = Yealink_GetRegionVersion(uDevId);
        return pVersion;
    },
    [](const Napi::Env& objEnv, char* pVersion) {
        return Napi::String::New(objEnv, pVersion ? pVersion : "");;
    },
    [](char* pVersion) {
        if (nullptr != pVersion)
        {
            Yealink_FreeString(pVersion);
        }
    });

    if (pWorker)
    {
        pWorker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_SetCurrentTalkContact(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN
    // if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::STRING, NS_HELP::FUNCTION}))
    // {
    //     return env.Undefined();
    // }

    // const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    // Napi::Object objContact = objInfo[1].As<Napi::Object>();
    // Yealink_ContactInfo stContact;
    // stContact.name = std::string(NS_HELP::getObjStringOrDefault(objContact, "strName", "")).c_str();
    // stContact.number = std::string(NS_HELP::getObjStringOrDefault(objContact, "strName", "")).c_str();
    // Napi::Function funcJsResultCallback = objInfo[1].As<Napi::Function>();

    // auto *const pWorker = new AsyncWorker<void, void>(
    //     pFuncName,
    //     funcJsResultCallback,
    // [pFuncName, uDevId, stContact]() {
    //     Yealink_ReturnCode eRetCode = Yealink_SetCurrentTalkContact(uDevId, &stContact);
    //     if (Return_Ok != eRetCode)
    //     {
    //         YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
    //     }
    // });

    return env.Undefined();
}

Napi::Value napi_SetContactInfoList(const Napi::CallbackInfo& objInfo)
{
    Napi::Env env = objInfo.Env();
    return env.Undefined();
}

Napi::Value napi_GetBatteryStatus(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    // temp define
    struct BatteryInfo
    {
        int iLevelInPercent = 0;
        bool bCharging = false;
        bool bBatteryLow = false;
    };

    return SimpleDeviceAsyncFunction<Napi::Object, BatteryInfo>(pFuncName, objInfo,
    [pFuncName](unsigned short uDevId) {
        BatteryInfo stBattery;
        Yealink_ReturnCode eRetCode = Yealink_GetBatteryStatus(uDevId, &stBattery.iLevelInPercent, &stBattery.bCharging, &stBattery.bBatteryLow);
        if (eRetCode == Return_Ok)
        {
            return stBattery;
        }
        else
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            return stBattery; // Dummy return - avoid compiler warnings.
        }
    },
    [](const Napi::Env& objEnv, BatteryInfo stBattery) {
        Napi::Object objBattery = Napi::Object::New(objEnv);
        objBattery.Set(Napi::String::New(objEnv, "levelInPercent"), (Napi::Number::New(objEnv, stBattery.iLevelInPercent)));
        objBattery.Set(Napi::String::New(objEnv, "charging"), (Napi::Boolean::New(objEnv, stBattery.bCharging)));
        objBattery.Set(Napi::String::New(objEnv, "batteryLow"), (Napi::Boolean::New(objEnv, stBattery.bBatteryLow)));
        return objBattery;
    });
}

Napi::Value napi_GetDeviceImagePath(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::String, std::string>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        char* pPath = Yealink_GetDeviceImagePath(uDevId);
        if (pPath)
        {
            std::string strPath(pPath);
            Yealink_FreeString(pPath);
            return strPath;
        }
        else
        {
            YLExcetion::LogAndThrow(pFuncName, "null returned");
            return std::string(); // Dummy return - avoid compiler warnings.
        }
    }, [](const Napi::Env& env, const std::string& strCppRet) {
        return Napi::String::New(env, strCppRet);
    });
}

Napi::Value napi_ExportDevLog(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::STRING, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    const std::string strPath = objInfo[1].As<Napi::String>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    auto *const pWorker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strPath]() {
        VERBOSE_TRACE << "export device log, dev:" << uDevId << ", path:" << strPath;
        Yealink_ReturnCode eRetCode = Yealink_ExportDevLog(uDevId, strPath.c_str());
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWorker)
    {
        pWorker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_UploadDevLogToCloud(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[1].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(UploadDevLogToCloud, uDevId);

    return env.Undefined();
}

Napi::Value napi_GetESN(const Napi::CallbackInfo& objInfo)
{
    Napi::Env env = objInfo.Env();
    return env.Undefined();
}
