/******************************************************************************
 *
 *  Copyright (C) 2011-2012 Broadcom Corporation
 *  Copyright 2018-2019, 2023 NXP
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/
//#define LOG_NDEBUG 0
#define LOG_TAG "NxpUwbConf"

#include <limits.h>
#include <sys/stat.h>

#include <iomanip>
#include <list>
#include <memory>
#include <sstream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include <android-base/logging.h>
#include <cutils/properties.h>
#include <log/log.h>

#include "phNxpConfig.h"
#include "phNxpUciHal.h"
#include "phNxpUciHal_ext.h"
#include "phNxpUciHal_utils.h"
#include "phNxpLog.h"

static const char default_nxp_config_path[] = "/vendor/etc/libuwb-nxp.conf";
static const char country_code_config_name[] = "libuwb-countrycode.conf";
static const char nxp_uci_config_file[] = "libuwb-uci.conf";
static const char default_uci_config_path[] = "/vendor/etc/";

static const char country_code_specifier[] = "<country>";
static const char sku_specifier[] = "<sku>";
static const char extid_specifier[] = "<extid>";
static const char revision_specifier[] = "<revision>";

static const char extid_config_name[] = "cal.extid";
static const char extid_default_value[] = "defaultextid";

static const char prop_name_calsku[] = "persist.vendor.uwb.cal.sku";
static const char prop_default_calsku[] = "defaultsku";

static const char prop_name_revision[] = "persist.vendor.uwb.cal.revision";
static const char prop_default_revision[] = "defaultrevision";

using namespace::std;

class uwbParam
{
public:
    enum class type { STRING, NUMBER, BYTEARRAY, STRINGARRAY };
    uwbParam();
    uwbParam(const uwbParam& param);
    uwbParam(uwbParam&& param);

    uwbParam(const string& value);
    uwbParam(vector<uint8_t>&& value);
    uwbParam(unsigned long value);
    uwbParam(vector<string>&& value);

    virtual ~uwbParam();

    type getType() const { return m_type; }
    unsigned long numValue() const {return m_numValue;}
    const char*   str_value() const {return m_str_value.c_str();}
    size_t        str_len() const   {return m_str_value.length();}
    const uint8_t* arr_value() const { return m_arrValue.data(); }
    size_t arr_len() const { return m_arrValue.size(); }

    size_t str_arr_len() const { return m_arrStrValue.size(); }
    const char* str_arr_elem(const int index) const { return m_arrStrValue[index].c_str(); }
    size_t str_arr_elem_len(const int index) const { return m_arrStrValue[index].length(); }

    void dump(const string &tag) const;
private:
    unsigned long   m_numValue;
    string          m_str_value;
    vector<uint8_t>  m_arrValue;
    vector<string>  m_arrStrValue;
    type m_type;
};

class CUwbNxpConfig
{
public:
    CUwbNxpConfig();
    CUwbNxpConfig(CUwbNxpConfig&& config);
    CUwbNxpConfig(const char *filepath);
    virtual ~CUwbNxpConfig();
    CUwbNxpConfig& operator=(CUwbNxpConfig&& config);

    bool open(const char *filepath);
    bool isValid() const { return mValidFile; }
    void reset() {
        m_map.clear();
        mValidFile = false;
    }

    const uwbParam*    find(const char* p_name) const;
    void    setCountry(const string& strCountry);
    const char* getFilePath() const {
        return mFilePath.c_str();
    }

    void    dump() const;

    const unordered_map<string, uwbParam>& get_data() const {
        return m_map;
    }
private:
    bool    readConfig();

    unordered_map<string, uwbParam> m_map;
    bool    mValidFile;
    string  mFilePath;
};

/*******************************************************************************
**
** Function:    isPrintable()
**
** Description: determine if 'c' is printable
**
** Returns:     1, if printable, otherwise 0
**
*******************************************************************************/
static inline bool isPrintable(char c)
{
    return  (c >= 'A' && c <= 'Z') ||
            (c >= 'a' && c <= 'z') ||
            (c >= '0' && c <= '9') ||
            c == '/' || c == '_' || c == '-' || c == '.' || c == ',';
}

