#include "camera.h"
#include "asynchelper.h"
#include "help.h"

#define FUNC_ENV_SIGN                           const char * pFuncName = __func__;\
                                                Napi::Env env = objInfo.Env();


Napi::Value napi_CameraIsReady(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        bool bReady = Yealink_CameraIsReady(uDevId);
        return bReady;
    },
    [](const Napi::Env& objEnv, bool bResult) {
        return Napi::Boolean::New(objEnv, bResult);
    });
}

Napi::Value napi_CameraDirectionCtrl(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 CameraDirectionCmd eCtrlCmd = (CameraDirectionCmd)(objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(CameraDirectionCtrl, uDevId, eCtrlCmd);

    return env.Undefined();
}

Napi::Value napi_CameraZoomCtrl(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 CameraZoomCmd eCtrlCmd = (CameraZoomCmd)(objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(CameraZoomCtrl, uDevId, eCtrlCmd);

    return env.Undefined();
}

Napi::Value napi_CameraPTZReset(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(CameraPTZReset, uDevId);

    return env.Undefined();
}

Napi::Value napi_CameraAddPreset(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 strFilePath = objInfo[1].As<Napi::String>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strFilePath]() {
        Yealink_ReturnCode eRetCode = Yealink_CameraAddPreset(uDevId, strFilePath.c_str());
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_CameraDelPreset(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 unsigned short uPresetIndex = (unsigned short)(objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(CameraDelPreset, uDevId, uPresetIndex);

    return env.Undefined();
}

Napi::Value napi_CameraDelAllPreset(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(CameraDelAllPreset, uDevId);

    return env.Undefined();
}

Napi::Value napi_CameraGetAllPreset(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Object, Yealink_CameraPresetList*>(pFuncName, objInfo,
    [pFuncName](unsigned short uDevId) {
        return Yealink_CameraGetAllPreset(uDevId);
    },
    [](const Napi::Env& objEnv, const Yealink_CameraPresetList *pList) {
        Napi::Array arrDev = Napi::Array::New(objEnv);
        if (!pList)
        {
            return arrDev;
        }

        for (int i = 0; i < pList->presetCount; i++)
        {
            Napi::Object objVal = Napi::Object::New(objEnv);

            objVal.Set(Napi::String::New(objEnv, "presetIndex"), (Napi::Number::New(objEnv, pList->presetInfo[i].presetIndex)));
            objVal.Set(Napi::String::New(objEnv, "pathPic"), NAPI_NEW_STRING(objEnv, pList->presetInfo[i].picPath));
            arrDev.Set(i, objVal);
        }
        return arrDev;
    },
    [](Yealink_CameraPresetList* pList) {
        if (pList)
        {
            Yealink_FreeCameraPresetInfo(pList);
        }
    });
}

Napi::Value napi_CameraUpdatePreset(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, 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 unsigned short uPresetIndex = (unsigned short)(objInfo[1].As<Napi::Number>().Int32Value());
    const std::string strFileName = objInfo[2].As<Napi::String>();
    Napi::Function funcJsResultCallback = objInfo[3].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, uPresetIndex, strFileName]() {
        Yealink_ReturnCode eRetCode = Yealink_CameraUpdatePreset(uDevId, uPresetIndex, strFileName.c_str());
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_CameraApplyPreset(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 iPresetIndex = (objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(CameraApplyPreset, uDevId, iPresetIndex);

    return env.Undefined();
}

Napi::Value napi_CameraGetSmartTrackingOptInfo(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Object, Yealink_CameraSmartTrackingOptList*>(pFuncName, objInfo,
    [pFuncName](unsigned short uDevId) {
        return Yealink_GetSmartTrackingOptInfo(uDevId);
    },
    [](const Napi::Env& objEnv, const Yealink_CameraSmartTrackingOptList *pList) {
        if (!pList)
        {
            return Napi::Array::New(objEnv);
        }

        Napi::Array arrDev = Napi::Array::New(objEnv, pList->optCount);
        for (int i = 0; i < pList->optCount; i++)
        {
            arrDev.Set(i, (int)pList->optSmartTracking[i]);
        }
        return arrDev;
    },
    [](Yealink_CameraSmartTrackingOptList* pList) {
        if (pList)
        {
            Yealink_FreeSmartTackingOptInfo(pList);
        }
    });
}

Napi::Value napi_CameraSetSmartTrackingOpt(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 CameraSmartTrackingOpt eSmartTrackingOpt = (CameraSmartTrackingOpt)(objInfo[1].As<Napi::Number>().Int32Value());
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    QUERY_ASYNC_WRAPPER_E(SetSmartTrackingOpt, uDevId, eSmartTrackingOpt);

    return env.Undefined();
}

Napi::Value napi_CameraGetSamrtTrackingOpt(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Number, CameraSmartTrackingOpt>(pFuncName, objInfo,
    [pFuncName](unsigned short uDevId) {
        CameraSmartTrackingOpt eCurrentOpt;
        Yealink_ReturnCode eRetCode = Yealink_GetSamrtTrackingOpt(uDevId, &eCurrentOpt);
        if (eRetCode == Return_Ok)
        {
            return eCurrentOpt;
        }
        else
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            return eCurrentOpt; // Dummy return - avoid compiler warnings.
        }
    },
    [](const Napi::Env& env, CameraSmartTrackingOpt eResult) {
        return Napi::Number::New(env, eResult);
    });
}

Napi::Value napi_CameraSetFaceDetection(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncBoolSetter(pFuncName, objInfo, [pFuncName](unsigned short uDevId, bool bEnable) {
        Yealink_ReturnCode eRetCode = Yealink_CameraSetFaceDetection(uDevId, bEnable);
        if (eRetCode != Return_Ok)
        {
            throw YLReturnCodeException(pFuncName, eRetCode);
        }
    });
}

Napi::Value napi_CameraIsFaceDetectionEnable(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        bool bEnable = Yealink_CameraIsFaceDetectionEnable(uDevId);
        return bEnable;
    },
    [](const Napi::Env& objEnv, bool bResult) {
        return Napi::Boolean::New(objEnv, bResult);
    });
}

Napi::Value napi_CameraIsInIdle(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        bool bInIdle = Yealink_CameraIsInIdle(uDevId);
        return bInIdle;
    },
    [](const Napi::Env& objEnv, bool bResult) {
        return Napi::Boolean::New(objEnv, bResult);
    });
}
