#include "bt.h"
#include "asynchelper.h"
#include "help.h"
#include "commoninclude.h"


#define FUNC_ENV_SIGN                           const char * pFuncName = __func__;\
                                                Napi::Env env = objInfo.Env();


void toBTAddr(unsigned char *pDst, const std::string& strHex, size_t iSize)
{
    for (int i = 0; i < strHex.length() && iSize > 0; i += 2)
    {
        std::string str = strHex.substr(i, 2);
        unsigned char uVal = (unsigned char)strtol(str.c_str(), nullptr, 16);
        *(pDst++) = uVal;
        --iSize;
    }
}

std::string toBTAddrString(const unsigned char *pSrc, size_t iSize)
{
    std::stringstream ss;
    ss << std::hex << std::setfill('0');
    for (int i = 0; i < iSize;  ++i)
    {
        ss << std::setw(2) << (int)pSrc[i];
    }

    return ss.str();
}

Napi::Value napi_ConnectBTDevice(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Value, Yealink_ReturnCode>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        Yealink_ReturnCode eRetCode = Yealink_ConnectBTDevice(uDevId);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
        return eRetCode;
    },
    [](const Napi::Env& objEnv, Yealink_ReturnCode eRetCode) {
        return objEnv.Undefined();
    });
}

Napi::Value napi_GetConnectedBTDeviceName(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::String, std::string>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        char* pName = Yealink_GetConnectedBTDeviceName(uDevId);
        if (pName)
        {
            std::string strName(pName);
            Yealink_FreeString(pName);
            return strName;
        }
        else
        {
            YLExcetion::LogAndThrow(pFuncName, "return null");
            return std::string();
        }
    },
    [](const Napi::Env& objEnv, const std::string& strRet) {
        return Napi::String::New(objEnv, strRet);
    });
}

Napi::Value napi_ConnectNewDevice(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::STRING, NS_HELP::STRING, NS_HELP::BOOLEAN, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    std::string strName = objInfo[1].As<Napi::String>();
    std::string strBtAddr = objInfo[2].As<Napi::String>();
    bool bIsConnected = objInfo[3].As<Napi::Boolean>().ToBoolean();
    Napi::Function funcJsResultCallback = objInfo[4].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strName, strBtAddr, bIsConnected]() {
        Yealink_PairedDevice stPairedDevice;
        stPairedDevice.deviceName = (char*)strName.c_str();
        stPairedDevice.isConnected = bIsConnected;
        toBTAddr(stPairedDevice.deviceBTAddr, strBtAddr, sizeof(stPairedDevice.deviceBTAddr)/sizeof(uint8_t));

        Yealink_ReturnCode eRetCode = Yealink_ConnectNewDevice(uDevId, &stPairedDevice);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_ConnectPairedDevice(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::STRING, NS_HELP::STRING, NS_HELP::BOOLEAN, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    std::string strName = objInfo[1].As<Napi::String>();
    std::string strBtAddr = objInfo[2].As<Napi::String>();
    bool bIsConnected = objInfo[3].As<Napi::Boolean>().ToBoolean();
    Napi::Function funcJsResultCallback = objInfo[4].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strName, strBtAddr, bIsConnected]() {
        Yealink_PairedDevice stPairedDevice;
        stPairedDevice.deviceName = (char*)strName.c_str();
        stPairedDevice.isConnected = bIsConnected;
        toBTAddr(stPairedDevice.deviceBTAddr, strBtAddr, sizeof(stPairedDevice.deviceBTAddr)/sizeof(uint8_t));

        Yealink_ReturnCode eRetCode = Yealink_ConnectPairedDevice(uDevId, &stPairedDevice);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_DisconnectBTDevice(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Value, Yealink_ReturnCode>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        Yealink_ReturnCode eRetCode = Yealink_DisconnectBTDevice(uDevId);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
        return eRetCode;
    },
    [](const Napi::Env& objEnv, Yealink_ReturnCode eRetCode) {
        return objEnv.Undefined();
    });
}

Napi::Value napi_DisconnectPairedDevice(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::STRING, NS_HELP::STRING, NS_HELP::BOOLEAN, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    std::string strName = objInfo[1].As<Napi::String>();
    std::string strBtAddr = objInfo[2].As<Napi::String>();
    bool bIsConnected = objInfo[3].As<Napi::Boolean>().ToBoolean();
    Napi::Function funcJsResultCallback = objInfo[4].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strName, strBtAddr, bIsConnected]() {
        Yealink_PairedDevice stPairedDevice;
        stPairedDevice.deviceName = (char*)strName.c_str();
        stPairedDevice.isConnected = bIsConnected;
        toBTAddr(stPairedDevice.deviceBTAddr, strBtAddr, sizeof(stPairedDevice.deviceBTAddr)/sizeof(uint8_t));

        Yealink_ReturnCode eRetCode = Yealink_DisConnectPairedDevice(uDevId, &stPairedDevice);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_GetAutoConnecting(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        bool bAutoConnecting = Yealink_GetAutoConnecting(uDevId);
        return bAutoConnecting;
    },
    [](const Napi::Env& objEnv, bool bRet) {
        return Napi::Boolean::New(objEnv, bRet);
    });
}

Napi::Value napi_SetAutoConnecting(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncBoolSetter(pFuncName, objInfo, [pFuncName](unsigned short uDevId, bool bEnable) {
        Yealink_ReturnCode eRetCode = Yealink_SetAutoConnecting(uDevId, bEnable);
        if (Return_Ok != eRetCode)
        {
            throw YLReturnCodeException(pFuncName, eRetCode);
        }
    });
}

