/*
Copyright (c) 2017-2020,
Battelle Memorial Institute; Lawrence Livermore National Security, LLC; Alliance for Sustainable Energy, LLC.  See
the top-level NOTICE for additional details. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
*/

#pragma once

#define CLI11_EXPERIMENTAL_OPTIONAL 0
#include "helics/external/CLI11/CLI11.hpp"
#undef CLI11_EXPERIMENTAL_OPTIONAL
#include "core-types.hpp"
#include "helics-time.hpp"
#if defined HELICS_SHARED_LIBRARY || !defined HELICS_STATIC_CORE_LIBRARY
#    include "../application_api/timeOperations.hpp"
#    include "../application_api/typeOperations.hpp"

using helics::coreTypeFromString;
using helics::loadTimeFromString;
using helics::to_string;
#else
#    include "../utilities/timeStringOps.hpp"
#    include "coreTypeOperations.hpp"

using helics::core::coreTypeFromString;
using helics::core::to_string;
/** generate a local function that uses the utilities library*/
inline helics::Time loadTimeFromString(const std::string& str, time_units defUnit)
{
    return gmlc::utilities::loadTimeFromString<helics::Time>(str, defUnit);
}

#endif
#include "helicsVersion.hpp"
namespace helics {
class helicsCLI11App: public CLI::App {
  public:
    explicit helicsCLI11App(std::string app_description = "", const std::string& app_name = ""):
        CLI::App(std::move(app_description), app_name, nullptr)
    {
        set_help_flag("-h,-?,--help", "Print this help message and exit");
        set_config("--config-file", "helics_config.ini", "specify base configuration file");
        add_flag_callback("--version,-v", []() { throw(CLI::Success{}); });
        add_option_group("quiet")->immediate_callback()->add_flag(
            "--quiet", quiet, "silence most print output");
    }

    enum class parse_output : int {
        ok = 0,
        help_call = 1,
        help_all_call = 2,
        version_call = 4,
        parse_error = -4,
    };
    bool quiet{false};
    parse_output last_output{parse_output::ok};

    template<typename... Args>
    parse_output helics_parse(Args&&... args) noexcept
    {
        try {
            parse(std::forward<Args>(args)...);
            last_output = parse_output::ok;
            remArgs = remaining_for_passthrough();
            return parse_output::ok;
        }
        catch (const CLI::CallForHelp& ch) {
            if (!quiet) {
                exit(ch);
            }
            last_output = parse_output::help_call;
            return parse_output::help_call;
        }
        catch (const CLI::CallForAllHelp& ca) {
            if (!quiet) {
                exit(ca);
            }
            last_output = parse_output::help_all_call;
            return parse_output::help_all_call;
        }
        catch (const CLI::Success&) {
            if (!quiet) {
                std::cout << helics::versionString << '\n';
            }
            last_output = parse_output::version_call;
            return parse_output::version_call;
        }
        catch (const CLI::Error& ce) {
            CLI::App::exit(ce);
            last_output = parse_output::parse_error;
            return parse_output::parse_error;
        }
        catch (...) {
            last_output = parse_output::parse_error;
            return parse_output::parse_error;
        }
    }
    std::vector<std::string>& remainArgs() { return remArgs; }
    void remove_helics_specifics()
    {
        set_help_flag();
        set_config();
        try {
            remove_option(get_option_no_throw("-v"));
            remove_subcommand(get_option_group("quiet"));
        }
        catch (const CLI::Error&) {
            // must have been removed earlier
        }
    }
    /** Add a callback function to execute on parsing*/
    void add_callback(std::function<void()> cback)
    {
        if (cbacks.empty()) {
            callback([this]() {
                for (auto& cb : cbacks) {
                    cb();
                }
            });
        }
        cbacks.push_back(std::move(cback));
    }

    void addTypeOption()
    {
        auto og = add_option_group("network type")->immediate_callback();
        og->add_option_function<std::string>(
              "--coretype,-t,--type,--core",
              [this](const std::string& val) {
                  coreType = coreTypeFromString(val);
                  if (coreType == core_type::UNRECOGNIZED)
                      throw CLI::ValidationError(val + " is NOT a recognized core type");
              },
              "type of the core to connect to")
            ->default_str("(" + to_string(coreType) + ")");
    }
    core_type getCoreType() const { return coreType; }
    /** set default core core type*/
    void setDefaultCoreType(core_type type) { coreType = type; }

  private:
    std::vector<std::function<void()>> cbacks;
    std::vector<std::string> remArgs;
    core_type coreType{core_type::DEFAULT};
};
} // namespace helics

// use the CLI lexical cast function overload to convert a string into a time
namespace CLI {
namespace detail {
    template<>
    inline bool lexical_cast<helics::Time>(const std::string& input, helics::Time& output)
    {
        try {
            output = loadTimeFromString(input, time_units::ms);
        }
        catch (std::invalid_argument&) {
            return false;
        }
        return true;
    }

    template<>
    constexpr const char* type_name<helics::Time>()
    {
        return "TIME";
    }
} // namespace detail
} // namespace CLI
