/**
 *    Copyright (C) 2018-present MongoDB, Inc.
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the Server Side Public License, version 1,
 *    as published by MongoDB, Inc.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    Server Side Public License for more details.
 *
 *    You should have received a copy of the Server Side Public License
 *    along with this program. If not, see
 *    <http://www.mongodb.com/licensing/server-side-public-license>.
 *
 *    As a special exception, the copyright holders give permission to link the
 *    code of portions of this program with the OpenSSL library under certain
 *    conditions as described in each individual source file and distribute
 *    linked combinations including the program with the OpenSSL library. You
 *    must comply with the Server Side Public License in all respects for
 *    all of the code used other than as permitted herein. If you modify file(s)
 *    with this exception, you may extend this exception to your version of the
 *    file(s), but you are not obligated to do so. If you do not wish to do so,
 *    delete this exception statement from your version. If you delete this
 *    exception statement from all source files in the program, then also delete
 *    it in the license file.
 */

#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kControl

#include "mongo/idl/server_parameter.h"

#include "mongo/logv2/log.h"

namespace mongo {
using SPT = ServerParameterType;

MONGO_INITIALIZER_GROUP(BeginServerParameterRegistration, (), ("EndServerParameterRegistration"))
MONGO_INITIALIZER_GROUP(EndServerParameterRegistration,
                        ("BeginServerParameterRegistration"),
                        ("BeginStartupOptionHandling"))

ServerParameter::ServerParameter(StringData name, ServerParameterType spt)
    : ServerParameter(ServerParameterSet::getGlobal(),
                      name,
                      spt != SPT::kRuntimeOnly,
                      spt != SPT::kStartupOnly) {}

ServerParameter::ServerParameter(ServerParameterSet* sps,
                                 StringData name,
                                 bool allowedToChangeAtStartup,
                                 bool allowedToChangeAtRuntime)
    : _name(name.toString()),
      _allowedToChangeAtStartup(allowedToChangeAtStartup),
      _allowedToChangeAtRuntime(allowedToChangeAtRuntime) {
    if (sps) {
        sps->add(this);
    }
}

ServerParameter::ServerParameter(ServerParameterSet* sps, StringData name)
    : _name(name.toString()), _allowedToChangeAtStartup(true), _allowedToChangeAtRuntime(true) {
    if (sps) {
        sps->add(this);
    }
}

namespace {
ServerParameterSet* gGlobalServerParameterSet = nullptr;
}  // namespace

ServerParameterSet* ServerParameterSet::getGlobal() {
    if (!gGlobalServerParameterSet) {
        gGlobalServerParameterSet = new ServerParameterSet();
    }
    return gGlobalServerParameterSet;
}

void ServerParameterSet::add(ServerParameter* sp) {
    ServerParameter*& x = _map[sp->name()];
    if (x) {
        LOGV2_FATAL(23784,
                    "'{name}' already exists in the server parameter set",
                    "Duplicate server parameter registration",
                    "name"_attr = x->name());
    }
    x = sp;
}

StatusWith<std::string> ServerParameter::coerceToString(const BSONElement& element, bool redact) {
    switch (element.type()) {
        case NumberDouble:
            return std::to_string(element.Double());
        case String:
            return element.String();
        case NumberInt:
            return std::to_string(element.Int());
        case NumberLong:
            return std::to_string(element.Long());
        case Date:
            return dateToISOStringLocal(element.Date());
        default:
            std::string diag;
            if (redact) {
                diag = "###";
            } else {
                diag = element.toString();
            }
            return {ErrorCodes::BadValue,
                    str::stream() << "Unsupported type " << typeName(element.type()) << " (value: '"
                                  << diag << "') for setParameter: " << name()};
    }
}

void ServerParameterSet::remove(const std::string& name) {
    invariant(1 == _map.erase(name));
}

IDLServerParameterDeprecatedAlias::IDLServerParameterDeprecatedAlias(StringData name,
                                                                     ServerParameter* sp)
    : ServerParameter(ServerParameterSet::getGlobal(),
                      name,
                      sp->allowedToChangeAtStartup(),
                      sp->allowedToChangeAtRuntime()),
      _sp(sp) {
    if (_sp->isTestOnly()) {
        setTestOnly();
    }
}

void IDLServerParameterDeprecatedAlias::append(OperationContext* opCtx,
                                               BSONObjBuilder& b,
                                               const std::string& fieldName) {
    std::call_once(_warnOnce, [&] {
        LOGV2_WARNING(23781,
                      "Use of deprecated server parameter '{deprecatedName}', "
                      "please use '{canonicalName}' instead",
                      "Use of deprecated server parameter name",
                      "deprecatedName"_attr = name(),
                      "canonicalName"_attr = _sp->name());
    });
    _sp->append(opCtx, b, fieldName);
}

Status IDLServerParameterDeprecatedAlias::set(const BSONElement& newValueElement) {
    std::call_once(_warnOnce, [&] {
        LOGV2_WARNING(23782,
                      "Use of deprecated server parameter '{deprecatedName}', "
                      "please use '{canonicalName}' instead",
                      "Use of deprecated server parameter name",
                      "deprecatedName"_attr = name(),
                      "canonicalName"_attr = _sp->name());
    });
    return _sp->set(newValueElement);
}

Status IDLServerParameterDeprecatedAlias::setFromString(const std::string& str) {
    std::call_once(_warnOnce, [&] {
        LOGV2_WARNING(23783,
                      "Use of deprecated server parameter '{deprecatedName}', "
                      "please use '{canonicalName}' instead",
                      "Use of deprecated server parameter name",
                      "deprecatedName"_attr = name(),
                      "canonicalName"_attr = _sp->name());
    });
    return _sp->setFromString(str);
}

namespace {
class DisabledTestParameter : public ServerParameter {
public:
    DisabledTestParameter() = delete;

    DisabledTestParameter(ServerParameter* sp)
        : ServerParameter(
              nullptr, sp->name(), sp->allowedToChangeAtStartup(), sp->allowedToChangeAtRuntime()),
          _sp(sp) {
        setTestOnly();
    }

    void append(OperationContext* opCtx, BSONObjBuilder& b, const std::string& name) final {}

    Status setFromString(const std::string&) final {
        return {ErrorCodes::BadValue,
                str::stream() << "setParameter: '" << name()
                              << "' is only supported with 'enableTestCommands=true'"};
    }

    Status set(const BSONElement& newValueElement) final {
        return setFromString("");
    }

private:
    // Retain the original pointer to avoid ASAN complaining.
    ServerParameter* _sp;
};
}  // namespace

void ServerParameterSet::disableTestParameters() {
    for (auto& spit : _map) {
        auto*& sp = spit.second;
        if (sp->isTestOnly()) {
            sp = new DisabledTestParameter(sp);
        }
    }
}

}  // namespace mongo