/*******************************************************************************
**
** Function:    isDigit()
**
** Description: determine if 'c' is numeral digit
**
** Returns:     true, if numerical digit
**
*******************************************************************************/
static inline bool isDigit(char c, int base)
{
    if (base == 10) {
        return isdigit(c);
    } else if (base == 16) {
        return isxdigit(c);
    } else {
        return false;
    }
}

static inline bool isArrayDelimeter(char c)
{
    return (isspace(c) || c== ',' || c == ':' || c == '-' || c == '}');
}

/*******************************************************************************
**
** Function:    getDigitValue()
**
** Description: return numerical value of a decimal or hex char
**
** Returns:     numerical value if decimal or hex char, otherwise 0
**
*******************************************************************************/
inline int getDigitValue(char c, int base)
{
    if ('0' <= c && c <= '9')
        return c - '0';
    if (base == 16)
    {
        if ('A' <= c && c <= 'F')
            return c - 'A' + 10;
        else if ('a' <= c && c <= 'f')
            return c - 'a' + 10;
    }
    return 0;
}

/*******************************************************************************
**
** Function:    CUwbNxpConfig::readConfig()
**
** Description: read Config settings and parse them into a linked list
**              move the element from linked list to a array at the end
**
** Returns:     1, if there are any config data, 0 otherwise
**
*******************************************************************************/
bool CUwbNxpConfig::readConfig()
{
    enum {
        BEGIN_LINE = 1,
        TOKEN,
        STR_VALUE,
        NUM_VALUE,
        ARR_SPACE,
        ARR_STR,
        ARR_STR_SPACE,
        ARR_NUM,
        BEGIN_HEX,
        BEGIN_QUOTE,
        END_LINE
    };

    FILE*   fd;
    string  token;
    string  strValue;
    unsigned long    numValue = 0;
    vector<uint8_t> arrValue;
    vector<string> arrStr;
    int     base = 0;
    int     c;
    const char *name = mFilePath.c_str();
    unsigned long state = BEGIN_LINE;

    mValidFile = false;
    m_map.clear();

    /* open config file, read it into a buffer */
    if ((fd = fopen(name, "r")) == NULL)
    {
        ALOGV("Extra calibration file %s failed to open.", name);
        return false;
    }
    ALOGV("%s Opened config %s\n", __func__, name);

    for (;;) {
        c = fgetc(fd);

        switch (state) {
        case BEGIN_LINE:
            if (isPrintable(c)) {
                token.clear();
                numValue = 0;
                strValue.clear();
                arrValue.clear();
                arrStr.clear();
                state = TOKEN;
                token.push_back(c);
            } else {
                state = END_LINE;
            }
            break;
        case TOKEN:
            if (c == '=') {
                state = BEGIN_QUOTE;
            } else if (isPrintable(c)) {
                token.push_back(c);
            } else {
                state = END_LINE;
            }
            break;
        case BEGIN_QUOTE:
            if (c == '"') {
                state = STR_VALUE;
                base = 0;
            } else if (c == '0') {
                state = BEGIN_HEX;
            } else if (isDigit(c, 10)) {
                state = NUM_VALUE;
                base = 10;
                numValue = getDigitValue(c, base);
            } else if (c == '{') {
                state = ARR_SPACE;
                base = 16;
            } else {
                state = END_LINE;
            }
            break;
        case BEGIN_HEX:
            if (c == 'x' || c == 'X') {
                state = NUM_VALUE;
                base = 16;
                numValue = 0;
            } else if (isDigit(c, 10)) {
                state = NUM_VALUE;
                base = 10;
                numValue = getDigitValue(c, base);
            } else {
                m_map.try_emplace(token, move(uwbParam(numValue)));
                state = END_LINE;
            }
            break;
        case NUM_VALUE:
            if (isDigit(c, base)) {
                numValue *= base;
                numValue += getDigitValue(c, base);
            } else {m_map.try_emplace(token, move(uwbParam(numValue)));
                state = END_LINE;
            }
            break;
        case ARR_SPACE:
            if (isDigit(c, 16)) {
                numValue = getDigitValue(c, base);
                state = ARR_NUM;
            } else if (c == '}') {
                m_map.try_emplace(token, move(uwbParam(move(arrValue))));
                state = END_LINE;
            } else if (c == '"') {
                state = ARR_STR;
            } else if (c == EOF) {
                state = END_LINE;
            }
            break;
        case ARR_STR:
            if (c == '"') {
                arrStr.emplace_back(move(strValue));
                strValue.clear();
                state = ARR_STR_SPACE;
            } else {
                strValue.push_back(c);
            }
            break;
        case ARR_STR_SPACE:
            if (c == '}') {
                m_map.try_emplace(token, move(uwbParam(move(arrStr))));
                state = END_LINE;
            } else if (c == '"') {
                state = ARR_STR;
            }
            break;
        case ARR_NUM:
            if (isDigit(c, 16)) {
                numValue *= 16;
                numValue += getDigitValue(c, base);
            } else if (isArrayDelimeter(c)) {
                arrValue.push_back(numValue & 0xff);
                state = ARR_SPACE;
            } else {
                state = END_LINE;
            }
            if (c == '}') {
                m_map.try_emplace(token, move(uwbParam(move(arrValue))));
                state = END_LINE;
            }
            break;
        case STR_VALUE:
            if (c == '"') {
                state = END_LINE;
                m_map.try_emplace(token, move(uwbParam(strValue)));
            } else {
                strValue.push_back(c);
            }
            break;
        case END_LINE:
            // do nothing
        default:
            break;
        }
        if (c == EOF)
            break;
        else if (state == END_LINE && (c == '\n' || c == '\r'))
            state = BEGIN_LINE;
        else if (c == '#')
            state = END_LINE;
    }

    fclose(fd);

    if (m_map.size() > 0) {
        mValidFile = true;
        ALOGI("Extra calibration file %s opened.", name);
    }

    return mValidFile;
}

