#include "settings.h"
#include "help.h"
#include "asynchelper.h"
#include "logwrapper.h"


#define FUNC_ENV_SIGN                           const char * pFuncName = __func__;\
                                                Napi::Env env = objInfo.Env();


static void Custom_FreeDeviceSettings(DeviceSettings* setting) {
    if (setting != nullptr) {
        if (setting->settingInfo != nullptr) {

            SettingInfo *lstStngInfo = setting->settingInfo;
            for (unsigned int i = 0; i < setting->settingCount; i++) {
                delete[] lstStngInfo->guid;
                delete[] lstStngInfo->name;
                delete[] lstStngInfo->helpText;
                delete[](char *)lstStngInfo->currValue;
                //List
                if (lstStngInfo->listKeyValue != nullptr) {
                    ListKeyValue *lstKeyVal = lstStngInfo->listKeyValue;
                    for (int i = 0; i < lstStngInfo->listSize; i++) {
                        delete[](char *)lstKeyVal->value;
                        //dependents
                        if (lstStngInfo->isDepedentsetting) {
                            for (int depIdx = 0; depIdx < lstKeyVal->dependentcount; depIdx++) {
                                delete[](lstKeyVal->dependents + depIdx)->GUID;
                            }
                            delete[] lstKeyVal->dependents;
                        }
                        lstKeyVal++;
                    }
                }
                delete[] lstStngInfo->listKeyValue;

                //validation rule
                if (lstStngInfo->validationRule != nullptr) {
                    delete[] lstStngInfo->validationRule->regExp;
                }
                delete lstStngInfo->validationRule;
                delete[] lstStngInfo->groupName;
                delete[] lstStngInfo->groupHelpText;
                delete[](char *)lstStngInfo->dependentDefaultValue;
                lstStngInfo++;
            }
        }
        delete[] setting->settingInfo;
    }
    delete setting;
}

