// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause

#pragma once

#include <algorithm>
#include <functional>
#include <memory>
#include <set>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "Error.hpp"
#include "Macros.hpp"
#include "Split.hpp"
#include "StringTools.hpp"
#include "Validators.hpp"

namespace CLI {

using results_t = std::vector<std::string>;
/// callback function definition
using callback_t = std::function<bool(const results_t &)>;

class Option;
class App;

using Option_p = std::unique_ptr<Option>;
/// Enumeration of the multiOption Policy selection
enum class MultiOptionPolicy : char {
    Throw,      //!< Throw an error if any extra arguments were given
    TakeLast,   //!< take only the last Expected number of arguments
    TakeFirst,  //!< take only the first Expected number of arguments
    Join,       //!< merge all the arguments together into a single string via the delimiter character default('\n')
    TakeAll     //!< just get all the passed argument regardless
};

/// This is the CRTP base class for Option and OptionDefaults. It was designed this way
/// to share parts of the class; an OptionDefaults can copy to an Option.
template <typename CRTP> class OptionBase {
    friend App;

  protected:
    /// The group membership
    std::string group_ = std::string("Options");

    /// True if this is a required option
    bool required_{false};

    /// Ignore the case when matching (option, not value)
    bool ignore_case_{false};

    /// Ignore underscores when matching (option, not value)
    bool ignore_underscore_{false};

    /// Allow this option to be given in a configuration file
    bool configurable_{true};

    /// Disable overriding flag values with '=value'
    bool disable_flag_override_{false};

    /// Specify a delimiter character for vector arguments
    char delimiter_{'\0'};

    /// Automatically capture default value
    bool always_capture_default_{false};

    /// Policy for handling multiple arguments beyond the expected Max
    MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};

    /// Copy the contents to another similar class (one based on OptionBase)
    template <typename T> void copy_to(T *other) const {
        other->group(group_);
        other->required(required_);
        other->ignore_case(ignore_case_);
        other->ignore_underscore(ignore_underscore_);
        other->configurable(configurable_);
        other->disable_flag_override(disable_flag_override_);
        other->delimiter(delimiter_);
        other->always_capture_default(always_capture_default_);
        other->multi_option_policy(multi_option_policy_);
    }

  public:
    // setters

    /// Changes the group membership
    CRTP *group(const std::string &name) {
        group_ = name;
        return static_cast<CRTP *>(this);
    }

    /// Set the option as required
    CRTP *required(bool value = true) {
        required_ = value;
        return static_cast<CRTP *>(this);
    }

    /// Support Plumbum term
    CRTP *mandatory(bool value = true) { return required(value); }

    CRTP *always_capture_default(bool value = true) {
        always_capture_default_ = value;
        return static_cast<CRTP *>(this);
    }

    // Getters

    /// Get the group of this option
    const std::string &get_group() const { return group_; }

    /// True if this is a required option
    bool get_required() const { return required_; }

    /// The status of ignore case
    bool get_ignore_case() const { return ignore_case_; }

    /// The status of ignore_underscore
    bool get_ignore_underscore() const { return ignore_underscore_; }

    /// The status of configurable
    bool get_configurable() const { return configurable_; }

    /// The status of configurable
    bool get_disable_flag_override() const { return disable_flag_override_; }

    /// Get the current delimiter char
    char get_delimiter() const { return delimiter_; }

    /// Return true if this will automatically capture the default value for help printing
    bool get_always_capture_default() const { return always_capture_default_; }

    /// The status of the multi option policy
    MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }

    // Shortcuts for multi option policy

    /// Set the multi option policy to take last
    CRTP *take_last() {
        auto self = static_cast<CRTP *>(this);
        self->multi_option_policy(MultiOptionPolicy::TakeLast);
        return self;
    }

    /// Set the multi option policy to take last
    CRTP *take_first() {
        auto self = static_cast<CRTP *>(this);
        self->multi_option_policy(MultiOptionPolicy::TakeFirst);
        return self;
    }

    /// Set the multi option policy to take all arguments
    CRTP *take_all() {
        auto self = static_cast<CRTP *>(this);
        self->multi_option_policy(MultiOptionPolicy::TakeAll);
        return self;
    }

    /// Set the multi option policy to join
    CRTP *join() {
        auto self = static_cast<CRTP *>(this);
        self->multi_option_policy(MultiOptionPolicy::Join);
        return self;
    }

    /// Set the multi option policy to join with a specific delimiter
    CRTP *join(char delim) {
        auto self = static_cast<CRTP *>(this);
        self->delimiter_ = delim;
        self->multi_option_policy(MultiOptionPolicy::Join);
        return self;
    }

    /// Allow in a configuration file
    CRTP *configurable(bool value = true) {
        configurable_ = value;
        return static_cast<CRTP *>(this);
    }

    /// Allow in a configuration file
    CRTP *delimiter(char value = '\0') {
        delimiter_ = value;
        return static_cast<CRTP *>(this);
    }
};