/*******************************************************************************
**
** Function:    CUwbNxpConfig::CUwbNxpConfig()
**
** Description: class constructor
**
** Returns:     none
**
*******************************************************************************/
CUwbNxpConfig::CUwbNxpConfig() :
    mValidFile(false)
{
}

/*******************************************************************************
**
** Function:    CUwbNxpConfig::~CUwbNxpConfig()
**
** Description: class destructor
**
** Returns:     none
**
*******************************************************************************/
CUwbNxpConfig::~CUwbNxpConfig()
{
}

CUwbNxpConfig::CUwbNxpConfig(const char *filepath)
{
    open(filepath);
}

CUwbNxpConfig::CUwbNxpConfig(CUwbNxpConfig&& config)
{
    m_map = move(config.m_map);
    mValidFile = config.mValidFile;
    mFilePath = move(config.mFilePath);

    config.mValidFile = false;
}

CUwbNxpConfig& CUwbNxpConfig::operator=(CUwbNxpConfig&& config)
{
    m_map = move(config.m_map);
    mValidFile = config.mValidFile;
    mFilePath = move(config.mFilePath);

    config.mValidFile = false;
    return *this;
}

bool CUwbNxpConfig::open(const char *filepath)
{
    mValidFile = false;
    mFilePath = filepath;

    return readConfig();
}

/*******************************************************************************
**
** Function:    CUwbNxpConfig::find()
**
** Description: search if a setting exist in the setting array
**
** Returns:     pointer to the setting object
**
*******************************************************************************/
const uwbParam* CUwbNxpConfig::find(const char* p_name) const
{
    const auto it = m_map.find(p_name);

    if (it == m_map.cend()) {
        return NULL;
    }
    return &it->second;
}

/*******************************************************************************
**
** Function:    CUwbNxpConfig::dump()
**
** Description: prints all elements in the list
**
** Returns:     none
**
*******************************************************************************/
void CUwbNxpConfig::dump() const
{
    ALOGV("Dump configuration file %s : %s, %zu entries", mFilePath.c_str(),
        mValidFile ? "valid" : "invalid", m_map.size());

    for (auto &it : m_map) {
        auto &key = it.first;
        auto &param = it.second;
        param.dump(key);
    }
}

/*******************************************************************************/
uwbParam::uwbParam() :
    m_numValue(0),
    m_type(type::NUMBER)
{
}

uwbParam::~uwbParam()
{
}

uwbParam::uwbParam(const uwbParam &param) :
    m_numValue(param.m_numValue),
    m_str_value(param.m_str_value),
    m_arrValue(param.m_arrValue),
    m_arrStrValue(param.m_arrStrValue),
    m_type(param.m_type)
{
}