static void toNodeType(const unsigned short deviceId, DeviceSettings *src, Napi::Object& dest)
{
    Napi::Env env = dest.Env();

    Napi::Array settings = Napi::Array::New(env, src->settingCount);
    for (unsigned int i=0; i<src->settingCount; ++i)
    {
        SettingInfo& settingSrc = src->settingInfo[i];

        Napi::Object settingDst = Napi::Object::New(env);

        settingDst.Set(Napi::String::New(env, "settingGuid"), Napi::String::New(env, settingSrc.guid ? settingSrc.guid : ""));
        settingDst.Set(Napi::String::New(env, "name"), Napi::String::New(env, settingSrc.name ? settingSrc.name : ""));
        settingDst.Set(Napi::String::New(env, "helpText"), Napi::String::New(env, settingSrc.helpText ? settingSrc.helpText : ""));
        settingDst.Set(Napi::String::New(env, "isValidationSupport"), Napi::Boolean::New(env, settingSrc.isValidationSupport));

        ValidationRule *validationRuleSrc = settingSrc.validationRule;
        if (validationRuleSrc)
        {
            Napi::Object validationRuleDst = Napi::Object::New(env);

            validationRuleDst.Set(Napi::String::New(env, "minLength"), Napi::Number::New(env, validationRuleSrc->minLength));
            validationRuleDst.Set(Napi::String::New(env, "maxLength"), Napi::Number::New(env, validationRuleSrc->maxLength));
            validationRuleDst.Set(Napi::String::New(env, "regExp"), Napi::String::New(env, validationRuleSrc->regExp ? validationRuleSrc->regExp : ""));

            settingDst.Set(Napi::String::New(env, "validationRule"), validationRuleDst);
        }

        settingDst.Set(Napi::String::New(env, "isDeviceRestart"), Napi::Boolean::New(env, settingSrc.isDeviceRestart));
        settingDst.Set(Napi::String::New(env, "isSettingProtected"), Napi::Boolean::New(env, settingSrc.isSettingProtected));
        settingDst.Set(Napi::String::New(env, "isSettingProtectionEnabled"), Napi::Boolean::New(env, settingSrc.isSettingProtectionEnabled));
        settingDst.Set(Napi::String::New(env, "cntrlType"), Napi::Number::New(env, settingSrc.cntrlType));
        settingDst.Set(Napi::String::New(env, "dataType"), Napi::Number::New(env, settingSrc.settingDataType));

        if (settingSrc.currValue)
        {
            if (settingSrc.settingDataType == DataType::settingShort)
            {
                settingDst.Set(Napi::String::New(env, "currValue"), Napi::Number::New(env, *((unsigned short *)settingSrc.currValue)));
            }
            else if (settingSrc.settingDataType == DataType::settingString)
            {
                settingDst.Set(Napi::String::New(env, "currValue"), Napi::String::New(env, (char *)settingSrc.currValue));
            }
            else
            {
                LOG_ERROR_(LOGINSTANCE) << "Device " << deviceId << " has unexpected settingDataType " << settingSrc.currValue << " for settings GUID " << settingSrc.guid;
            }
        }

        settingDst.Set(Napi::String::New(env, "groupName"), Napi::String::New(env, settingSrc.groupName ? settingSrc.groupName : ""));
        settingDst.Set(Napi::String::New(env, "groupHelpText"), Napi::String::New(env, settingSrc.groupHelpText ? settingSrc.groupHelpText : ""));
        settingDst.Set(Napi::String::New(env, "isDepedentsetting"), Napi::Boolean::New(env, settingSrc.isDepedentsetting));

        if (settingSrc.dependentDefaultValue)
        {
            if (settingSrc.settingDataType == DataType::settingShort)
            {
                unsigned short* pValue = (unsigned short *)settingSrc.dependentDefaultValue;
                settingDst.Set(Napi::String::New(env, "dependentDefaultValue"), Napi::Number::New(env, pValue ? *pValue : 0));
            }
            else if (settingSrc.settingDataType == DataType::settingString)
            {
                settingDst.Set(Napi::String::New(env, "dependentDefaultValue"), Napi::String::New(env,  (char *)settingSrc.dependentDefaultValue ? (char *)settingSrc.dependentDefaultValue : ""));
            }
            else
            {
                LOG_ERROR_(LOGINSTANCE) << "Device " << deviceId << " has unexpected settingDataType " << settingSrc.settingDataType << " for settings GUID " << settingSrc.guid;
            }
        }

        Napi::Array keyValueList = Napi::Array::New(env, settingSrc.listSize);
        settingDst.Set(Napi::String::New(env, "listSize"), Napi::Number::New(env, settingSrc.listSize));
        for (int j=0; j< settingSrc.listSize; ++j)
        {
            ListKeyValue& listKeyValueSrc = settingSrc.listKeyValue[j];

            Napi::Object listKeyValueDst = Napi::Object::New(env);
            listKeyValueDst.Set(Napi::String::New(env, "key"), Napi::Number::New(env, listKeyValueSrc.key));

            if (listKeyValueSrc.value)
            {
                listKeyValueDst.Set(Napi::String::New(env, "value"), Napi::String::New(env, (char *)listKeyValueSrc.value ? (char *)listKeyValueSrc.value : ""));
            }

            listKeyValueDst.Set(Napi::String::New(env, "dependentcount"), Napi::Number::New(env, listKeyValueSrc.dependentcount));

            Napi::Array dependenciesList = Napi::Array::New(env, listKeyValueSrc.dependentcount);
            for (int k=0; k< listKeyValueSrc.dependentcount; ++k)
            {
                DependencySetting& dependencySettingSrc = listKeyValueSrc.dependents[k];

                Napi::Object dependencySettingDst = Napi::Object::New(env);
                dependencySettingDst.Set(Napi::String::New(env, "GUID"), Napi::String::New(env, dependencySettingSrc.GUID ? dependencySettingSrc.GUID : ""));
                dependencySettingDst.Set(Napi::String::New(env, "enableFlag"), Napi::Boolean::New(env, dependencySettingSrc.enableFlag));

                dependenciesList.Set(k, dependencySettingDst);
            }

            listKeyValueDst.Set(Napi::String::New(env, "dependents"), dependenciesList);
            keyValueList.Set(j, listKeyValueDst);
        }

        settingDst.Set(Napi::String::New(env, "listKeyValue"), keyValueList);
        settings.Set(i, settingDst);
    }

    dest.Set(Napi::String::New(env, "listSettingInfo"), settings);
}