/// This is a version of OptionBase that only supports setting values,
/// for defaults. It is stored as the default option in an App.
class OptionDefaults : public OptionBase<OptionDefaults> {
  public:
    OptionDefaults() = default;

    // Methods here need a different implementation if they are Option vs. OptionDefault

    /// Take the last argument if given multiple times
    OptionDefaults *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
        multi_option_policy_ = value;
        return this;
    }

    /// Ignore the case of the option name
    OptionDefaults *ignore_case(bool value = true) {
        ignore_case_ = value;
        return this;
    }

    /// Ignore underscores in the option name
    OptionDefaults *ignore_underscore(bool value = true) {
        ignore_underscore_ = value;
        return this;
    }

    /// Disable overriding flag values with an '=<value>' segment
    OptionDefaults *disable_flag_override(bool value = true) {
        disable_flag_override_ = value;
        return this;
    }

    /// set a delimiter character to split up single arguments to treat as multiple inputs
    OptionDefaults *delimiter(char value = '\0') {
        delimiter_ = value;
        return this;
    }
};

class Option : public OptionBase<Option> {
    friend App;

  protected:
    /// @name Names
    ///@{

    /// A list of the short names (`-a`) without the leading dashes
    std::vector<std::string> snames_{};

    /// A list of the long names (`--long`) without the leading dashes
    std::vector<std::string> lnames_{};

    /// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of
    /// what is in snames or lnames but will trigger a particular response on a flag
    std::vector<std::pair<std::string, std::string>> default_flag_values_{};

    /// a list of flag names with specified default values;
    std::vector<std::string> fnames_{};

    /// A positional name
    std::string pname_{};

    /// If given, check the environment for this option
    std::string envname_{};

    ///@}
    /// @name Help
    ///@{

    /// The description for help strings
    std::string description_{};

    /// A human readable default value, either manually set, captured, or captured by default
    std::string default_str_{};

    /// A human readable type value, set when App creates this
    ///
    /// This is a lambda function so "types" can be dynamic, such as when a set prints its contents.
    std::function<std::string()> type_name_{[]() { return std::string(); }};

    /// Run this function to capture a default (ignore if empty)
    std::function<std::string()> default_function_{};

    ///@}
    /// @name Configuration
    ///@{

    /// The number of arguments that make up one option. max is the nominal type size, min is the minimum number of
    /// strings
    int type_size_max_{1};
    /// The minimum number of arguments an option should be expecting
    int type_size_min_{1};

    /// The minimum number of expected values
    int expected_min_{1};
    /// The maximum number of expected values
    int expected_max_{1};

    /// A list of Validators to run on each value parsed
    std::vector<Validator> validators_{};

    /// A list of options that are required with this option
    std::set<Option *> needs_{};

    /// A list of options that are excluded with this option
    std::set<Option *> excludes_{};

    ///@}
    /// @name Other
    ///@{

    /// Remember the parent app
    App *parent_{nullptr};

    /// Options store a callback to do all the work
    callback_t callback_{};

    ///@}
    /// @name Parsing results
    ///@{

    /// complete Results of parsing
    results_t results_{};
    /// results after reduction
    results_t proc_results_{};
    /// enumeration for the option state machine
    enum class option_state {
        parsing = 0,       //!< The option is currently collecting parsed results
        validated = 2,     //!< the results have been validated
        reduced = 4,       //!< a subset of results has been generated
        callback_run = 6,  //!< the callback has been executed
    };
    /// Whether the callback has run (needed for INI parsing)
    option_state current_option_state_{option_state::parsing};
    /// Specify that extra args beyond type_size_max should be allowed
    bool allow_extra_args_{false};
    /// Specify that the option should act like a flag vs regular option
    bool flag_like_{false};
    /// Control option to run the callback to set the default
    bool run_callback_for_default_{false};
    ///@}