uwbParam::uwbParam(uwbParam &&param) :
    m_numValue(param.m_numValue),
    m_str_value(move(param.m_str_value)),
    m_arrValue(move(param.m_arrValue)),
    m_arrStrValue(move(param.m_arrStrValue)),
    m_type(param.m_type)
{
}

uwbParam::uwbParam(const string& value) :
    m_numValue(0),
    m_str_value(value),
    m_type(type::STRING)
{
}

uwbParam::uwbParam(unsigned long value) :
    m_numValue(value),
    m_type(type::NUMBER)
{
}

uwbParam::uwbParam(vector<uint8_t> &&value) :
    m_arrValue(move(value)),
    m_type(type::BYTEARRAY)
{
}

uwbParam::uwbParam(vector<string> &&value) :
    m_arrStrValue(move(value)),
    m_type(type::STRINGARRAY)
{
}


void uwbParam::dump(const string &tag) const
{
    if (m_type == type::NUMBER) {
        ALOGV(" - %s = 0x%lx", tag.c_str(), m_numValue);
    } else if (m_type == type::STRING) {
        ALOGV(" - %s = %s", tag.c_str(), m_str_value.c_str());
    } else if (m_type == type::BYTEARRAY) {
        stringstream ss_hex;
        ss_hex.fill('0');
        for (auto b : m_arrValue) {
            ss_hex << setw(2) << hex << (int)b << " ";
        }
        ALOGV(" - %s = { %s}", tag.c_str(), ss_hex.str().c_str());
    } else if (m_type == type::STRINGARRAY) {
        stringstream ss;
        for (auto s : m_arrStrValue) {
            ss << "\"" << s << "\", ";
        }
        ALOGV(" - %s = { %s}", tag.c_str(), ss.str().c_str());
    }
}
/*******************************************************************************/
class RegionCodeMap {
public:
    void loadMapping(const char *filepath) {
        CUwbNxpConfig config(filepath);
        if (!config.isValid()) {
            ALOGW("Region mapping was not provided.");
            return;
        }

        ALOGI("Region mapping was provided by %s", filepath);
        auto &all_params = config.get_data();
        for (auto &it : all_params) {
            const auto &region_str = it.first;
            const uwbParam *param = &it.second;

            // split space-separated strings into set
            stringstream ss(param->str_value());
            string cc;
            unordered_set<string> cc_set;
            while (ss >> cc) {
              if (cc.length() == 2 && isupper(cc[0]) && isupper(cc[1])) {
                cc_set.emplace(move(cc));
              }
            }
            auto result = m_map.try_emplace(region_str, move(cc_set));
            if (!result.second) {
              // region conlifct : merge
              result.first->second.merge(move(cc_set));
            }
        }
        m_config = move(config);
    }
    string xlateCountryCode(const char country_code[2]) {
        string code{country_code[0], country_code[1]};
        if (m_config.isValid()) {
            for (auto &it : m_map) {
                const auto &region_str = it.first;
                const auto &cc_set = it.second;
                if (cc_set.find(code) != cc_set.end()) {
                    ALOGV("map country code %c%c --> %s",
                            country_code[0], country_code[1], region_str.c_str());
                    return region_str;
                }
            }
        }
        return code;
    }
    void reset() {
        m_config.reset();
        m_map.clear();
    }
    void dump() {
        ALOGV("Region mapping dump:");
        for (auto &entry : m_map) {
            const auto &region_str = entry.first;
            const auto &cc_set = entry.second;
            stringstream ss;
            for (const auto s : cc_set) {
                ss << "\"" << s << "\", ";
            }
            ALOGV("- %s = { %s}", region_str.c_str(), ss.str().c_str());
        }
    }
private:
    CUwbNxpConfig m_config;
    unordered_map<string, unordered_set<string>> m_map;
};

/*******************************************************************************/
class CascadeConfig {
public:
    CascadeConfig();

    void init(const char *main_config);
    void deinit();
    bool setCountryCode(const char country_code[2]);

    const uwbParam* find(const char *name)  const;
    bool    getValue(const char* name, char* pValue, size_t len) const;
    bool    getValue(const char* name, unsigned long& rValue) const;
    bool    getValue(const char* name, uint8_t* pValue, long len, long* readlen) const;
private:
    // default_nxp_config_path
    CUwbNxpConfig mMainConfig;