Napi::Value napi_IsPairingListSupported(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Boolean, bool>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        bool bSupported = Yealink_IsPairedListSupported(uDevId);
        return bSupported;
    },
    [](const Napi::Env& objEnv, bool bResult) {
        return Napi::Boolean::New(objEnv, bResult);
    });
}

Napi::Value napi_GetPairingList(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Object, Yealink_PairingList*>(pFuncName, objInfo,
    [pFuncName](unsigned short uDevId) {
        return Yealink_GetPairedList(uDevId);
    },
    [](const Napi::Env& objEnv, const Yealink_PairingList *pList) {
        Napi::Array arrDev = Napi::Array::New(objEnv);
        if (!pList)
        {
            return arrDev;
        }

        for (int i = 0; i < pList->count; i++)
        {
            Napi::Object objVal = Napi::Object::New(objEnv);

            objVal.Set(Napi::String::New(objEnv, "deviceName"), NAPI_NEW_STRING(objEnv, pList->pairedDevice[i].deviceName));
            objVal.Set(Napi::String::New(objEnv, "deviceBTAddr"), (Napi::String::New(objEnv, toBTAddrString(pList->pairedDevice[i].deviceBTAddr, sizeof(pList->pairedDevice[i].deviceBTAddr)/sizeof(uint8_t)))));
            objVal.Set(Napi::String::New(objEnv, "isConnected"), (Napi::Boolean::New(objEnv, pList->pairedDevice[i].isConnected)));
            arrDev.Set(i, objVal);
        }
        return arrDev;
    },
    [](Yealink_PairingList* pList) {
        if (pList)
        {
            Yealink_FreePairingList(pList);
        }
    });
}

Napi::Value napi_ClearPairingList(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Value, Yealink_ReturnCode>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        Yealink_ReturnCode eRetCode = Yealink_ClearPairedList(uDevId);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
        return eRetCode;
    },
    [](const Napi::Env& objEnv, Yealink_ReturnCode eRetCode) {
        return objEnv.Undefined();
    });
}

Napi::Value napi_SetBTDiscoverable(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Value, Yealink_ReturnCode>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        Yealink_ReturnCode eRetCode = Yealink_SetBTDiscoverable(uDevId);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
        return eRetCode;
    },
    [](const Napi::Env& objEnv, Yealink_ReturnCode eRetCode) {
        return objEnv.Undefined();
    });
}

Napi::Value napi_ScanNewDevices(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Value, Yealink_ReturnCode>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        Yealink_ReturnCode eRetCode = Yealink_ScanNewDevices(uDevId);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
        return eRetCode;
    },
    [](const Napi::Env& objEnv, Yealink_ReturnCode eRetCode) {
        return objEnv.Undefined();
    });
}

Napi::Value napi_StopBTScan(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Value, Yealink_ReturnCode>(pFuncName, objInfo, [pFuncName](unsigned short uDevId) {
        Yealink_ReturnCode eRetCode = Yealink_StopBTScan(uDevId);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
        return eRetCode;
    },
    [](const Napi::Env& objEnv, Yealink_ReturnCode eRetCode) {
        return objEnv.Undefined();
    });
}

Napi::Value napi_ClearPairedDevice(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    if (!NS_HELP::VerifyArgs(pFuncName, objInfo, {NS_HELP::NUMBER, NS_HELP::STRING, NS_HELP::STRING, NS_HELP::BOOLEAN, NS_HELP::FUNCTION}))
    {
        return env.Undefined();
    }

    const unsigned short uDevId = (unsigned short)(objInfo[0].As<Napi::Number>().Int32Value());
    std::string strName = objInfo[1].As<Napi::String>();
    std::string strBtAddr = objInfo[2].As<Napi::String>();
    bool bIsConnected = objInfo[3].As<Napi::Boolean>().ToBoolean();
    Napi::Function funcJsResultCallback = objInfo[4].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strName, strBtAddr, bIsConnected]() {
        Yealink_PairedDevice stPairedDevice;
        stPairedDevice.deviceName = (char*)strName.c_str();
        stPairedDevice.isConnected = bIsConnected;
        toBTAddr(stPairedDevice.deviceBTAddr, strBtAddr, sizeof(stPairedDevice.deviceBTAddr)/sizeof(uint8_t));

        Yealink_ReturnCode eRetCode = Yealink_ClearPairedDevice(uDevId, &stPairedDevice);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_GetSearchDeviceList(const Napi::CallbackInfo& objInfo)
{
    FUNC_ENV_SIGN

    return SimpleDeviceAsyncFunction<Napi::Object, Yealink_PairingList*>(pFuncName, objInfo,
    [pFuncName](unsigned short uDevId) {
        return Yealink_GetSearchDeviceList(uDevId);
    },
    [](const Napi::Env& objEnv, const Yealink_PairingList *pList) {
        Napi::Array arrDev = Napi::Array::New(objEnv);
        if (!pList)
        {
            return arrDev;
        }

        for (int i = 0; i < pList->count; i++)
        {
            Napi::Object objVal = Napi::Object::New(objEnv);

            objVal.Set(Napi::String::New(objEnv, "deviceName"), NAPI_NEW_STRING(objEnv, pList->pairedDevice[i].deviceName));
            objVal.Set(Napi::String::New(objEnv, "deviceBTAddr"), (Napi::String::New(objEnv, toBTAddrString(pList->pairedDevice[i].deviceBTAddr, sizeof(pList->pairedDevice[i].deviceBTAddr)/sizeof(uint8_t)))));
            objVal.Set(Napi::String::New(objEnv, "isConnected"), (Napi::Boolean::New(objEnv, pList->pairedDevice[i].isConnected)));
            arrDev.Set(i, objVal);
        }

        return arrDev;
    },
    [](Yealink_PairingList* pList) {
        if (pList)
        {
            Yealink_FreePairingList(pList);
        }
    });
}