    /// Making an option by hand is not defined, it must be made by the App class
    Option(std::string option_name, std::string option_description, callback_t callback, App *parent)
        : description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) {
        std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
    }

  public:
    /// @name Basic
    ///@{

    Option(const Option &) = delete;
    Option &operator=(const Option &) = delete;

    /// Count the total number of times an option was passed
    std::size_t count() const { return results_.size(); }

    /// True if the option was not passed
    bool empty() const { return results_.empty(); }

    /// This class is true if option is passed.
    explicit operator bool() const { return !empty(); }

    /// Clear the parsed results (mostly for testing)
    void clear() {
        results_.clear();
        current_option_state_ = option_state::parsing;
    }

    ///@}
    /// @name Setting options
    ///@{

    /// Set the number of expected arguments
    Option *expected(int value) {
        if(value < 0) {
            expected_min_ = -value;
            if(expected_max_ < expected_min_) {
                expected_max_ = expected_min_;
            }
            allow_extra_args_ = true;
            flag_like_ = false;
        } else if(value == detail::expected_max_vector_size) {
            expected_min_ = 1;
            expected_max_ = detail::expected_max_vector_size;
            allow_extra_args_ = true;
            flag_like_ = false;
        } else {
            expected_min_ = value;
            expected_max_ = value;
            flag_like_ = (expected_min_ == 0);
        }
        return this;
    }

    /// Set the range of expected arguments
    Option *expected(int value_min, int value_max) {
        if(value_min < 0) {
            value_min = -value_min;
        }

        if(value_max < 0) {
            value_max = detail::expected_max_vector_size;
        }
        if(value_max < value_min) {
            expected_min_ = value_max;
            expected_max_ = value_min;
        } else {
            expected_max_ = value_max;
            expected_min_ = value_min;
        }

        return this;
    }
    /// Set the value of allow_extra_args which allows extra value arguments on the flag or option to be included
    /// with each instance
    Option *allow_extra_args(bool value = true) {
        allow_extra_args_ = value;
        return this;
    }
    /// Get the current value of allow extra args
    bool get_allow_extra_args() const { return allow_extra_args_; }

    /// Set the value of run_callback_for_default which controls whether the callback function should be called to set
    /// the default This is controlled automatically but could be manipulated by the user.
    Option *run_callback_for_default(bool value = true) {
        run_callback_for_default_ = value;
        return this;
    }
    /// Get the current value of run_callback_for_default
    bool get_run_callback_for_default() const { return run_callback_for_default_; }

    /// Adds a Validator with a built in type name
    Option *check(Validator validator, const std::string &validator_name = "") {
        validator.non_modifying();
        validators_.push_back(std::move(validator));
        if(!validator_name.empty())
            validators_.back().name(validator_name);
        return this;
    }

    /// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
    Option *check(std::function<std::string(const std::string &)> Validator,
                  std::string Validator_description = "",
                  std::string Validator_name = "") {
        validators_.emplace_back(Validator, std::move(Validator_description), std::move(Validator_name));
        validators_.back().non_modifying();
        return this;
    }

    /// Adds a transforming Validator with a built in type name
    Option *transform(Validator Validator, const std::string &Validator_name = "") {
        validators_.insert(validators_.begin(), std::move(Validator));
        if(!Validator_name.empty())
            validators_.front().name(Validator_name);
        return this;
    }

    /// Adds a Validator-like function that can change result
    Option *transform(const std::function<std::string(std::string)> &func,
                      std::string transform_description = "",
                      std::string transform_name = "") {
        validators_.insert(validators_.begin(),
                           Validator(
                               [func](std::string &val) {
                                   val = func(val);
                                   return std::string{};
                               },
                               std::move(transform_description),
                               std::move(transform_name)));

        return this;
    }

    /// Adds a user supplied function to run on each item passed in (communicate though lambda capture)
    Option *each(const std::function<void(std::string)> &func) {
        validators_.emplace_back(
            [func](std::string &inout) {
                func(inout);
                return std::string{};
            },
            std::string{});
        return this;
    }
    /// Get a named Validator
    Validator *get_validator(const std::string &Validator_name = "") {
        for(auto &Validator : validators_) {
            if(Validator_name == Validator.get_name()) {
                return &Validator;
            }
        }
        if((Validator_name.empty()) && (!validators_.empty())) {
            return &(validators_.front());
        }
        throw OptionNotFound(std::string{"Validator "} + Validator_name + " Not Found");
    }

    /// Get a Validator by index NOTE: this may not be the order of definition
    Validator *get_validator(int index) {
        // This is an signed int so that it is not equivalent to a pointer.
        if(index >= 0 && index < static_cast<int>(validators_.size())) {
            return &(validators_[static_cast<decltype(validators_)::size_type>(index)]);
        }
        throw OptionNotFound("Validator index is not valid");
    }

    /// Sets required options
    Option *needs(Option *opt) {
        if(opt != this) {
            needs_.insert(opt);
        }
        return this;
    }

    /// Can find a string if needed
    template <typename T = App> Option *needs(std::string opt_name) {
        auto opt = static_cast<T *>(parent_)->get_option_no_throw(opt_name);
        if(opt == nullptr) {
            throw IncorrectConstruction::MissingOption(opt_name);
        }
        return needs(opt);
    }

    /// Any number supported, any mix of string and Opt
    template <typename A, typename B, typename... ARG> Option *needs(A opt, B opt1, ARG... args) {
        needs(opt);
        return needs(opt1, args...);
    }

    /// Remove needs link from an option. Returns true if the option really was in the needs list.
    bool remove_needs(Option *opt) {
        auto iterator = std::find(std::begin(needs_), std::end(needs_), opt);

        if(iterator == std::end(needs_)) {
            return false;
        }
        needs_.erase(iterator);
        return true;
    }

    /// Sets excluded options
    Option *excludes(Option *opt) {
        if(opt == this) {
            throw(IncorrectConstruction("and option cannot exclude itself"));
        }
        excludes_.insert(opt);

        // Help text should be symmetric - excluding a should exclude b
        opt->excludes_.insert(this);

        // Ignoring the insert return value, excluding twice is now allowed.
        // (Mostly to allow both directions to be excluded by user, even though the library does it for you.)

        return this;
    }

    /// Can find a string if needed
    template <typename T = App> Option *excludes(std::string opt_name) {
        auto opt = static_cast<T *>(parent_)->get_option_no_throw(opt_name);
        if(opt == nullptr) {
            throw IncorrectConstruction::MissingOption(opt_name);
        }
        return excludes(opt);
    }

    /// Any number supported, any mix of string and Opt
    template <typename A, typename B, typename... ARG> Option *excludes(A opt, B opt1, ARG... args) {
        excludes(opt);
        return excludes(opt1, args...);
    }

    /// Remove needs link from an option. Returns true if the option really was in the needs list.
    bool remove_excludes(Option *opt) {
        auto iterator = std::find(std::begin(excludes_), std::end(excludes_), opt);

        if(iterator == std::end(excludes_)) {
            return false;
        }
        excludes_.erase(iterator);
        return true;
    }

    /// Sets environment variable to read if no option given
    Option *envname(std::string name) {
        envname_ = std::move(name);
        return this;
    }

    /// Ignore case
    ///
    /// The template hides the fact that we don't have the definition of App yet.
    /// You are never expected to add an argument to the template here.
    template <typename T = App> Option *ignore_case(bool value = true) {
        if(!ignore_case_ && value) {
            ignore_case_ = value;
            auto *parent = static_cast<T *>(parent_);
            for(const Option_p &opt : parent->options_) {
                if(opt.get() == this) {
                    continue;
                }
                auto &omatch = opt->matching_name(*this);
                if(!omatch.empty()) {
                    ignore_case_ = false;
                    throw OptionAlreadyAdded("adding ignore case caused a name conflict with " + omatch);
                }
            }
        } else {
            ignore_case_ = value;
        }
        return this;
    }

    /// Ignore underscores in the option names
    ///
    /// The template hides the fact that we don't have the definition of App yet.
    /// You are never expected to add an argument to the template here.
    template <typename T = App> Option *ignore_underscore(bool value = true) {

        if(!ignore_underscore_ && value) {
            ignore_underscore_ = value;
            auto *parent = static_cast<T *>(parent_);
            for(const Option_p &opt : parent->options_) {
                if(opt.get() == this) {
                    continue;
                }
                auto &omatch = opt->matching_name(*this);
                if(!omatch.empty()) {
                    ignore_underscore_ = false;
                    throw OptionAlreadyAdded("adding ignore underscore caused a name conflict with " + omatch);
                }
            }
        } else {
            ignore_underscore_ = value;
        }
        return this;
    }

    /// Take the last argument if given multiple times (or another policy)
    Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
        if(value != multi_option_policy_) {
            if(multi_option_policy_ == MultiOptionPolicy::Throw && expected_max_ == detail::expected_max_vector_size &&
               expected_min_ > 1) {  // this bizarre condition is to maintain backwards compatibility
                                     // with the previous behavior of expected_ with vectors
                expected_max_ = expected_min_;
            }
            multi_option_policy_ = value;
            current_option_state_ = option_state::parsing;
        }
        return this;
    }

    /// Disable flag overrides values, e.g. --flag=<value> is not allowed
    Option *disable_flag_override(bool value = true) {
        disable_flag_override_ = value;
        return this;
    }
    ///@}
    /// @name Accessors
    ///@{

    /// The number of arguments the option expects
    int get_type_size() const { return type_size_min_; }

    /// The minimum number of arguments the option expects
    int get_type_size_min() const { return type_size_min_; }
    /// The maximum number of arguments the option expects
    int get_type_size_max() const { return type_size_max_; }

    /// The environment variable associated to this value
    std::string get_envname() const { return envname_; }

    /// The set of options needed
    std::set<Option *> get_needs() const { return needs_; }

    /// The set of options excluded
    std::set<Option *> get_excludes() const { return excludes_; }

    /// The default value (for help printing)
    std::string get_default_str() const { return default_str_; }

    /// Get the callback function
    callback_t get_callback() const { return callback_; }

    /// Get the long names
    const std::vector<std::string> &get_lnames() const { return lnames_; }

    /// Get the short names
    const std::vector<std::string> &get_snames() const { return snames_; }

    /// Get the flag names with specified default values
    const std::vector<std::string> &get_fnames() const { return fnames_; }

    /// The number of times the option expects to be included
    int get_expected() const { return expected_min_; }

    /// The number of times the option expects to be included
    int get_expected_min() const { return expected_min_; }
    /// The max number of times the option expects to be included
    int get_expected_max() const { return expected_max_; }

    /// The total min number of expected  string values to be used
    int get_items_expected_min() const { return type_size_min_ * expected_min_; }

    /// Get the maximum number of items expected to be returned and used for the callback
    int get_items_expected_max() const {
        int t = type_size_max_;
        return detail::checked_multiply(t, expected_max_) ? t : detail::expected_max_vector_size;
    }
    /// The total min number of expected  string values to be used
    int get_items_expected() const { return get_items_expected_min(); }

    /// True if the argument can be given directly
    bool get_positional() const { return pname_.length() > 0; }

    /// True if option has at least one non-positional name
    bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; }

    /// True if option has description
    bool has_description() const { return description_.length() > 0; }

    /// Get the description
    const std::string &get_description() const { return description_; }

    /// Set the description
    Option *description(std::string option_description) {
        description_ = std::move(option_description);
        return this;
    }

    ///@}
    /// @name Help tools
    ///@{

    /// \brief Gets a comma separated list of names.
    /// Will include / prefer the positional name if positional is true.
    /// If all_options is false, pick just the most descriptive name to show.
    /// Use `get_name(true)` to get the positional name (replaces `get_pname`)
    std::string get_name(bool positional = false,  ///< Show the positional name
                         bool all_options = false  ///< Show every option
                         ) const {
        if(get_group().empty())
            return {};  // Hidden

        if(all_options) {

            std::vector<std::string> name_list;

            /// The all list will never include a positional unless asked or that's the only name.
            if((positional && (!pname_.empty())) || (snames_.empty() && lnames_.empty())) {
                name_list.push_back(pname_);
            }
            if((get_items_expected() == 0) && (!fnames_.empty())) {
                for(const std::string &sname : snames_) {
                    name_list.push_back("-" + sname);
                    if(check_fname(sname)) {
                        name_list.back() += "{" + get_flag_value(sname, "") + "}";
                    }
                }

                for(const std::string &lname : lnames_) {
                    name_list.push_back("--" + lname);
                    if(check_fname(lname)) {
                        name_list.back() += "{" + get_flag_value(lname, "") + "}";
                    }
                }
            } else {
                for(const std::string &sname : snames_)
                    name_list.push_back("-" + sname);

                for(const std::string &lname : lnames_)
                    name_list.push_back("--" + lname);
            }

            return detail::join(name_list);
        }

        // This returns the positional name no matter what
        if(positional)
            return pname_;

        // Prefer long name
        if(!lnames_.empty())
            return std::string(2, '-') + lnames_[0];

        // Or short name if no long name
        if(!snames_.empty())
            return std::string(1, '-') + snames_[0];

        // If positional is the only name, it's okay to use that
        return pname_;
    }

    ///@}
    /// @name Parser tools
    ///@{

    /// Process the callback
    void run_callback() {

        if(current_option_state_ == option_state::parsing) {
            _validate_results(results_);
            current_option_state_ = option_state::validated;
        }

        if(current_option_state_ < option_state::reduced) {
            _reduce_results(proc_results_, results_);
            current_option_state_ = option_state::reduced;
        }
        if(current_option_state_ >= option_state::reduced) {
            current_option_state_ = option_state::callback_run;
            if(!(callback_)) {
                return;
            }
            const results_t &send_results = proc_results_.empty() ? results_ : proc_results_;
            bool local_result = callback_(send_results);

            if(!local_result)
                throw ConversionError(get_name(), results_);
        }
    }

    /// If options share any of the same names, find it
    const std::string &matching_name(const Option &other) const {
        static const std::string estring;
        for(const std::string &sname : snames_)
            if(other.check_sname(sname))
                return sname;
        for(const std::string &lname : lnames_)
            if(other.check_lname(lname))
                return lname;

        if(ignore_case_ ||
           ignore_underscore_) {  // We need to do the inverse, in case we are ignore_case or ignore underscore
            for(const std::string &sname : other.snames_)
                if(check_sname(sname))
                    return sname;
            for(const std::string &lname : other.lnames_)
                if(check_lname(lname))
                    return lname;
        }
        return estring;
    }
    /// If options share any of the same names, they are equal (not counting positional)
    bool operator==(const Option &other) const { return !matching_name(other).empty(); }

    /// Check a name. Requires "-" or "--" for short / long, supports positional name
    bool check_name(std::string name) const {

        if(name.length() > 2 && name[0] == '-' && name[1] == '-')
            return check_lname(name.substr(2));
        if(name.length() > 1 && name.front() == '-')
            return check_sname(name.substr(1));

        std::string local_pname = pname_;
        if(ignore_underscore_) {
            local_pname = detail::remove_underscore(local_pname);
            name = detail::remove_underscore(name);
        }
        if(ignore_case_) {
            local_pname = detail::to_lower(local_pname);
            name = detail::to_lower(name);
        }
        return name == local_pname;
    }

    /// Requires "-" to be removed from string
    bool check_sname(std::string name) const {
        return (detail::find_member(std::move(name), snames_, ignore_case_) >= 0);
    }

    /// Requires "--" to be removed from string
    bool check_lname(std::string name) const {
        return (detail::find_member(std::move(name), lnames_, ignore_case_, ignore_underscore_) >= 0);
    }

    /// Requires "--" to be removed from string
    bool check_fname(std::string name) const {
        if(fnames_.empty()) {
            return false;
        }
        return (detail::find_member(std::move(name), fnames_, ignore_case_, ignore_underscore_) >= 0);
    }

    /// Get the value that goes for a flag, nominally gets the default value but allows for overrides if not
    /// disabled
    std::string get_flag_value(const std::string &name, std::string input_value) const {
        static const std::string trueString{"true"};
        static const std::string falseString{"false"};
        static const std::string emptyString{"{}"};
        // check for disable flag override_
        if(disable_flag_override_) {
            if(!((input_value.empty()) || (input_value == emptyString))) {
                auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
                if(default_ind >= 0) {
                    // We can static cast this to std::size_t because it is more than 0 in this block
                    if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) {
                        throw(ArgumentMismatch::FlagOverride(name));
                    }
                } else {
                    if(input_value != trueString) {
                        throw(ArgumentMismatch::FlagOverride(name));
                    }
                }
            }
        }
        auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
        if((input_value.empty()) || (input_value == emptyString)) {
            if(flag_like_) {
                return (ind < 0) ? trueString : default_flag_values_[static_cast<std::size_t>(ind)].second;
            } else {
                return (ind < 0) ? default_str_ : default_flag_values_[static_cast<std::size_t>(ind)].second;
            }
        }
        if(ind < 0) {
            return input_value;
        }
        if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
            try {
                auto val = detail::to_flag_value(input_value);
                return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
            } catch(const std::invalid_argument &) {
                return input_value;
            }
        } else {
            return input_value;
        }
    }

    /// Puts a result at the end
    Option *add_result(std::string s) {
        _add_result(std::move(s), results_);
        current_option_state_ = option_state::parsing;
        return this;
    }

    /// Puts a result at the end and get a count of the number of arguments actually added
    Option *add_result(std::string s, int &results_added) {
        results_added = _add_result(std::move(s), results_);
        current_option_state_ = option_state::parsing;
        return this;
    }

    /// Puts a result at the end
    Option *add_result(std::vector<std::string> s) {
        for(auto &str : s) {
            _add_result(std::move(str), results_);
        }
        current_option_state_ = option_state::parsing;
        return this;
    }

    /// Get a copy of the results
    results_t results() const { return results_; }

    /// Get a copy of the results
    results_t reduced_results() const {
        results_t res = proc_results_.empty() ? results_ : proc_results_;
        if(current_option_state_ < option_state::reduced) {
            if(current_option_state_ == option_state::parsing) {
                res = results_;
                _validate_results(res);
            }
            if(!res.empty()) {
                results_t extra;
                _reduce_results(extra, res);
                if(!extra.empty()) {
                    res = std::move(extra);
                }
            }
        }
        return res;
    }

    /// Get the results as a specified type
    template <typename T, enable_if_t<!std::is_const<T>::value, detail::enabler> = detail::dummy>
    void results(T &output) const {
        bool retval;
        if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) {
            const results_t &res = (proc_results_.empty()) ? results_ : proc_results_;
            retval = detail::lexical_conversion<T, T>(res, output);
        } else {
            results_t res;
            if(results_.empty()) {
                if(!default_str_.empty()) {
                    // _add_results takes an rvalue only
                    _add_result(std::string(default_str_), res);
                    _validate_results(res);
                    results_t extra;
                    _reduce_results(extra, res);
                    if(!extra.empty()) {
                        res = std::move(extra);
                    }
                } else {
                    res.emplace_back();
                }
            } else {
                res = reduced_results();
            }
            retval = detail::lexical_conversion<T, T>(res, output);
        }
        if(!retval) {
            throw ConversionError(get_name(), results_);
        }
    }

    /// Return the results as the specified type
    template <typename T> T as() const {
        T output;
        results(output);
        return output;
    }

    /// See if the callback has been run already
    bool get_callback_run() const { return (current_option_state_ == option_state::callback_run); }

    ///@}
    /// @name Custom options
    ///@{

    /// Set the type function to run when displayed on this option
    Option *type_name_fn(std::function<std::string()> typefun) {
        type_name_ = std::move(typefun);
        return this;
    }

    /// Set a custom option typestring
    Option *type_name(std::string typeval) {
        type_name_fn([typeval]() { return typeval; });
        return this;
    }

    /// Set a custom option size
    Option *type_size(int option_type_size) {
        if(option_type_size < 0) {
            // this section is included for backwards compatibility
            type_size_max_ = -option_type_size;
            type_size_min_ = -option_type_size;
            expected_max_ = detail::expected_max_vector_size;
        } else {
            type_size_max_ = option_type_size;
            if(type_size_max_ < detail::expected_max_vector_size) {
                type_size_min_ = option_type_size;
            }
            if(type_size_max_ == 0)
                required_ = false;
        }
        return this;
    }
    /// Set a custom option type size range
    Option *type_size(int option_type_size_min, int option_type_size_max) {
        if(option_type_size_min < 0 || option_type_size_max < 0) {
            // this section is included for backwards compatibility
            expected_max_ = detail::expected_max_vector_size;
            option_type_size_min = (std::abs)(option_type_size_min);
            option_type_size_max = (std::abs)(option_type_size_max);
        }

        if(option_type_size_min > option_type_size_max) {
            type_size_max_ = option_type_size_min;
            type_size_min_ = option_type_size_max;
        } else {
            type_size_min_ = option_type_size_min;
            type_size_max_ = option_type_size_max;
        }
        if(type_size_max_ == 0) {
            required_ = false;
        }
        return this;
    }

    /// Set a capture function for the default. Mostly used by App.
    Option *default_function(const std::function<std::string()> &func) {
        default_function_ = func;
        return this;
    }

    /// Capture the default value from the original value (if it can be captured)
    Option *capture_default_str() {
        if(default_function_) {
            default_str_ = default_function_();
        }
        return this;
    }

    /// Set the default value string representation (does not change the contained value)
    Option *default_str(std::string val) {
        default_str_ = std::move(val);
        return this;
    }

    /// Set the default value and validate the results and run the callback if appropriate to set the value into the
    /// bound value only available for types that can be converted to a string
    template <typename X> Option *default_val(const X &val) {
        std::string val_str = detail::to_string(val);
        auto old_option_state = current_option_state_;
        results_t old_results{std::move(results_)};
        results_.clear();
        try {
            add_result(val_str);
            if(run_callback_for_default_) {
                run_callback();  // run callback sets the state we need to reset it again
                current_option_state_ = option_state::parsing;
            } else {
                _validate_results(results_);
                current_option_state_ = old_option_state;
            }
        } catch(const CLI::Error &) {
            // this should be done
            results_ = std::move(old_results);
            current_option_state_ = old_option_state;
            throw;
        }
        results_ = std::move(old_results);
        default_str_ = std::move(val_str);
        return this;
    }

    /// Get the full typename for this option
    std::string get_type_name() const {
        std::string full_type_name = type_name_();
        if(!validators_.empty()) {
            for(auto &Validator : validators_) {
                std::string vtype = Validator.get_description();
                if(!vtype.empty()) {
                    full_type_name += ":" + vtype;
                }
            }
        }
        return full_type_name;
    }

  private:
    /// Run the results through the Validators
    void _validate_results(results_t &res) const {
        // Run the Validators (can change the string)
        if(!validators_.empty()) {
            if(type_size_max_ > 1) {  // in this context index refers to the index in the type
                int index = 0;
                if(get_items_expected_max() < static_cast<int>(res.size()) &&
                   multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
                    // create a negative index for the earliest ones
                    index = get_items_expected_max() - static_cast<int>(res.size());
                }

                for(std::string &result : res) {
                    if(result.empty() && type_size_max_ != type_size_min_ && index >= 0) {
                        index = 0;  // reset index for variable size chunks
                        continue;
                    }
                    auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index);
                    if(!err_msg.empty())
                        throw ValidationError(get_name(), err_msg);
                    ++index;
                }
            } else {
                int index = 0;
                if(expected_max_ < static_cast<int>(res.size()) &&
                   multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
                    // create a negative index for the earliest ones
                    index = expected_max_ - static_cast<int>(res.size());
                }
                for(std::string &result : res) {
                    auto err_msg = _validate(result, index);
                    ++index;
                    if(!err_msg.empty())
                        throw ValidationError(get_name(), err_msg);
                }
            }
        }
    }

    /** reduce the results in accordance with the MultiOptionPolicy
    @param[out] res results are assigned to res if there if they are different
    */
    void _reduce_results(results_t &res, const results_t &original) const {

        // max num items expected or length of vector, always at least 1
        // Only valid for a trimming policy

        res.clear();
        // Operation depends on the policy setting
        switch(multi_option_policy_) {
        case MultiOptionPolicy::TakeAll:
            break;
        case MultiOptionPolicy::TakeLast: {
            // Allow multi-option sizes (including 0)
            std::size_t trim_size = std::min<std::size_t>(
                static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
            if(original.size() != trim_size) {
                res.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end());
            }
        } break;
        case MultiOptionPolicy::TakeFirst: {
            std::size_t trim_size = std::min<std::size_t>(
                static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
            if(original.size() != trim_size) {
                res.assign(original.begin(), original.begin() + static_cast<results_t::difference_type>(trim_size));
            }
        } break;
        case MultiOptionPolicy::Join:
            if(results_.size() > 1) {
                res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
            }
            break;
        case MultiOptionPolicy::Throw:
        default: {
            auto num_min = static_cast<std::size_t>(get_items_expected_min());
            auto num_max = static_cast<std::size_t>(get_items_expected_max());
            if(num_min == 0) {
                num_min = 1;
            }
            if(num_max == 0) {
                num_max = 1;
            }
            if(original.size() < num_min) {
                throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size());
            }
            if(original.size() > num_max) {
                throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
            }
            break;
        }
        }
    }

    // Run a result through the Validators
    std::string _validate(std::string &result, int index) const {
        std::string err_msg;
        if(result.empty() && expected_min_ == 0) {
            // an empty with nothing expected is allowed
            return err_msg;
        }
        for(const auto &vali : validators_) {
            auto v = vali.get_application_index();
            if(v == -1 || v == index) {
                try {
                    err_msg = vali(result);
                } catch(const ValidationError &err) {
                    err_msg = err.what();
                }
                if(!err_msg.empty())
                    break;
            }
        }

        return err_msg;
    }

    /// Add a single result to the result set, taking into account delimiters
    int _add_result(std::string &&result, std::vector<std::string> &res) const {
        int result_count = 0;
        if(allow_extra_args_ && !result.empty() && result.front() == '[' &&
           result.back() == ']') {  // this is now a vector string likely from the default or user entry
            result.pop_back();

            for(auto &var : CLI::detail::split(result.substr(1), ',')) {
                if(!var.empty()) {
                    result_count += _add_result(std::move(var), res);
                }
            }
            return result_count;
        }
        if(delimiter_ == '\0') {
            res.push_back(std::move(result));
            ++result_count;
        } else {
            if((result.find_first_of(delimiter_) != std::string::npos)) {
                for(const auto &var : CLI::detail::split(result, delimiter_)) {
                    if(!var.empty()) {
                        res.push_back(var);
                        ++result_count;
                    }
                }
            } else {
                res.push_back(std::move(result));
                ++result_count;
            }
        }
        return result_count;
    }
};  // namespace CLI

}  // namespace CLI