    // uci config
    CUwbNxpConfig mUciConfig;

    // EXTRA_CONF_PATH[N]
    std::vector<std::pair<string, CUwbNxpConfig>> mExtraConfig;

    // [COUNTRY_CODE_CAP_FILE_LOCATION]/country_code_config_name
    CUwbNxpConfig mCapsConfig;

    // Region Code mapping
    RegionCodeMap mRegionMap;

    // current set of specifiers for EXTRA_CONF_PATH[]
    struct ExtraConfPathSpecifiers {
        string mCurSku;
        string mCurExtid;
        string mCurRegionCode;
        string mCurRevision;
        void reset() {
            mCurSku.clear();
            mCurExtid.clear();
            mCurRegionCode.clear();
            mCurRevision.clear();
        }
    };
    ExtraConfPathSpecifiers mExtraConfSpecifiers;

    // Re-evaluate filepaths of mExtraConfig with mExtraConfSpecifiers, and re-load them.
    // returns true if any of entries were updated.
    bool evaluateExtraConfPaths();

    void dump() {
        mMainConfig.dump();
        mUciConfig.dump();

        for (const auto &[filename, config] : mExtraConfig)
            config.dump();

        mCapsConfig.dump();
        mRegionMap.dump();
    }
};

CascadeConfig::CascadeConfig()
{
}

bool CascadeConfig::evaluateExtraConfPaths()
{
    bool updated = false;

    for (auto& [filename, config] : mExtraConfig) {
        std::string new_filename(filename);

        auto posSku = filename.find(sku_specifier);
        if (posSku != std::string::npos && !mExtraConfSpecifiers.mCurSku.empty()) {
            new_filename.replace(posSku, strlen(sku_specifier), mExtraConfSpecifiers.mCurSku);
        }

        auto posExtid = filename.find(extid_specifier);
        if (posExtid != std::string::npos && !mExtraConfSpecifiers.mCurExtid.empty()) {
            new_filename.replace(posExtid, strlen(extid_specifier), mExtraConfSpecifiers.mCurExtid);
        }

        auto posCountry = filename.find(country_code_specifier);
        if (posCountry != std::string::npos && !mExtraConfSpecifiers.mCurRegionCode.empty()) {
            new_filename.replace(posCountry, strlen(country_code_specifier), mExtraConfSpecifiers.mCurRegionCode);
        }

        auto posRevision = filename.find(revision_specifier);
        if (posRevision != std::string::npos && !mExtraConfSpecifiers.mCurRevision.empty()) {
            new_filename.replace(posRevision, strlen(revision_specifier), mExtraConfSpecifiers.mCurRevision);
        }

        // re-open the file if filepath got re-evaluated.
        if (new_filename != config.getFilePath()) {
            config.open(new_filename.c_str());
            updated = true;
        }
    }
    return updated;
}