static DeviceSettings *toCType(const unsigned short deviceId, Napi::Object src)
{
    DeviceSettings * result = new DeviceSettings();

    Napi::Array settingInfo = src.Get("listSettingInfo").As<Napi::Array>();
    if (!settingInfo.IsArray())
    {
        result->settingCount = 0;
        result->settingInfo = nullptr;
        return result;
    }

    result->settingCount = NS_HELP::getObjInt32OrDefault(src, "settingsCount", settingInfo.Length());
    result->settingInfo = new SettingInfo[result->settingCount];

    for (unsigned int i=0; i<result->settingCount; ++i)
    {
        SettingInfo& settingDst = result->settingInfo[i];
        Napi::Object settingSrc = settingInfo.Get(i).ToObject();

        settingDst.guid = NS_HELP::NewCString(settingSrc.Get("settingGuid"));
        settingDst.name = NS_HELP::NewCString(settingSrc.Get("name"));
        settingDst.helpText = NS_HELP::NewCString(settingSrc.Get("helpText"));
        settingDst.isValidationSupport = NS_HELP::NewCString(settingSrc.Get("isValidationSupport"));

        Napi::Value validationRuleSrc = settingSrc.Get("validationRule");
        if (validationRuleSrc.IsObject())
        {
            Napi::Object validationRuleSrcObj = validationRuleSrc.As<Napi::Object>();
            settingDst.validationRule = new ValidationRule();
            settingDst.validationRule->maxLength = NS_HELP::getObjInt32OrDefault(validationRuleSrcObj, "maxLength", INT_MAX);
            settingDst.validationRule->minLength = NS_HELP::getObjInt32OrDefault(validationRuleSrcObj, "minLength", 0);
            settingDst.validationRule->regExp = NS_HELP::NewCString(validationRuleSrcObj.Get("regExp"));
        }
        else
        {
            settingDst.validationRule = nullptr;
        }

        settingDst.isDeviceRestart = NS_HELP::getObjBooleanOrDefault(settingSrc, "isDeviceRestart", false);
        settingDst.isSettingProtected = NS_HELP::getObjBooleanOrDefault(settingSrc, "isSettingProtected", false);
        settingDst.isSettingProtectionEnabled = NS_HELP::getObjBooleanOrDefault(settingSrc, "isSettingProtectionEnabled", false);
        settingDst.cntrlType = NS_HELP::getObjEnumValueOrDefault<ControlType>(settingSrc, "cntrlType", ControlType::cntrlUnknown);
        settingDst.settingDataType = NS_HELP::getObjEnumValueOrDefault<DataType>(settingSrc, "dataType", DataType::settingShort);

        if (settingSrc.Has("currValue"))
        {
            if (settingDst.settingDataType == DataType::settingShort)
            {
                settingDst.currValue = new unsigned short[1] { (unsigned short)(uint8_t)NS_HELP::getObjInt32OrDefault(settingSrc, "currValue", 0) };
            }
            else if (settingDst.settingDataType == DataType::settingString)
            {
                settingDst.currValue = NS_HELP::NewCString(settingSrc.Get("currValue"));
            }
            else
            {
                LOG_ERROR_(LOGINSTANCE) << "Device " << deviceId << " has unexpected settingDataType " << settingDst.currValue << " for settings GUID " << settingDst.guid;
                settingDst.currValue = nullptr;
            }
        }
        else
        {
            settingDst.currValue = nullptr;
        }

        settingDst.groupName = NS_HELP::NewCString(settingSrc.Get("groupName"));
        settingDst.groupHelpText = NS_HELP::NewCString(settingSrc.Get("groupHelpText"));
        settingDst.isDepedentsetting = NS_HELP::getObjBooleanOrDefault(settingSrc, "isDepedentsetting", false);

        if (settingSrc.Has("dependentDefaultValue"))
        {
            if (settingDst.settingDataType == DataType::settingShort)
            {
                settingDst.dependentDefaultValue = new char[1] { (char)(uint8_t)NS_HELP::getObjInt32OrDefault(settingSrc, "dependentDefaultValue", 0) };
            }
            else if (settingDst.settingDataType == DataType::settingString)
            {
                settingDst.dependentDefaultValue = NS_HELP::NewCString(settingSrc.Get("dependentDefaultValue"));
            }
            else
            {
                LOG_ERROR_(LOGINSTANCE) << "Device " << deviceId << " has unexpected settingDataType " << settingDst.currValue << " for settings GUID " << settingDst.guid;
                settingDst.dependentDefaultValue = nullptr;
            }
        }
        else
        {
            settingDst.dependentDefaultValue = nullptr;
        }

        Napi::Array listKeyValueAry = settingSrc.Get("listKeyValue").As<Napi::Array>();
        if (listKeyValueAry.IsArray())
        {
            settingDst.listSize = NS_HELP::getObjInt32OrDefault(settingSrc, "listSize", listKeyValueAry.Length());
            settingDst.listKeyValue = new ListKeyValue[settingDst.listSize];

            for (int j=0; j<settingDst.listSize; ++j)
            {
                ListKeyValue& listKeyValueDst = settingDst.listKeyValue[j];
                Napi::Object listKeyValueSrcObj = listKeyValueAry.Get(j).As<Napi::Object>();

                listKeyValueDst.key = NS_HELP::getObjInt32OrDefault(listKeyValueSrcObj, "key", 0);
                listKeyValueDst.value = NS_HELP::NewCString(listKeyValueSrcObj.Get("value"));

                Napi::Array dependentsSrc = listKeyValueSrcObj.Get("dependents").As<Napi::Array>();
                if (dependentsSrc.IsArray())
                {
                    listKeyValueDst.dependentcount = NS_HELP::getObjInt32OrDefault(listKeyValueSrcObj, "dependentcount", dependentsSrc.Length());
                    listKeyValueDst.dependents = new DependencySetting[listKeyValueDst.dependentcount];

                    for (int k=0; k<listKeyValueDst.dependentcount; ++k)
                    {
                        DependencySetting& dependencySettingDst = listKeyValueDst.dependents[k];
                        Napi::Object dependencySettingSrc = dependentsSrc.Get(k).As<Napi::Object>();

                        dependencySettingDst.GUID = NS_HELP::NewCString(dependencySettingSrc.Get("GUID"));
                        dependencySettingDst.enableFlag = NS_HELP::getObjBooleanOrDefault(dependencySettingSrc, "enableFlag", false);
                    }
                }
                else
                {
                    listKeyValueDst.dependentcount = 0;
                    listKeyValueDst.dependents = nullptr;
                }
            }
        }
        else
        {
            settingDst.listSize = 0;
            settingDst.listKeyValue = nullptr;
        }
    }

    return result;
}

