#include "fw.h"
#include "asynchelper.h"
#include "help.h"
#include "commoninclude.h"


#define FUNC_ENV_SIGN                           const char * pFuncName = __func__;\
                                                Napi::Env env = objInfo.Env();

#define NAPI_NEW_STRING(env, ptr)    (ptr ? Napi::String::New(env, ptr) : Napi::String::New(env, ""))


Napi::Value napi_GetFirmwareVersion(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::String, std::string>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        char szVersion[128] = {0};
        Yealink_ReturnCode eRetCode = Yealink_GetFirmwareVersion(uDevId, &szVersion[0], sizeof(szVersion));
        if (eRetCode == Return_Ok)
        {
            return std::string(szVersion);
        }
        else
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
            return std::string(); // Dummy return - avoid compiler warnings.
        }
    },
    [](const Napi::Env& objEnv, std::string strResult) {
        return Napi::String::New(objEnv, strResult);
    });
}

Napi::Value napi_GetLatestFirmwareInformation(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<Yealink_FirmwareInfo *, Napi::Object>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId]() {
        Yealink_FirmwareInfo * pFwInfo = Yealink_GetLatestFirmwareInformation(uDevId);
        if (!pFwInfo) {
            YLExcetion::LogAndThrow(pFuncName, "null returned");
        }

        return pFwInfo;
    },
    [](const Napi::Env& objEnv, Yealink_FirmwareInfo *pFwInfo) {
        Napi::Object objResult = Napi::Object::New(objEnv);
        if (pFwInfo) {
            if (pFwInfo->version)
            {
                objResult.Set(Napi::String::New(objEnv, "version"), NAPI_NEW_STRING(objEnv, pFwInfo->version));
            }
            if (pFwInfo->fileSize)
            {
                objResult.Set(Napi::String::New(objEnv, "fileSize"), NAPI_NEW_STRING(objEnv, pFwInfo->fileSize));
            }
            if (pFwInfo->releaseDate)
            {
                objResult.Set(Napi::String::New(objEnv, "releaseDate"), NAPI_NEW_STRING(objEnv, pFwInfo->releaseDate));
            }
            if (pFwInfo->stage)
            {
                objResult.Set(Napi::String::New(objEnv, "stage"), NAPI_NEW_STRING(objEnv, pFwInfo->stage));
            }
            if (pFwInfo->releaseNotes)
            {
                objResult.Set(Napi::String::New(objEnv, "releaseNotes"), NAPI_NEW_STRING(objEnv, (const char16_t*)pFwInfo->releaseNotes));
            }
        }

        return objResult;
    },
    [](Yealink_FirmwareInfo* pFwInfo) {
        if (pFwInfo)
        {
            Yealink_FreeFirmwareInfo(pFwInfo);
        }
    });

    if (pWorker)
    {
        pWorker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_DownloadFirmware(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 strVersion = objInfo[1].As<Napi::String>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strVersion]() {
        Yealink_ReturnCode eRetCode = Yealink_DownloadFirmware(uDevId, strVersion.c_str());
        if (Return_Ok != eRetCode && Return_Async != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_UpdateFirmware(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 strFwPath = objInfo[1].As<Napi::String>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<Yealink_ReturnCode, Napi::Number>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strFwPath]() {
        Yealink_ReturnCode eRetCode = Yealink_UpdateFirmware(uDevId, strFwPath.c_str());
        if (Return_Ok != eRetCode && Return_Async != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
        return eRetCode;
    },
    [](const Napi::Env& objEnv, Yealink_ReturnCode eRet) {
        return Napi::Number::New(objEnv, eRet);
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_GetFirmwareFilePath(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 strVersion = objInfo[1].As<Napi::String>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<std::string, Napi::String>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strVersion]() {
        char* pPath = Yealink_GetFirmwareFilePath(uDevId, strVersion.c_str());
        if (pPath)
        {
            std::string strPath(pPath);
            Yealink_FreeString(pPath);
            return strPath;
        }
        YLExcetion::LogAndThrow(pFuncName, "null returned");
    },
    [](const Napi::Env& objEnv, const std::string& strPath) {
        Napi::String objResult = Napi::String::New(objEnv, strPath.c_str());
        return objResult;
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_CancelFirmwareDownload(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(CancelFirmwareDownload, uDevId);

    return env.Undefined();
}

Napi::Value napi_CheckForFirmwareUpdate(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 pWoreker = new AsyncWorker<bool, Napi::Boolean>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId]() {
        Yealink_ReturnCode eRetCode =  Yealink_CheckForFirmwareUpdate(uDevId);
        if (Return_Ok == eRetCode || Firmware_Available == eRetCode)
        {
            return true;
        }
        else if (Firmware_UpToDate == eRetCode)
        {
            return false;
        }

        YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        return false;
    },
    [](const Napi::Env& objEnv, const bool bRet) {
        return Napi::Boolean::New(objEnv, bRet);
    });

    if (pWoreker)
        pWoreker->Queue();

    return env.Undefined();
}