void CascadeConfig::init(const char *main_config)
{
    ALOGV("CascadeConfig initialize with %s", main_config);

    // Main config file
    CUwbNxpConfig config(main_config);
    if (!config.isValid()) {
        ALOGW("Failed to load main config file");
        return;
    }
    mMainConfig = move(config);

    {
        // UCI config file
        std::string uciConfigFilePath = default_uci_config_path;
        uciConfigFilePath += nxp_uci_config_file;

        CUwbNxpConfig config(uciConfigFilePath.c_str());
        if (!config.isValid()) {
            ALOGW("Failed to load uci config file:%s",
                    uciConfigFilePath.c_str());
        } else {
            mUciConfig = move(config);
        }
    }

    char sku_value[PROPERTY_VALUE_MAX];
    char revision_value[PROPERTY_VALUE_MAX];
    property_get(prop_name_calsku, sku_value, prop_default_calsku);
    property_get(prop_name_revision, revision_value, prop_default_revision);

    // Read EXTRA_CONF_PATH[N]
    for (int i = 1; i <= 10; i++) {
        char key[32];
        snprintf(key, sizeof(key), "EXTRA_CONF_PATH_%d", i);
        const uwbParam *param = mMainConfig.find(key);
        if (!param)
            continue;

        std::string filename(param->str_value());

        auto entry = std::make_pair(param->str_value(), CUwbNxpConfig(filename.c_str()));
        mExtraConfig.emplace_back(std::move(entry));
    }

    // evaluate <sku> and <revision>
    mExtraConfSpecifiers.mCurSku = sku_value;
    mExtraConfSpecifiers.mCurRevision = revision_value;
    evaluateExtraConfPaths();

    // re-evaluate with "<extid>"
    char extid_value[PROPERTY_VALUE_MAX];
    if (!NxpConfig_GetStr(extid_config_name, extid_value, sizeof(extid_value))) {
        strcpy(extid_value, extid_default_value);
    }
    mExtraConfSpecifiers.mCurExtid = extid_value;
    evaluateExtraConfPaths();

    ALOGI("Provided specifiers: sku=[%s] revision=[%s] extid=[%s]", sku_value, revision_value, extid_value);

    // Pick one libuwb-countrycode.conf with the highest VERSION number
    // from multiple directories specified by COUNTRY_CODE_CAP_FILE_LOCATION
    unsigned long arrLen = 0;
    if (NxpConfig_GetStrArrayLen(NAME_COUNTRY_CODE_CAP_FILE_LOCATION, &arrLen) && arrLen > 0) {
        const long loc_max_len = 260;
        auto loc = make_unique<char[]>(loc_max_len);
        int version, max_version = -1;
        string strPickedPath;
        bool foundCapFile = false;
        CUwbNxpConfig pickedConfig;

        for (int i = 0; i < arrLen; i++) {
            if (!NxpConfig_GetStrArrayVal(NAME_COUNTRY_CODE_CAP_FILE_LOCATION, i, loc.get(), loc_max_len)) {
                continue;
            }
            string strPath(loc.get());
            strPath += country_code_config_name;

            ALOGV("Try to load %s", strPath.c_str());

            CUwbNxpConfig config(strPath.c_str());

            const uwbParam *param = config.find(NAME_NXP_COUNTRY_CODE_VERSION);
            version = param ? atoi(param->str_value()) : -2;
            if (version > max_version) {
                foundCapFile = true;
                pickedConfig = move(config);
                strPickedPath = move(strPath);
                max_version = version;
            }
        }
        if (foundCapFile) {
            mCapsConfig = move(pickedConfig);
            ALOGI("CountryCodeCaps file %s loaded with VERSION=%d", strPickedPath.c_str(), max_version);
        } else {
            ALOGI("No CountryCodeCaps specified");
        }
    } else {
        ALOGI(NAME_COUNTRY_CODE_CAP_FILE_LOCATION " was not specified, skip loading CountryCodeCaps");
    }

    // Load region mapping
    const uwbParam *param = find(NAME_REGION_MAP_PATH);
    if (param) {
        mRegionMap.loadMapping(param->str_value());
    }

    ALOGD("CascadeConfig initialized");

    dump();
}

void CascadeConfig::deinit()
{
    mMainConfig.reset();
    mExtraConfig.clear();
    mCapsConfig.reset();
    mRegionMap.reset();
    mUciConfig.reset();
    mExtraConfSpecifiers.reset();
}

bool CascadeConfig::setCountryCode(const char country_code[2])
{
    string strRegion = mRegionMap.xlateCountryCode(country_code);

    if (strRegion == mExtraConfSpecifiers.mCurRegionCode) {
        ALOGI("Same region code(%c%c --> %s), per-country configuration not updated.",
              country_code[0], country_code[1], strRegion.c_str());
        return false;
    }

    ALOGI("Apply country code %c%c --> %s\n", country_code[0], country_code[1], strRegion.c_str());
    mExtraConfSpecifiers.mCurRegionCode = strRegion;

    return evaluateExtraConfPaths();
}

const uwbParam* CascadeConfig::find(const char *name) const
{
    const uwbParam* param = NULL;

    param = mCapsConfig.find(name);
    if (param)
      return param;

    for (auto it = mExtraConfig.rbegin(); it != mExtraConfig.rend(); it++) {
        auto &config = it->second;
        param = config.find(name);
        if (param)
            break;
    }
    if (!param) {
        param = mMainConfig.find(name);
    }
    if (!param) {
        param = mUciConfig.find(name);
    }
    return param;
}

