/**
 * @file ArgumentParser.h
 * @author apio (cloudapio.eu)
 * @brief Command-line argument parser.
 *
 * @copyright Copyright (c) 2023, the Luna authors.
 *
 */

#pragma once
#include <luna/StringView.h>
#include <luna/Vector.h>

namespace os
{
    /**
     * @brief A simple command-line argument parser.
     */
    class ArgumentParser
    {
      public:
        /**
         * @brief Construct a new ArgumentParser object.
         */
        ArgumentParser();

        /**
         * @brief Add a description for this command-line utility (shown in the help text).
         *
         * @param description The description to use.
         */
        void add_description(StringView description);

        /**
         * @brief Register a new positional argument.
         *
         * @param out The variable where the argument's value will be stored.
         * @param name The positional argument's name.
         * @param fallback The value to use if the user does not provide one.
         * @return Result<void> Whether the operation succeeded.
         */
        Result<void> add_positional_argument(StringView& out, StringView name, StringView fallback);

        /**
         * @brief Register a new positional argument.
         *
         * @param out The variable where the argument's value will be stored.
         * @param name The positional argument's name.
         * @param required Whether the user must enter a value for this argument. If this is false and the user does not
         * enter a value, out will be set to an empty string.
         * @return Result<void> Whether the operation succeeded.
         */
        Result<void> add_positional_argument(StringView& out, StringView name, bool required);

        /**
         * @brief Register a new switch argument.
         *
         * @param out This variable will be set to true if the user provides the switch argument on the command
         * line.
         * @param short_flag The short flag to use for this argument, excluding the '-' prefix: an option '-c' would be
         * passed as 'c'. Can only be a single ASCII character. If you do not wish this argument to have a short flag,
         * use the space character: ' '.
         * @param long_flag The long flag to use for this argument, excluding the '--' prefix: an option '--example'
         * would be passed as 'example'. Can be a string of any length. If you do not wish this argument to have a long
         * flag, use an empty string: "".
         * @param help The help text to show for this argument (optional).
         * @return Result<void> Whether the operation succeeded.
         */
        Result<void> add_switch_argument(bool& out, char short_flag, StringView long_flag, StringView help = {});

        /**
         * @brief Register a new value argument.
         *
         * @param out The variable where the argument's value will be stored.
         * @param short_flag The short flag to use for this argument, excluding the '-' prefix: an option '-c' would be
         * passed as 'c'. Can only be a single ASCII character. If you do not wish this argument to have a short flag,
         * use the space character: ' '.
         * @param long_flag The long flag to use for this argument, excluding the '--' prefix: an option '--example'
         * would be passed as 'example'. Can be a string of any length. If you do not wish this argument to have a long
         * flag, use an empty string: "".
         * @param help The help text to show for this argument (optional).
         * @return Result<void> Whether the operation succeeded.
         */
        Result<void> add_value_argument(StringView& out, char short_flag, StringView long_flag, StringView help = {});

        /**
         * @brief Register a positional argument vector (can be only done once).
         *
         * @param out The vector of StringViews to store arguments in.
         * @param name The name to give to the argument vector (in the help text).
         * @param fallback The value to put in the vector if no values are provided.
         * @param allow_no_more_flags If set, after starting to append values into the vector, no more flags (switch and
         * value arguments) will be parsed, and will instead be treated as regular positional arguments, as if '--' had
         * been specified on the command line.
         */
        Result<void> set_vector_argument(Vector<StringView>& out, StringView name, StringView fallback,
                                         bool allow_no_more_flags = false);

        /**
         * @brief Register a positional argument vector (can be only done once).
         *
         * @param out The vector of StringViews to store arguments in.
         * @param name The name to give to the argument vector (in the help text).
         * @param required Whether the user must enter at least one value for this argument. If this is false and the
         * user does not enter a value, out will be cleared.
         * @param allow_no_more_flags If set, after starting to append values into the vector, no more flags (switch and
         * value arguments) will be parsed, and will instead be treated as regular positional arguments, as if '--' had
         * been specified on the command line.
         */
        Result<void> set_vector_argument(Vector<StringView>& out, StringView name, bool required,
                                         bool allow_no_more_flags = false);

        /**
         * @brief Parse the given command-line using this ArgumentParser's registered arguments.
         *
         * @param argc The argc value passed to main() or luna_main().
         * @param argv The argv value passed to main() or luna_main().
         * @return Result<void> Whether the operation succeeded.
         */
        Result<void> parse(int argc, char* const* argv);

        /**
         * @brief A program's copyright and version information.
         */
        struct ProgramInfo
        {
            StringView name;      // The program's name.
            StringView version;   // The program's version/release.
            StringView copyright; // The program's copyright statement.
            StringView license;   // The program's licensing information.
            StringView authors;   // The program's authors (optional).
            StringView package;   // The package the program is a part of (optional).
        };

        /**
         * @brief Set this program's copyright and version information (shown in the version text).
         *
         * @param info The program information to use.
         */
        void add_program_info(ProgramInfo info);

        /**
         * @brief For programs that are part of the Luna source tree, set the version information using Luna's own
         * version and copyright info and the program name.
         *
         * @param name The program name to show in the version information.
         */
        void add_system_program_info(StringView name);

        /**
         * @brief Show a short message describing how to find usage information (usually "<program> --help").
         *
         * The actual message shown is:
         *  "Try running '<program> --help' for more information." if --help has not been overridden.
         *  "Try running '<program> -h' for more information." if -h has not been overridden.
         *  If both have been overridden, no output is shown.
         *
         * Then, the program exits with a non-zero exit code.
         * This function is designed to be used when the program detects an invalid argument value after parse() has run
         * successfully.
         *
         * @param program_name The program name to show (usually argv[0]).
         */
        void short_usage(StringView program_name);

      private:
        struct PositionalArgument
        {
            StringView* out;
            StringView name;
            bool required;
            StringView fallback;
        };

        struct SwitchArgument
        {
            bool* out;
            char short_flag;
            StringView long_flag;
            StringView help;
        };

        struct ValueArgument
        {
            StringView* out;
            char short_flag;
            StringView long_flag;
            StringView help;
        };

        Result<void> usage(StringView program_name);

        void version();

        Vector<PositionalArgument> m_positional_args;
        Vector<SwitchArgument> m_switch_args;
        Vector<ValueArgument> m_value_args;
        Vector<StringView>* m_vector_argument { nullptr };
        bool m_allow_no_more_flags_after_vector_argument_start { false };
        ProgramInfo m_program_info;
        StringView m_description = {};
        bool m_add_short_help_flag { false };
        bool m_add_long_help_flag { false };
        bool m_add_short_version_flag { false };
        bool m_add_long_version_flag { false };
    };
}