static std::string toString(DeviceSettings * src) {
    std::ostringstream buffer;

    buffer << "{ ";
    buffer << "\"settingInfo\": [ ";

    for (unsigned int i=0; i<src->settingCount; ++i) {
        SettingInfo& settingSrc = src->settingInfo[i];
        if (i>0)  {
            buffer << ",";
        }

        buffer << "{ ";
        buffer << "\"guid\": \"" << (settingSrc.guid ? settingSrc.guid : "") << "\", ";
        buffer << "\"name\": \"" << (settingSrc.name ? settingSrc.name : "") << "\", ";
        buffer << "\"helpText\": \"" << (settingSrc.helpText ? settingSrc.helpText : "") << "\", ";
        buffer << "\"isValidationSupport\": " << (settingSrc.isValidationSupport ? "true" : "false") << ", ";

        ValidationRule *validationRuleSrc = settingSrc.validationRule;
        if (validationRuleSrc) {
            buffer << "\"validationRule\": { ";
            buffer << "\"minLength\": " << validationRuleSrc->minLength << ", ";
            buffer << "\"maxLength\": " << validationRuleSrc->maxLength << ", ";
            buffer << "\"regExp\": \"" << (validationRuleSrc->regExp ? validationRuleSrc->regExp : "") << "\" " ;
            buffer << "}, " << std::endl;
        }


        buffer << "\"isDeviceRestart\": " << (settingSrc.isDeviceRestart ? "true" : "false") << ", ";
        buffer << "\"isSettingProtected\": " << (settingSrc.isSettingProtected ? "true" : "false") << ", ";
        buffer << "\"isSettingProtectionEnabled\": " << (settingSrc.isSettingProtectionEnabled ? "true" : "false") << ", ";
        buffer << "\"cntrlType\": " << settingSrc.cntrlType << ", ";

        if (settingSrc.currValue) {
            if (settingSrc.settingDataType == DataType::settingShort) {
                buffer << "\"currValue\": " << std::to_string(*((unsigned short *)settingSrc.currValue)) << ", ";
            } else if (settingSrc.settingDataType == DataType::settingString) {
                buffer << "\"currValue\": \"" << (char *)settingSrc.currValue << "\", ";
            } else {
                buffer << "\"currValue\": " << "ERROR: unexpected datatype " << settingSrc.currValue << ", ";
            }
        }

        buffer << "\"settingDataType\": " << settingSrc.settingDataType << ", ";
        buffer << "\"groupName\": \"" << (settingSrc.groupName ? settingSrc.groupName : "") << "\", ";
        buffer << "\"groupHelpText\": \"" << (settingSrc.groupHelpText ? settingSrc.groupHelpText : "") << "\", ";
        buffer << "\"isDepedentsetting\": " << (settingSrc.isDepedentsetting ? "true" : "false") << ", ";

        if (settingSrc.dependentDefaultValue) {
            if (settingSrc.settingDataType == DataType::settingShort) {
                buffer << "\"dependentDefaultValue\": " << std::to_string(*((uint8_t *)settingSrc.dependentDefaultValue)) << ", ";
            } else if (settingSrc.settingDataType == DataType::settingString) {
                buffer << "\"dependentDefaultValue\": \"" << (char *)settingSrc.dependentDefaultValue << "\", ";
            } else {
                buffer << "\"dependentDefaultValue\": " << "ERROR: unexpected datatype " << settingSrc.settingDataType << ", ";
            }
        }

        buffer << "\"listSize\": " << settingSrc.listSize << ", ";
        buffer << "\"listKeyValue\": [ ";
        for (int j=0; j< settingSrc.listSize; ++j) {
            if (j>0)  {
                buffer << ",";
            }

            buffer << "{ ";

            ListKeyValue& listKeyValueSrc = settingSrc.listKeyValue[j];

            buffer << "\"key\": " << listKeyValueSrc.key << ", ";

            if (listKeyValueSrc.value) {
                buffer << "\"value\": \"" <<  (char *)listKeyValueSrc.value << "\", ";
            }

            buffer << "\"dependentcount\": " << listKeyValueSrc.dependentcount << ", ";
            buffer << "\"dependents\": [ ";
            for (int k=0; k< listKeyValueSrc.dependentcount; ++k) {
                if (k>0)  {
                    buffer << ",";
                }

                DependencySetting& dependencySettingSrc = listKeyValueSrc.dependents[k];
                buffer << "{ ";
                buffer << "\"GUID\": \"" << (dependencySettingSrc.GUID ? dependencySettingSrc.GUID : "") << "\", ";
                buffer << "\"enableFlag\": " << (dependencySettingSrc.enableFlag ? "true" : "false") << " ";
                buffer << "}";
            }
            buffer << "]";

            buffer << "}";
        }
        buffer << "]";

        buffer << "}";
    }

    buffer << "]"; // settingInfo

    buffer << "}"; // top level object

    return buffer.str();
}

