/**
 * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved.
 *
 * 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.
 */

#include "datasystem/common/flags/flag_manager.h"

#include <cerrno>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <mutex>
#include <securec.h>
#include <stdarg.h>
#include <string>
#include <type_traits>
#include <utility>

#include <securec.h>

#include "datasystem/common/flags/string_to_long.h"
#include "datasystem/common/util/format.h"
#include "datasystem/utils/embedded_config.h"
#include "datasystem/utils/kv_client_config.h"

DS_DECLARE_bool(help);
DS_DECLARE_bool(version);

#define ugly_exit exit

#define TREAT_VALUE_AS(type, value) (*reinterpret_cast<type *>(value))

#define TREAT_VALIDATOR_AS(type, validator, name, value) \
    (reinterpret_cast<bool (*)(const char *, type)>(validator)(name, TREAT_VALUE_AS(const type, value)))

namespace datasystem {
const int K_DOUBLE_STR_PRECISION = 15;
char one[2] = { '1', '\0' };

void ReportError(const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    vfprintf(stdout, format, ap);
    va_end(ap);
    fflush(stdout);
}

bool Flag::Assign(const std::string &value, std::string &errMsg)
{
    bool success;
    switch (type_) {
        case FLAG_BOOL:
            success = ParseAssignFromBoolean(value, errMsg);
            break;
        case FLAG_UINT32:
            success = ParseAssignFromUint32(value, errMsg);
            break;
        case FLAG_INT32:
            success = ParseAssignFromInt32(value, errMsg);
            break;
        case FLAG_UINT64:
            success = ParseAssignFromUint64(value, errMsg);
            break;
        case FLAG_INT64:
            success = ParseAssignFromInt64(value, errMsg);
            break;
        case FLAG_STRING:
            success = ParseAssignFromString(value, errMsg);
            break;
        case FLAG_DOUBLE:
            success = ParseAssignFromDouble(value, errMsg);
            break;
        default:
            success = false;
    }

    if (success) {
        wasSpecified_ = true;
    }
    return success;
}

bool Flag::IsValidate(const void *value) const
{
    if (validator_ == nullptr) {
        return true;
    }

    value = value != nullptr ? value : currentVal_;
    switch (type_) {
        case FLAG_BOOL:
            return TREAT_VALIDATOR_AS(bool, validator_, name_.c_str(), value);
        case FLAG_UINT32:
            return TREAT_VALIDATOR_AS(uint32_t, validator_, name_.c_str(), value);
        case FLAG_INT32:
            return TREAT_VALIDATOR_AS(int32_t, validator_, name_.c_str(), value);
        case FLAG_UINT64:
            return TREAT_VALIDATOR_AS(uint64_t, validator_, name_.c_str(), value);
        case FLAG_INT64:
            return TREAT_VALIDATOR_AS(int64_t, validator_, name_.c_str(), value);
        case FLAG_STRING:
            return TREAT_VALIDATOR_AS(std::string, validator_, name_.c_str(), value);
        case FLAG_DOUBLE:
            return TREAT_VALIDATOR_AS(double, validator_, name_.c_str(), value);
        default:
            return false;
    }
}

std::string Flag::TypeName() const
{
    switch (type_) {
        case FLAG_BOOL:
            return "bool";
        case FLAG_UINT32:
            return "uint32";
        case FLAG_INT32:
            return "int32";
        case FLAG_UINT64:
            return "uint64";
        case FLAG_INT64:
            return "int64";
        case FLAG_STRING:
            return "string";
        case FLAG_DOUBLE:
            return "double";
        default:
            return "Unknown";
    }
}

std::string Flag::ValueString() const
{
    switch (type_) {
        case FLAG_BOOL:
            return std::to_string(TREAT_VALUE_AS(bool, currentVal_));
        case FLAG_UINT32:
            return std::to_string(TREAT_VALUE_AS(uint32_t, currentVal_));
        case FLAG_INT32:
            return std::to_string(TREAT_VALUE_AS(int32_t, currentVal_));
        case FLAG_UINT64:
            return std::to_string(TREAT_VALUE_AS(uint64_t, currentVal_));
        case FLAG_INT64:
            return std::to_string(TREAT_VALUE_AS(int64_t, currentVal_));
        case FLAG_STRING:
            return TREAT_VALUE_AS(std::string, currentVal_);
        case FLAG_DOUBLE: {
            char buf[32];
            sprintf_s(buf, sizeof(buf), "%.15g", TREAT_VALUE_AS(double, currentVal_));
            return std::string(buf);
        }
        default:
            return "Unknown";
    }
}

void Flag::UpdateModified()
{
    switch (type_) {
        case FLAG_BOOL:
            modified_ = (TREAT_VALUE_AS(bool, currentVal_) != TREAT_VALUE_AS(bool, defaultVal_));
            break;
        case FLAG_UINT32:
            modified_ = (TREAT_VALUE_AS(uint32_t, currentVal_) != TREAT_VALUE_AS(uint32_t, defaultVal_));
            break;
        case FLAG_INT32:
            modified_ = (TREAT_VALUE_AS(int32_t, currentVal_) != TREAT_VALUE_AS(int32_t, defaultVal_));
            break;
        case FLAG_UINT64:
            modified_ = (TREAT_VALUE_AS(uint64_t, currentVal_) != TREAT_VALUE_AS(uint64_t, defaultVal_));
            break;
        case FLAG_INT64:
            modified_ = (TREAT_VALUE_AS(int64_t, currentVal_) != TREAT_VALUE_AS(int64_t, defaultVal_));
            break;
        case FLAG_STRING:
            modified_ = (TREAT_VALUE_AS(std::string, currentVal_) != TREAT_VALUE_AS(std::string, defaultVal_));
            break;
        case FLAG_DOUBLE:
            modified_ = (fabs(TREAT_VALUE_AS(double, currentVal_) - TREAT_VALUE_AS(double, defaultVal_)) > 1e-12);
            break;
        default:
            break;
    }
}

namespace {
const char *kTrue[] = { "1", "t", "true", "y", "yes" };
const char *kFalse[] = { "0", "f", "false", "n", "no" };

bool HasOnlyTrailingSpaces(const std::string &value, std::size_t pos)
{
    while (pos < value.size()) {
        if (!std::isspace(static_cast<unsigned char>(value[pos]))) {
            return false;
        }
        ++pos;
    }
    return true;
}

template <typename T>
struct AlwaysFalse : public std::false_type {};

template <typename IntegerType>
bool ParseIntegerStrict(const std::string &value, IntegerType &res)
{
    std::size_t pos = 0;
    if constexpr (std::is_same_v<IntegerType, uint32_t>) {
        unsigned long parsed = StrToUnsignedLong(value, &pos);
        res = static_cast<uint32_t>(parsed);
        if (static_cast<unsigned long>(res) != parsed) {
            return false;
        }
    } else if constexpr (std::is_same_v<IntegerType, int32_t>) {
        res = std::stoi(value, &pos);
    } else if constexpr (std::is_same_v<IntegerType, uint64_t>) {
        res = StrToUnsignedLongLong(value, &pos);
    } else if constexpr (std::is_same_v<IntegerType, int64_t>) {
        res = std::stoll(value, &pos);
    } else {
        static_assert(AlwaysFalse<IntegerType>::value, "Unsupported integer type");
    }
    return HasOnlyTrailingSpaces(value, pos);
}
}  // namespace

bool Flag::ParseAssignFromBoolean(const std::string &value, std::string &errMsg)
{
    static_assert(sizeof(kTrue) == sizeof(kFalse), "True false equal");
    if (value.empty()) {
        errMsg = IllegalValueMessage(value);
        return false;
    }
    bool res = false;
    bool success = false;
    for (size_t i = 0; i < sizeof(kTrue) / sizeof(*kTrue); ++i) {
        if (strcasecmp(value.c_str(), kTrue[i]) == 0) {
            res = true;
            success = true;
            break;
        } else if (strcasecmp(value.c_str(), kFalse[i]) == 0) {
            res = false;
            success = true;
            break;
        }
    }

    if (!success) {
        errMsg = IllegalValueMessage(value);
        return false;
    }

    success = IsValidate(reinterpret_cast<const void *>(&res));
    if (success) {
        TREAT_VALUE_AS(bool, currentVal_) = res;
        UpdateModified();
    } else {
        errMsg = ValidateFailureMessage();
    }
    return success;
}

bool Flag::ParseAssignFromUint32(const std::string &value, std::string &errMsg)
{
    if (value.empty() || value[0] == '-') {
        errMsg = IllegalValueMessage(value);
        return false;
    }

    uint32_t res;
    bool success = false;
    try {
        success = ParseIntegerStrict<uint32_t>(value, res);
    } catch (std::invalid_argument &e) {
        success = false;
    } catch (const std::out_of_range &e) {
        success = false;
    }

    if (!success) {
        errMsg = IllegalValueMessage(value);
        return false;
    }

    success = IsValidate(reinterpret_cast<const void *>(&res));
    if (success) {
        TREAT_VALUE_AS(uint32_t, currentVal_) = res;
        UpdateModified();
    } else {
        errMsg = ValidateFailureMessage();
    }
    return success;
}

bool Flag::ParseAssignFromInt32(const std::string &value, std::string &errMsg)
{
    if (value.empty()) {
        errMsg = IllegalValueMessage(value);
        return false;
    }

    int32_t res;
    bool success = false;
    try {
        success = ParseIntegerStrict<int32_t>(value, res);
    } catch (std::invalid_argument &e) {
        success = false;
    } catch (const std::out_of_range &e) {
        success = false;
    }

    if (!success) {
        errMsg = IllegalValueMessage(value);
        return false;
    }

    success = IsValidate(reinterpret_cast<const void *>(&res));
    if (success) {
        TREAT_VALUE_AS(int32_t, currentVal_) = res;
        UpdateModified();
    } else {
        errMsg = ValidateFailureMessage();
    }
    return success;
}

bool Flag::ParseAssignFromUint64(const std::string &value, std::string &errMsg)
{
    if (value.empty() || value[0] == '-') {
        errMsg = IllegalValueMessage(value);
        return false;
    }

    uint64_t res;
    bool success = false;
    try {
        success = ParseIntegerStrict<uint64_t>(value, res);
    } catch (std::invalid_argument &e) {
        success = false;
    } catch (const std::out_of_range &e) {
        success = false;
    }

    if (!success) {
        errMsg = IllegalValueMessage(value);
        return false;
    }

    success = IsValidate(reinterpret_cast<const void *>(&res));
    if (success) {
        TREAT_VALUE_AS(uint64_t, currentVal_) = res;
        UpdateModified();
    } else {
        errMsg = ValidateFailureMessage();
    }
    return success;
}

bool Flag::ParseAssignFromInt64(const std::string &value, std::string &errMsg)
{
    if (value.empty()) {
        errMsg = IllegalValueMessage(value);
        return false;
    }

    int64_t res;
    bool success = false;
    try {
        success = ParseIntegerStrict<int64_t>(value, res);
    } catch (std::invalid_argument &e) {
        success = false;
    } catch (const std::out_of_range &e) {
        success = false;
    }

    if (!success) {
        errMsg = IllegalValueMessage(value);
        return false;
    }

    success = IsValidate(reinterpret_cast<const void *>(&res));
    if (success) {
        TREAT_VALUE_AS(int64_t, currentVal_) = res;
        UpdateModified();
    } else {
        errMsg = ValidateFailureMessage();
    }
    return success;
}

bool Flag::ParseAssignFromString(const std::string &value, std::string &errMsg)
{
    bool success = IsValidate(static_cast<const void *>(&value));
    if (success) {
        TREAT_VALUE_AS(std::string, currentVal_) = value;
        UpdateModified();
    } else {
        errMsg = ValidateFailureMessage();
    }
    return success;
}

bool Flag::ParseAssignFromDouble(const std::string &value, std::string &errMsg)
{
    char *end;
    errno = 0;
    double parsed = strtod(value.c_str(), &end);
if (errno != 0 || end == value.c_str() || *end != '\0' || !std::isfinite(parsed)) {
        errMsg = IllegalValueMessage(value);
        return false;
    }
    bool success = IsValidate(static_cast<const void *>(&parsed));
    if (success) {
        TREAT_VALUE_AS(double, currentVal_) = parsed;
        UpdateModified();
    } else {
        errMsg = ValidateFailureMessage();
    }
    return success;
}

std::string Flag::IllegalValueMessage(const std::string &value) const
{
    return "Error: illegal value '" + value + "' specified for " + TypeName() + " flag '" + name_ + "' \n";
}

std::string Flag::ValidateFailureMessage() const
{
    return "Error: failed validation of new value '" + ValueString() + "' for flag '" + name_ + "'\n";
}

FlagManager *FlagManager::GetInstance()
{
    static FlagManager manager;
    return &manager;
}

bool FlagManager::ParseCommandLineFlagsFromArgsLocked(const std::unordered_map<std::string, std::string> &args,
                                                      std::string &errMsg)
{
    errorFlags_.clear();
    unknownFlags_.clear();
    for (const auto &flagKv : args) {
        if (flagKv.first == "connectTimeoutMs") {
            continue;
        }
        auto it = flagMap_.find(flagKv.first);
        if (it == flagMap_.end()) {
            (void)unknownFlags_.emplace(flagKv.first, "ERROR: unknown command line flag '" + flagKv.first + "'\n");
            continue;
        }
        auto &flag = it->second;
        if (flagKv.second.empty()) {
            errorFlags_[flagKv.first] =
                "Error: flag '" + flagKv.first + "' is missing its argument; flag description: " + flag.meaning_ + "\n";
            continue;
        }
        (void)AssignFlagValue(flag, flagKv.second);
    }

    ValidateDefaultFlagsLocked();

    if (CheckAndReportErrors(errMsg)) {
        return false;
    }
    return true;
}

bool FlagManager::ParseCommandLineFlags(const std::unordered_map<std::string, std::string> &args, std::string &errMsg)
{
    std::lock_guard<std::mutex> l(mutex_);
    return ParseCommandLineFlagsFromArgsLocked(args, errMsg);
}

bool FlagManager::ParseCommandLineFlags(const EmbeddedConfig &config, std::string &errMsg)
{
    std::lock_guard<std::mutex> l(mutex_);
    return ParseCommandLineFlagsFromArgsLocked(config.GetArgs(), errMsg);
}

bool FlagManager::ParseCommandLineFlags(const KVClientConfig &config, std::string &errMsg)
{
    std::lock_guard<std::mutex> l(mutex_);
    return ParseCommandLineFlagsFromArgsLocked(config.GetArgs(), errMsg);
}

void FlagManager::ParseCommandLineFlags(int argc, char **argv)
{
    if (argc == 0 || argv == nullptr || *argv == nullptr) {
        ReportError("Illegal command line arguments");
        ugly_exit(1);
    }

    argv0_ = argv[0];
    std::lock_guard<std::mutex> l(mutex_);
    for (int i = 1; i < argc; ++i) {
        auto *arg = argv[i];

        // We always thought that
        if (arg[0] != '-' || arg[1] == '\0') {
            continue;
        }
        arg++;
        if (arg[0] == '-') {
            arg++;
        }
        if (arg[0] == '\0') {
            break;
        }

        std::string key;
        char *value = nullptr;
        ParseFlagValue(arg, key, &value);

        auto it = flagMap_.find(key);
        if (it == flagMap_.end()) {
            // Unknown flag, report error.
            (void)unknownFlags_.emplace(key, "ERROR: unknown command line flag '" + key + "'\n");
            continue;
        }

        auto &flag = it->second;
        if (value == nullptr) {
            if (i + 1 >= argc) {
                // Value absent, we treat it as an unrecoverable error.
                errorFlags_[key] =
                    "Error: flag '" + key + "' is missing its argument; flag description: " + flag.meaning_ + "\n";
                break;
            } else {
                value = argv[i + 1];
                ++i;
            }
        }

        std::string errMsg;
        (void)AssignFlagValue(flag, value);
    }

    // 2. If help or version flag is set, we need to output the help message and exit ugly.
    if (NeedPrintHelpfulMessage(argv[0])) {
        ugly_exit(1);
    }

    // 3. Validate the default value.
    ValidateDefaultFlagsLocked();

    // 4. Report error and exit if need.
    std::string unUseErrMsg;
    if (CheckAndReportErrors(unUseErrMsg)) {
        ugly_exit(1);
    }
}

std::string FlagManager::ProgramInvocationShortName()
{
    size_t pos = argv0_.rfind('/');
    return (pos == std::string::npos ? argv0_ : std::string(argv0_.c_str() + pos + 1));
}

void FlagManager::GetAllFlags(std::vector<FlagInfo> &output) const
{
    std::lock_guard<std::mutex> l(mutex_);
    for (const auto &entry : flagMap_) {
        const auto &flag = entry.second;
        FlagInfo info{ .type = flag.TypeName(),
                       .name = flag.name_,
                       .meaning = flag.meaning_,
                       .filename = flag.filename_,
                       .value = flag.ValueString(),
                       .isDefault = !flag.IsModified(),
                       .wasSpecified = flag.wasSpecified_ };
        output.emplace_back(std::move(info));
    }
}

bool FlagManager::WasFlagSpecified(const char *name) const
{
    if (name == nullptr) {
        return false;
    }
    std::lock_guard<std::mutex> l(mutex_);
    auto it = flagMap_.find(name);
    if (it == flagMap_.end()) {
        return false;
    }
    return it->second.wasSpecified_;
}

bool FlagManager::IsModifiableFlag(const std::string &name) const
{
    std::lock_guard<std::mutex> l(mutex_);
    auto it = flagMap_.find(name);
    if (it == flagMap_.end()) {
        return false;
    }
    return it->second.modifiable_;
}

struct FlagManager::FlagValueSnapshot {
    bool wasSpecified{false};
    bool modified{false};
    bool boolVal{false};
    uint32_t uint32Val{0};
    int32_t int32Val{0};
    uint64_t uint64Val{0};
    int64_t int64Val{0};
    std::string stringVal;
    double doubleVal{0.0};
};

bool FlagManager::readCurrentIntoSnapshot(const Flag &flag, FlagValueSnapshot &snapshot, const std::string &name,
                                          std::string &errMsg) const
{
    snapshot.wasSpecified = flag.wasSpecified_;
    snapshot.modified = flag.modified_;
    switch (flag.type_) {
        case FLAG_BOOL:
            snapshot.boolVal = TREAT_VALUE_AS(bool, flag.currentVal_);
            return true;
        case FLAG_UINT32:
            snapshot.uint32Val = TREAT_VALUE_AS(uint32_t, flag.currentVal_);
            return true;
        case FLAG_INT32:
            snapshot.int32Val = TREAT_VALUE_AS(int32_t, flag.currentVal_);
            return true;
        case FLAG_UINT64:
            snapshot.uint64Val = TREAT_VALUE_AS(uint64_t, flag.currentVal_);
            return true;
        case FLAG_INT64:
            snapshot.int64Val = TREAT_VALUE_AS(int64_t, flag.currentVal_);
            return true;
        case FLAG_STRING:
            snapshot.stringVal = TREAT_VALUE_AS(std::string, flag.currentVal_);
            return true;
        case FLAG_DOUBLE:
            snapshot.doubleVal = TREAT_VALUE_AS(double, flag.currentVal_);
            return true;
        default:
            errMsg = "flag '" + name + "' has unsupported type";
            return false;
    }
}

void FlagManager::writeSnapshotIntoFlag(Flag &flag, const FlagValueSnapshot &snapshot) const
{
    switch (flag.type_) {
        case FLAG_BOOL:
            TREAT_VALUE_AS(bool, flag.currentVal_) = snapshot.boolVal;
            break;
        case FLAG_UINT32:
            TREAT_VALUE_AS(uint32_t, flag.currentVal_) = snapshot.uint32Val;
            break;
        case FLAG_INT32:
            TREAT_VALUE_AS(int32_t, flag.currentVal_) = snapshot.int32Val;
            break;
        case FLAG_UINT64:
            TREAT_VALUE_AS(uint64_t, flag.currentVal_) = snapshot.uint64Val;
            break;
        case FLAG_INT64:
            TREAT_VALUE_AS(int64_t, flag.currentVal_) = snapshot.int64Val;
            break;
        case FLAG_STRING:
            TREAT_VALUE_AS(std::string, flag.currentVal_) = snapshot.stringVal;
            break;
        case FLAG_DOUBLE:
            TREAT_VALUE_AS(double, flag.currentVal_) = snapshot.doubleVal;
            break;
        default:
            break;
    }
    flag.wasSpecified_ = snapshot.wasSpecified;
    flag.modified_ = snapshot.modified;
}

bool FlagManager::tryAssignWithRollback(Flag &flag, const std::string &newVal, std::string &errMsg)
{
    FlagValueSnapshot snapshot;
    if (!readCurrentIntoSnapshot(flag, snapshot, flag.name_, errMsg)) {
        return false;
    }
    if (!flag.Assign(newVal, errMsg)) {
        return false;
    }
    writeSnapshotIntoFlag(flag, snapshot);
    return true;
}

bool FlagManager::ValidateChange(const std::string &name, const std::string &newVal, std::string &errMsg)
{
    std::lock_guard<std::mutex> l(mutex_);
    auto it = flagMap_.find(name);
    if (it == flagMap_.end()) {
        errMsg = "flag '" + name + "' not found!";
        return false;
    }
    auto &flag = it->second;
    if (!flag.modifiable_) {
        errMsg = "flag '" + name + "' not modifiable";
        return false;
    }
    return tryAssignWithRollback(flag, newVal, errMsg);
}

void FlagManager::GetModifiableFlagNames(std::vector<std::string> &out) const
{
    out.clear();
    std::lock_guard<std::mutex> l(mutex_);
    for (const auto &entry : flagMap_) {
        if (entry.second.modifiable_) {
            out.emplace_back(entry.first);
        }
    }
}

void FlagManager::SetVersionString(const std::string &version)
{
    version_ = version;
}

void FlagManager::SetUsageMessage(const std::string &description)
{
    description_ = description;
}

void FlagManager::RegisterFlag(const std::string &name, FlagType type, const std::string &meaning,
                               const std::string &filename, void *currentVal, void *defaultVal, bool modifiable)
{
    Flag flag(type, name, meaning, filename, currentVal, defaultVal, modifiable);
    std::lock_guard<std::mutex> l(mutex_);
    auto pair = flagMap_.emplace(name, flag);
    if (!pair.second) {
        const auto &existedFlag = pair.first->second;
        if (flag.filename_ != pair.first->second.filename_) {
            ReportError("ERROR: flag '%s' was defined more than once (inf file '%s' and '%s')\n", name.c_str(),
                        flag.filename_.c_str(), pair.first->second.filename_.c_str());
        } else {
            if (existedFlag.currentVal_ == currentVal) {
                return;
            }
            ReportError(
                "ERROR: register flag '%s' in file '%s' meets error. One possibility: file '%s' is being linked both "
                "staticallyand dynamically into this executable.\n",
                name.c_str(), flag.filename_.c_str(), flag.filename_.c_str());
        }
        ugly_exit(1);
    }
    (void)flagPtrMap_.emplace(currentVal, &pair.first->second);
}

bool FlagManager::RegisterValidator(void *flag, void *func)
{
    std::lock_guard<std::mutex> l(mutex_);
    auto it = flagPtrMap_.find(flag);
    if (it == flagPtrMap_.end()) {
        ReportError("WARNING: Ignore register validator for flag: no flag found\n");
        return false;
    } else if (func == it->second->validator_) {
        return true;
    } else if (func != nullptr && it->second->validator_ != nullptr) {
        ReportError("WARNING: Ignore register validator for flag %s: validator already registered\n",
                    it->second->name_.c_str());
        return false;
    } else {
        it->second->validator_ = func;
        if (it->second->currentVal_ != nullptr) {
            it->second->IsValidate(it->second->currentVal_);
        }
        return true;
    }
}

bool FlagManager::FindAndAssignFlagValue(const char *name, const std::string &value, std::string &errMsg)
{
    if (name == nullptr) {
        errMsg = "name or value is nullptr";
        return false;
    }

    std::lock_guard<std::mutex> l(mutex_);
    auto it = flagMap_.find(name);
    if (it == flagMap_.end()) {
        errMsg = "flag '" + std::string(name) + "' not found!";
        return false;
    }

    auto &flag = it->second;
    return flag.Assign(value, errMsg);
}

bool FlagManager::FindAndGetFlagValue(const char *name, std::string &output)
{
    if (name == nullptr) {
        return false;
    }

    std::lock_guard<std::mutex> l(mutex_);
    auto it = flagMap_.find(name);
    if (it == flagMap_.end()) {
        return false;
    }

    output = it->second.ValueString();
    return true;
}

bool FlagManager::NeedPrintHelpfulMessage(const char *argv0)
{
    if (!FLAGS_help && !FLAGS_version) {
        return false;
    }

    const char *basename = std::strrchr(argv0, '/');
    basename = basename == nullptr ? argv0 : basename + 1;
    std::stringstream ss;
    ss << std::string(basename) << ": " << description_;

    if (FLAGS_help) {
        ss << "\n";
        for (const auto &entry : flagMap_) {
            const auto &flag = entry.second;
            ss << "  -" << flag.name_ << " (" << flag.meaning_ << ")"
               << "\n   type: " << flag.TypeName() << " default: ";
            if (flag.TypeName() == "string") {
                ss << "\"" << flag.ValueString() << "\"\n";
            } else {
                ss << flag.ValueString() << "\n";
            }
        }
    } else if (FLAGS_version) {
        ss << " version: " << version_ << "\n";
    }
    ReportError("%s", ss.str().c_str());
    return true;
}

void FlagManager::ValidateDefaultFlagsLocked()
{
    for (auto it = flagMap_.begin(); it != flagMap_.end(); ++it) {
        auto &flag = it->second;
        if (flag.IsModified()) {
            continue;
        }
        if (!errorFlags_[flag.name_].empty()) {
            continue;
        }
        if (flag.IsValidate()) {
            continue;
        }
        errorFlags_[flag.name_] = "Error: failed validation of default value for flag '" + flag.name_ + "'\n";
    }
}

void FlagManager::ParseFlagValue(char *arg, std::string &flag, char **value)
{
    auto *res = std::strchr(arg, '=');
    if (res == nullptr) {
        flag = arg;
    } else {
        flag.assign(arg, res - arg);
        *value = res + 1;
    }

    // If we found help flag, it means that we need to print the
    // help message, so let's set it as true to let it work.
    if (flag == "help" || flag == "version") {
        *value = one;
    }
}

void FlagManager::AssignFlagValue(Flag &flag, const std::string &value)
{
    std::string errMsg;
    bool success = flag.Assign(value, errMsg);
    if (!success) {
        errorFlags_[flag.name_] =
            "Error: illegal value '" + value + "' specified for " + flag.TypeName() + " flag '" + flag.name_ + "' \n";
    }
}

bool FlagManager::CheckAndReportErrors(std::string &errMsg) const
{
    bool error = false;
    std::stringstream ss;
    for (const auto &entry : errorFlags_) {
        if (!entry.second.empty()) {
            ss << entry.second;
            error = true;
        }
    }

    for (const auto &entry : unknownFlags_) {
        if (!entry.second.empty()) {
            ss << entry.second;
            error = true;
        }
    }
    errMsg = ss.str();
    if (error) {
        ReportError("%s", ss.str().c_str());
    }
    return error;
}
}  // namespace datasystem