// TODO: move these getValue() helpers out of the class
bool CascadeConfig::getValue(const char* name, char* pValue, size_t len) const
{
    const uwbParam *param = find(name);
    if (!param)
        return false;
    if (param->getType() != uwbParam::type::STRING)
        return false;
    if (len < (param->str_len() + 1))
        return false;

    strncpy(pValue, param->str_value(), len);
    return true;
}

bool CascadeConfig::getValue(const char* name, uint8_t* pValue, long len, long* readlen) const
{
    const uwbParam *param = find(name);
    if (!param)
        return false;
    if (param->getType() != uwbParam::type::BYTEARRAY)
        return false;
    if (len < param->arr_len())
        return false;
    memcpy(pValue, param->arr_value(), param->arr_len());
    if (readlen)
        *readlen = param->arr_len();
    return true;
}

bool CascadeConfig::getValue(const char* name, unsigned long& rValue) const
{
    const uwbParam *param = find(name);
    if (!param)
        return false;
    if (param->getType() != uwbParam::type::NUMBER)
        return false;

    rValue = param->numValue();
    return true;
}

/*******************************************************************************/

static CascadeConfig gConfig;

void NxpConfig_Init(void)
{
    gConfig.init(default_nxp_config_path);
}

void NxpConfig_Deinit(void)
{
    gConfig.deinit();
}

// return true if new per-country configuration file was load.
//        false if it can stay at the current configuration.
bool NxpConfig_SetCountryCode(const char country_code[2])
{
    return gConfig.setCountryCode(country_code);
}

/*******************************************************************************
**
** Function:    NxpConfig_GetStr
**
** Description: API function for getting a string value of a setting
**
** Returns:     True if found, otherwise False.
**
*******************************************************************************/
int NxpConfig_GetStr(const char* name, char* pValue, unsigned long len)
{
    return gConfig.getValue(name, pValue, len);
}

/*******************************************************************************
**
** Function:    NxpConfig_GetByteArray()
**
** Description: Read byte array value from the config file.
**
** Parameters:
**              name    - name of the config param to read.
**              pValue  - pointer to input buffer.
**              bufflen - input buffer length.
**              len     - out parameter to return the number of bytes read from config file,
**                        return -1 in case bufflen is not enough.
**
** Returns:     TRUE[1] if config param name is found in the config file, else FALSE[0]
**
*******************************************************************************/
int NxpConfig_GetByteArray(const char* name, uint8_t* pValue, long bufflen, long *len)
{
    return gConfig.getValue(name, pValue, bufflen,len);
}

/*******************************************************************************
**
** Function:    NxpConfig_GetNum
**
** Description: API function for getting a numerical value of a setting
**
** Returns:     true, if successful
**
*******************************************************************************/
int NxpConfig_GetNum(const char* name, void* pValue, unsigned long len)
{
    if (pValue == NULL){
        return false;
    }
    const uwbParam* pParam = gConfig.find(name);

    if (pParam == NULL)
        return false;
    if (pParam->getType() != uwbParam::type::NUMBER)
        return false;

    unsigned long v = pParam->numValue();
    switch (len)
    {
    case sizeof(unsigned long):
        *(static_cast<unsigned long*>(pValue)) = (unsigned long)v;
        break;
    case sizeof(unsigned short):
        *(static_cast<unsigned short*>(pValue)) = (unsigned short)v;
        break;
    case sizeof(unsigned char):
        *(static_cast<unsigned char*> (pValue)) = (unsigned char)v;
        break;
    default:
        return false;
    }
    return true;
}

// Get the length of a 'string-array' type parameter
int NxpConfig_GetStrArrayLen(const char* name, unsigned long* pLen)
{
    const uwbParam* param = gConfig.find(name);
    if (!param || param->getType() != uwbParam::type::STRINGARRAY)
        return false;

    *pLen = param->str_arr_len();
    return true;
}

// Get a string value from 'string-array' type parameters, index zero-based
int NxpConfig_GetStrArrayVal(const char* name, int index, char* pValue, unsigned long len)
{
    const uwbParam* param = gConfig.find(name);
    if (!param || param->getType() != uwbParam::type::STRINGARRAY)
        return false;
    if (index < 0 || index >= param->str_arr_len())
        return false;

    if (len < param->str_arr_elem_len(index) + 1)
        return false;
    strncpy(pValue, param->str_arr_elem(index), len);
    return true;
}
