/* Copyright 2013 10gen Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    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
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    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 GNU Affero General 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.
 */

#include "mongo/util/options_parser/option_description.h"

#include "mongo/util/assert_util.h"

namespace mongo {
namespace optionenvironment {

    namespace {
        /**
         * Utility function check that the type of our Value matches our OptionType
         */
        Status checkValueType(OptionType type, Value value) {
            switch (type) {
                case StringVector:
                    {
                        std::vector<std::string>  valueType;
                        return value.get(&valueType);
                    }
                case Bool:
                    {
                        bool valueType;
                        return value.get(&valueType);
                    }
                case Double:
                    {
                        double valueType;
                        return value.get(&valueType);
                    }
                case Int:
                    {
                        int valueType;
                        return value.get(&valueType);
                    }
                case Long:
                    {
                        long valueType;
                        return value.get(&valueType);
                    }
                case String:
                    {
                        std::string valueType;
                        return value.get(&valueType);
                    }
                case UnsignedLongLong:
                    {
                        unsigned long long valueType;
                        return value.get(&valueType);
                    }
                case Unsigned:
                    {
                        unsigned valueType;
                        return value.get(&valueType);
                    }
                case Switch:
                    {
                        bool valueType;
                        return value.get(&valueType);
                    }
                default:
                    {
                        StringBuilder sb;
                        sb << "Unrecognized option type: " << type;
                        return Status(ErrorCodes::InternalError, sb.str());
                    }
            }
        }
    } // namespace

    OptionDescription& OptionDescription::hidden() {
        _isVisible = false;
        return *this;
    }

    OptionDescription& OptionDescription::setDefault(Value defaultValue) {

        // Disallow registering a default for a composing option since the interaction between the
        // two is unclear (for example, should we override or compose the default)
        if (_isComposing) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
               << "Cannot register a default value for a composing option";
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        // Make sure the type of our default value matches our declared type
        Status ret = checkValueType(_type, defaultValue);
        if (!ret.isOK()) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
            << "mismatch between declared type and type of default value: "
            << ret.toString();
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        // It doesn't make sense to set a "default value" for switch options, so disallow it here
        if (_type == Switch) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
                << "the default value of a Switch option is false and cannot be changed";
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        _default = defaultValue;
        return *this;
    }

    OptionDescription& OptionDescription::setImplicit(Value implicitValue) {

        // Disallow registering an implicit value for a composing option since the interaction
        // between the two is unclear
        if (_isComposing) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
               << "Cannot register an implicit value for a composing option";
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        // Make sure the type of our implicit value matches our declared type
        Status ret = checkValueType(_type, implicitValue);
        if (!ret.isOK()) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
            << "mismatch between declared type and type of implicit value: "
            << ret.toString();
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        // It doesn't make sense to set an "implicit value" for switch options since they can never
        // have an argument anyway, so disallow it here
        if (_type == Switch) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
                << "the implicit value of a Switch option is true and cannot be changed";
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        _implicit = implicitValue;
        return *this;
    }

    OptionDescription& OptionDescription::composing() {

        if (_type != StringVector && _type != StringMap) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
                << "only options registered as StringVector or StringMap can be composing";
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        // Disallow registering a default value for a composing option since the interaction
        // between the two is unclear
        if (!_default.isEmpty()) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
               << "Cannot make an option with an default value composing";
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        // Disallow registering an implicit value for a composing option since the interaction
        // between the two is unclear
        if (!_implicit.isEmpty()) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
               << "Cannot make an option with an implicit value composing";
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        _isComposing = true;
        return *this;
    }

    OptionDescription& OptionDescription::setSources(OptionSources sources) {
        _sources = sources;
        return *this;
    }

    OptionDescription& OptionDescription::positional(int start, int end) {

        if (start < 1 || (end < 1 && end != -1) || (end != -1 && end < start)) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
               << "Invalid positional specification:  \"start\": " << start << ", \"end\": " << end;
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        if ((end - start) > 0) {
            if (_type != StringVector) {
                StringBuilder sb;
                sb << "Could not register option \"" << _dottedName << "\": "
                   << "Positional range implies that multiple values are allowed, "
                   << "but option is not registered as type StringVector";
                throw DBException(sb.str(), ErrorCodes::InternalError);
            }
        }

        _positionalStart = start;
        _positionalEnd = end;
        return *this;
    }

    OptionDescription& OptionDescription::addConstraint(Constraint* c) {
        _constraints.push_back(boost::shared_ptr<Constraint>(c));
        return *this;
    }

    OptionDescription& OptionDescription::validRange(long min, long max) {
        if (_type != Double &&
            _type != Int &&
            _type != Long &&
            _type != UnsignedLongLong &&
            _type != Unsigned) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
               << "only options registered as a numeric type can have a valid range, "
               << "but option has type: " << _type;
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        return addConstraint(new NumericKeyConstraint(_dottedName, min, max));
    }

    OptionDescription& OptionDescription::incompatibleWith(const std::string& otherDottedName) {
        return addConstraint(new MutuallyExclusiveKeyConstraint(_dottedName, otherDottedName));
    }

    OptionDescription& OptionDescription::requires(const std::string& otherDottedName) {
        return addConstraint(new RequiresOtherKeyConstraint(_dottedName, otherDottedName));
    }

    OptionDescription& OptionDescription::format(const std::string& regexFormat,
                                                 const std::string& displayFormat) {
        if (_type != String) {
            StringBuilder sb;
            sb << "Could not register option \"" << _dottedName << "\": "
               << "only options registered as a string type can have a required format, "
               << "but option has type: " << _type;
            throw DBException(sb.str(), ErrorCodes::InternalError);
        }

        return addConstraint(new StringFormatKeyConstraint(_dottedName, regexFormat,
                                                           displayFormat));
    }

} // namespace optionenvironment
} // namespace mongo