Napi::Value napi_GetSetting(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 strGuid = objInfo[1].As<Napi::String>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    auto *const pWoreker = new AsyncWorker<DeviceSettings *, Napi::Object>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, strGuid]() -> DeviceSettings * {
        DeviceSettings * const pSettings = Yealink_GetSetting(uDevId, strGuid.c_str());
        if (!pSettings)
        {
            YLExcetion::LogAndThrow(pFuncName, "null returned");
        }
        return pSettings;
    },
    [uDevId](const Napi::Env& env, DeviceSettings * const pSettings) {
        Napi::Object objResult = Napi::Object::New(env);
        if (pSettings)
        {
            toNodeType(uDevId, pSettings, objResult);
        }
        return objResult;
    },
    [](DeviceSettings * pSettings) {
        if (pSettings)
        {
            Yealink_FreeDeviceSettings(pSettings);
        }
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_GetSettings(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<DeviceSettings *, Napi::Object>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId]() -> DeviceSettings * {
        DeviceSettings * const pSettings = Yealink_GetSettings(uDevId);
        if (!pSettings)
        {
            YLExcetion::LogAndThrow(pFuncName, "null returned");
        }
        VERBOSE_TRACE << toString(pSettings);
        return pSettings;
    },
    [uDevId](const Napi::Env& env, DeviceSettings * const pSettings) {
        Napi::Object objResult = Napi::Object::New(env);
        if (pSettings)
        {
            toNodeType(uDevId, pSettings, objResult);
        }
        return objResult;
    },
    [](DeviceSettings * pSettings) {
        if (pSettings)
        {
            Yealink_FreeDeviceSettings(pSettings);
        }
    });

    if (pWoreker)
    {
        pWoreker->Queue();
    }

    return env.Undefined();
}

Napi::Value napi_SetSettings(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 objSettings = objInfo[1].As<Napi::Object>();
    Napi::Function funcJsResultCallback = objInfo[2].As<Napi::Function>();

    DeviceSettings * const pSettings = toCType(uDevId, objSettings);

    auto *const pWoreker = new AsyncWorker<void, void>(
        pFuncName,
        funcJsResultCallback,
    [pFuncName, uDevId, pSettings]() {
        VERBOSE_TRACE << toString(pSettings);
        Yealink_ReturnCode eRetCode = Yealink_SetSettings(uDevId, pSettings);
        if (Return_Ok != eRetCode)
        {
            YLReturnCodeException::LogAndThrow(pFuncName, eRetCode);
        }
    },
    [pSettings]() {
        if (pSettings)
        {
            Custom_FreeDeviceSettings(pSettings);
        }
    });

    if (pWoreker)
        pWoreker->Queue();

    return env.Undefined();
}
