/*
 * uuid-console - Microcontroller console shell
 * Copyright 2019,2021-2022  Simon Arlott
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef UUID_CONSOLE_H_
#define UUID_CONSOLE_H_

#include <Arduino.h>

#include <cstdarg>
#include <cstdint>
#include <deque>
#include <functional>
#include <iterator>
#include <limits>
#include <list>
#include <memory>
#include <map>
#include <set>
#include <string>
#include <vector>

#include <esp32-psram.h>
#include <uuid/common.h>
#include <uuid/log.h>

#ifndef UUID_LOG_THREAD_SAFE
#define UUID_LOG_THREAD_SAFE 0
#endif

#ifndef UUID_COMMON_STD_MUTEX_AVAILABLE
#define UUID_COMMON_STD_MUTEX_AVAILABLE 0
#endif

#if UUID_COMMON_STD_MUTEX_AVAILABLE
#define UUID_CONSOLE_THREAD_SAFE 1
#else
#define UUID_CONSOLE_THREAD_SAFE 0
#endif

#if UUID_CONSOLE_THREAD_SAFE
#include <mutex>
#endif

enum CommandFlags : uint8_t { USER = 0, ADMIN = (1 << 0), LOCAL = (1 << 1) };

namespace uuid {

/**
 * Console shell.
 *
 * - <a href="https://github.com/nomis/mcu-uuid-console/">Git Repository</a>
 * - <a href="https://mcu-uuid-console.readthedocs.io/">Documentation</a>
 */
namespace console {

/**
 * Thread-safe status of the library.
 *
 * @since 1.0.0
 */
#if UUID_COMMON_THREAD_SAFE && UUID_LOG_THREAD_SAFE && UUID_CONSOLE_THREAD_SAFE
static constexpr bool thread_safe = true;
#else
static constexpr bool thread_safe = false;
#endif

class Commands;
class CommandLine;
class Shell;

/**
 * Container of commands for use by a Shell.
 *
 * These should normally be stored in a std::shared_ptr and reused.
 *
 * @since 0.1.0
 */
class Commands {
  private:
    class Command;

  public:
    struct Completion;

    /**
	 * Result of a command execution operation.
	 *
	 * @since 0.1.0
	 */
    struct Execution {
        const char * error; /*!< Error message if the command could not be executed. @since 0.1.0 */
    };

    /**
	 * Function to handle a command.
	 *
	 * @param[in] shell Shell instance that is executing the command.
	 * @param[in] arguments Command line arguments.
	 * @since 0.1.0
	 */
    using command_function = std::function<void(Shell & shell, std::vector<std::string> & arguments)>;
    /**
	 * Function to obtain completions for a command line.
	 *
	 * This is a vector instead of set so that a custom sort order can
	 * be used. It should normally be sorted lexicographically so that
	 * the list of options is not confusing.
	 *
	 * @param[in] shell Shell instance that has a command line matching
	 *                  this command.
	 * @param[in] current_arguments Command line arguments prior to (but
	 *                              excluding) the argument being
	 *                              completed.
	 * @param[in] next_argument Next argument (the one being completed).
	 * @return Possible values for the next argument on the command
	 *         line.
	 * @since 2.0.0
	 */
    using argument_completion_function =
        std::function<const std::vector<std::string>(Shell & shell, const std::vector<std::string> & current_arguments, const std::string & next_argument)>;

    /**
	 * Available command for execution on a Shell.
	 *
	 * @since 0.9.0
	 */
    class AvailableCommand {
      public:
        /**
		 * Construct a new command that is available for execution on a Shell.
		 *
		 * @since 0.9.0
		 */
        AvailableCommand(const Command & command);

        /**
		 * Get the name of the command.
		 *
		 * @return Name of the command as a std::vector of strings.
		 * @since 0.9.0
		 */
        inline const std::vector<std::string> & name() const {
            return name_;
        }

        /**
		 * Get the help text of the command's arguments.
		 *
		 * @return Help text for arguments that the command accepts as a std::vector of strings.
		 * @since 0.9.0
		 */
        inline const std::vector<std::string> & arguments() const {
            return arguments_;
        }

        /**
		 * Get the function to be used when the command is executed.
		 *
		 * @return Function that can be used to execute the command.
		 * @since 0.9.0
		 */
        inline const command_function & function() const {
            return command_.function_;
        };

        /**
		 * Get the function to be used to perform argument completions for the command.
		 *
		 * @return Function that can be used to perform argument completions for the command.
		 * @since 0.9.0
		 */
        inline const argument_completion_function & arg_function() const {
            return command_.arg_function_;
        };

        /**
		 * Get the shell flags that must be set for this command to be available.
		 *
		 * @return Shell flags.
		 * @since 0.9.0
		 */
        inline int flags() const {
            return command_.flags_;
        }

        /**
		 * Get the shell flags that must not be set for this command to be available.
		 *
		 * @return Shell flags.
		 * @since 0.9.0
		 */
        inline int not_flags() const {
            return command_.not_flags_;
        }

      private:
        const Commands::Command & command_;   /*!< Command that is available. @since 0.9.0 */
        std::vector<std::string>  name_;      /*!< Name of the command. @since 0.9.0 */
        std::vector<std::string>  arguments_; /*!< Help text for arguments that the command accepts. @since 0.9.0 */
    };

    /**
	 * Available commands in the shell's context with the current flags.
	 *
	 * Iterators are invalidated if the commands, context or flags for
	 * the shell change.
	 *
	 * @since 0.9.0
	 */
    class AvailableCommands {
      public:
        using command_iterator = std::multimap<unsigned int, Command>::const_iterator; /*!< Type of the underlying command iterator. @since 0.9.0 */

        /**
		 * Iterator of available commands for execution on a Shell.
		 *
		 * @since 0.9.0
		 */
        class const_iterator {
          public:
            using iterator_category = std::bidirectional_iterator_tag; /*!< Category of this iterator (bidirectional).  @since 0.9.0 */
            using difference_type   = std::size_t;                     /*!< Type used by std::distance(). @since 0.9.0 */
            using value_type        = AvailableCommand;                /*!< Type of this iterator's values. @since 0.9.0 */
            using pointer           = const value_type *;              /*!< Pointer type of this iterator. @since 0.9.0 */
            using reference         = const value_type &;              /*!< Reference type of this iterator. @since 0.9.0 */

            /**
			 * Create an iterator describing an available command in a
			 * shell with the current flags.
			 *
			 * Iterators are invalidated if the commands, context or flags for
			 * the shell change.
			 *
			 * @param[in] shell Shell that is accessing commands.
			 * @param[in] begin Beginning of command iterators.
			 * @param[in] command Initial command iterator.
			 * @param[in] end End of command iterators.
			 * @since 0.9.0
			 */
            const_iterator(const Shell & shell, const command_iterator & begin, const command_iterator & command, const command_iterator & end);

            /**
			 * Dereference the current available command.
			 *
			 * @returns A pointer to the current available command,
			 *          that remains valid only while the iterator at
			 *          this position exists.
			 * @since 0.9.0
			 */
            inline reference operator*() const {
                return *available_command_;
            }
            /**
			 * Access the current available command.
			 *
			 * @returns A reference to the current available command,
			 *          that remains valid only while the iterator at
			 *          this position exists.
			 * @since 0.9.0
			 */
            inline pointer operator->() const {
                return available_command_.get();
            }

            /**
			 * Pre-increment the current iterator to the next
			 * available command.
			 *
			 * @returns The iterator to the next available command.
			 * @since 0.9.0
			 */
            const_iterator & operator++();
            /**
			 * Post-increment the current iterator to the next
			 * available command.
			 *
			 * @returns The current iterator.
			 * @since 0.9.0
			 */
            inline const_iterator operator++(int) {
                auto tmp = *this;
                ++(*this);
                return tmp;
            }

            /**
			 * Pre-decrement the current iterator to the previous
			 * available command.
			 *
			 * @returns The iterator to the previous available command.
			 * @since 0.9.0
			 */
            const_iterator & operator--();
            /**
			 * Post-decrement the current iterator to the previous
			 * available command.
			 *
			 * @returns The current iterator.
			 * @since 0.9.0
			 */
            inline const_iterator operator--(int) {
                auto tmp = *this;
                --(*this);
                return tmp;
            }

            /**
			 * Compare an available commands iterator to another available
			 * commands iterator for equality.
			 *
			 * @param[in] lhs Left-hand side available commands iterator.
			 * @param[in] rhs Right-hand side available commands iterator.
			 * @return True if the available commands iterators are equal, otherwise false.
			 * @since 0.9.0
			 */
            friend inline bool operator==(const const_iterator & lhs, const const_iterator & rhs) {
                return lhs.command_ == rhs.command_;
            }
            /**
			 * Compare an available commands iterator to another available
			 * commands iterator for inequality.
			 *
			 * @param[in] lhs Left-hand side available commands iterator.
			 * @param[in] rhs Right-hand side available commands iterator.
			 * @return True if the available commands iterators are not equal, otherwise false.
			 * @since 0.9.0
			 */
            friend inline bool operator!=(const const_iterator & lhs, const const_iterator & rhs) {
                return lhs.command_ != rhs.command_;
            }

          private:
            /**
			 * Update the available command from the current command iterator.
			 *
			 * @since 0.9.0
			 */
            void update();

            const Shell &                     shell_;             /*!< Shell that is accessing commands. @since 0.9.0 */
            const command_iterator            begin_;             /*!< Beginning of command iterators. @since 0.9.0 */
            command_iterator                  command_;           /*!< Current command iterator. @since 0.9.0 */
            const command_iterator            end_;               /*!< End of command iterators. @since 0.9.0 */
            std::shared_ptr<AvailableCommand> available_command_; /*!< Current available command. @since 0.9.0 */
        };

        /**
		 * Create an iterable object describing available commands in a
		 * shell with the current flags.
		 *
		 * Iterators are invalidated if the commands, context or flags for
		 * the shell change.
		 *
		 * @param[in] shell Shell that is accessing commands.
		 * @param[in] begin Beginning of command iterators.
		 * @param[in] end End of command iterators.
		 * @since 0.9.0
		 */
        AvailableCommands(const Shell & shell, const command_iterator & begin, const command_iterator & end);

        /**
		 * Returns an iterator to the first command.
		 *
		 * @since 0.9.0
		 */
        inline const_iterator begin() const {
            return cbegin();
        }
        /**
		 * Returns an iterator to the first command.
		 *
		 * @since 0.9.0
		 */
        const_iterator cbegin() const;
        /**
		 * Returns an iterator to the element following the last command.
		 *
		 * @since 0.9.0
		 */
        inline const_iterator end() const {
            return cend();
        }
        /**
		 * Returns an iterator to the element following the last command.
		 *
		 * @since 0.9.0
		 */
        const_iterator cend() const;

      private:
        const Shell &    shell_; /*!< Shell that is accessing commands. @since 0.9.0 */
        command_iterator begin_; /*!< Beginning of command iterators. @since 0.9.0 */
        command_iterator end_;   /*!< End of command iterators. @since 0.9.0 */
    };

    /**
	 * Construct a new container of commands for use by a Shell.
	 *
	 * This should normally be stored in a std::shared_ptr and reused.
	 *
	 * @since 0.1.0
	 */
    Commands()  = default;
    ~Commands() = default;

    /**
	 * Add a command with no arguments to the list of commands in this
	 * container.
	 *
	 * The shell context for the command will default to 0 and not
	 * require any flags for it to be available.
	 *
	 * @param[in] name Name of the command as a std::vector of strings.
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @since 0.2.0
	 */
    void add_command(const string_vector & name, command_function function);
    /**
	 * Add a command with arguments to the list of commands in this
	 * container.
	 *
	 * The shell context for the command will default to 0 and not
	 * require any flags for it to be available.
	 *
	 * @param[in] name Name of the command as a std::vector of strings.
	 * @param[in] arguments Help text for arguments that the command
	 *                      accepts as a std::vector of strings
	 *                      (use "<" to indicate a required argument).
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @since 0.2.0
	 */
    void add_command(const string_vector & name, const string_vector & arguments, command_function function);
    /**
	 * Add a command with arguments and automatic argument completion
	 * to the list of commands in this container.
	 *
	 * The shell context for the command will default to 0 and not
	 * require any flags for it to be available.
	 *
	 * @param[in] name Name of the command as a std::vector of strings.
	 * @param[in] arguments Help text for arguments that the command
	 *                      accepts as a std::vector of strings
	 *                      (use "<" to indicate a required argument).
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @param[in] arg_function Function to be used to perform argument
	 *                         completions for this command.
	 * @since 0.2.0
	 */
    void add_command(const string_vector & name, const string_vector & arguments, command_function function, argument_completion_function arg_function);

    /**
	 * Add a command with no arguments to the list of commands in this
	 * container.
	 *
	 * The command will not require any flags for it to be available.
	 *
	 * @param[in] context Shell context in which this command is
	 *                    available.
	 * @param[in] name Name of the command as a std::vector of 
	 *                 strings.
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @since 0.8.0
	 */
    void add_command(unsigned int context, const string_vector & name, command_function function);
    /**
	 * Add a command with arguments to the list of commands in this
	 * container.
	 *
	 * The command will not require any flags for it to be available.
	 *
	 * @param[in] context Shell context in which this command is
	 *                    available.
	 * @param[in] name Name of the command as a std::vector of 
	 *                 strings.
	 * @param[in] arguments Help text for arguments that the command
	 *                      accepts as a std::vector of  strings
	 *                      (use "<" to indicate a required argument).
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @since 0.8.0
	 */
    void add_command(unsigned int context, const string_vector & name, const string_vector & arguments, command_function function);
    /**
	 * Add a command with arguments and automatic argument completion
	 * to the list of commands in this container.
	 *
	 * The command will not require any flags for it to be available.
	 *
	 * @param[in] context Shell context in which this command is
	 *                    available.
	 * @param[in] name Name of the command as a std::vector of 
	 *                 strings.
	 * @param[in] arguments Help text for arguments that the command
	 *                      accepts as a std::vector of strings
	 *                      (use "<" to indicate a required argument).
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @param[in] arg_function Function to be used to perform argument
	 *                         completions for this command.
	 * @since 0.8.0
	 */
    void add_command(unsigned int                 context,
                     const string_vector &        name,
                     const string_vector &        arguments,
                     command_function             function,
                     argument_completion_function arg_function);
    /**
	 * Add a command with no arguments to the list of commands in this
	 * container.
	 *
	 * @param[in] context Shell context in which this command is
	 *                    available.
	 * @param[in] flags Shell flags that must be set for this command
	 *                  to be available.
	 * @param[in] name Name of the command as a std::vector of
	 *                 strings.
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @since 0.1.0
	 */
    void add_command(unsigned int context, unsigned int flags, const string_vector & name, command_function function);
    /**
	 * Add a command with arguments to the list of commands in this
	 * container.
	 *
	 * @param[in] context Shell context in which this command is
	 *                    available.
	 * @param[in] flags Shell flags that must be set for this command
	 *                  to be available.
	 * @param[in] name Name of the command as a std::vector of 
	 *                 strings.
	 * @param[in] arguments Help text for arguments that the command
	 *                      accepts as a std::vector of strings
	 *                      (use "<" to indicate a required argument).
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @since 0.1.0
	 */
    void add_command(unsigned int context, unsigned int flags, const string_vector & name, const string_vector & arguments, command_function function);
    /**
	 * Add a command with arguments and automatic argument completion
	 * to the list of commands in this container.
	 *
	 * @param[in] context Shell context in which this command is
	 *                    available.
	 * @param[in] flags Shell flags that must be set for this command
	 *                  to be available.
	 * @param[in] name Name of the command as a std::vector of 
	 *                 strings.
	 * @param[in] arguments Help text for arguments that the command
	 *                      accepts as a std::vector of strings
	 *                      (use "<" to indicate a required argument).
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @param[in] arg_function Function to be used to perform argument
	 *                         completions for this command.
	 * @since 0.1.0
	 */
    void add_command(unsigned int                 context,
                     unsigned int                 flags,
                     const string_vector &        name,
                     const string_vector &        arguments,
                     command_function             function,
                     argument_completion_function arg_function);
    /**
	 * Add a command with no arguments to the list of commands in this
	 * container.
	 *
	 * @param[in] context Shell context in which this command is
	 *                    available.
	 * @param[in] flags Shell flags that must be set for this command
	 *                  to be available.
	 * @param[in] not_flags Shell flags that must not be set for this command
	 *                      to be available.
	 * @param[in] name Name of the command as a std::vector of 
	 *                 strings.
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @since 0.8.0
	 */
    void add_command(unsigned int context, unsigned int flags, unsigned int not_flags, const string_vector & name, command_function function);
    /**
	 * Add a command with arguments to the list of commands in this
	 * container.
	 *
	 * @param[in] context Shell context in which this command is
	 *                    available.
	 * @param[in] flags Shell flags that must be set for this command
	 *                  to be available.
	 * @param[in] not_flags Shell flags that must not be set for this command
	 *                      to be available.
	 * @param[in] name Name of the command as a std::vector of 
	 *                 strings.
	 * @param[in] arguments Help text for arguments that the command
	 *                      accepts as a std::vector of strings
	 *                      (use "<" to indicate a required argument).
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @since 0.8.0
	 */
    void add_command(unsigned int          context,
                     unsigned int          flags,
                     unsigned int          not_flags,
                     const string_vector & name,
                     const string_vector & arguments,
                     command_function      function);
    /**
	 * Add a command with arguments and automatic argument completion
	 * to the list of commands in this container.
	 *
	 * @param[in] context Shell context in which this command is
	 *                    available.
	 * @param[in] flags Shell flags that must be set for this command
	 *                  to be available.
	 * @param[in] not_flags Shell flags that must not be set for this command
	 *                      to be available.
	 * @param[in] name Name of the command as a std::vector of 
	 *                 strings.
	 * @param[in] arguments Help text for arguments that the command
	 *                      accepts as a std::vector of strings
	 *                      (use "<" to indicate a required argument).
	 * @param[in] function Function to be used when the command is
	 *                     executed.
	 * @param[in] arg_function Function to be used to perform argument
	 *                         completions for this command.
	 * @since 0.8.0
	 */
    void add_command(unsigned int                 context,
                     unsigned int                 flags,
                     unsigned int                 not_flags,
                     const string_vector &        name,
                     const string_vector &        arguments,
                     command_function             function,
                     argument_completion_function arg_function);

    /**
	 * Execute a command for a Shell if it exists in the current
	 * context and with the current flags.
	 *
	 * @param[in] shell Shell that is executing the command.
	 * @param[in] command_line Command line parameters.
	 * @return An object describing the result of the command execution
	 *         operation.
	 * @since 0.1.0
	 */
    Execution execute_command(Shell & shell, CommandLine && command_line);

    /**
	 * Complete a partial command for a Shell if it exists in the
	 * current context and with the current flags.
	 *
	 * @param[in] shell Shell that is completing the command.
	 * @param[in] command_line Command line parameters.
	 * @return An object describing the result of the command
	 *         completion operation.
	 * @since 0.1.0
	 */
    Completion complete_command(Shell & shell, const CommandLine & command_line);

    /**
	 * Get the available commands in the current context and with the
	 * current flags.
	 *
	 * This iterable object and its iterators are invalidated if the
	 * commands, context or flags for the shell change.
	 *
	 * @param[in] shell Shell that is accessing commands.
	 * @return An iterable object describing the available commands.
	 * @since 0.9.0
	 */
    AvailableCommands available_commands(const Shell & shell) const;

  private:
    /**
	 * Command for execution on a Shell.
	 * @since 0.1.0
	 */
    class Command {
      public:
        /**
		 * Create a command for execution on a Shell.
		 *
		 * @param[in] flags Shell flags that must be set for this command
		 *                  to be available.
		 * @param[in] not_flags Shell flags that must not be set for this
		 *                      command to be available.
		 * @param[in] name Name of the command as a std::vector of 
		 *                 strings.
		 * @param[in] arguments Help text for arguments that the command
		 *                      accepts as a std::vector of strings
		 *                      (use "<" to indicate a required argument).
		 * @param[in] function Function to be used when the command is
		 *                     executed.
		 * @param[in] arg_function Function to be used to perform argument
		 *                         completions for this command.
		 * @since 0.8.0
		 */
        Command(unsigned int                 flags,
                unsigned int                 not_flags,
                const string_vector          name,
                const string_vector          arguments,
                command_function             function,
                argument_completion_function arg_function);
        ~Command();

        /**
		 * Determine the minimum number of arguments for this command
		 * based on the help text for the arguments that begin with the
		 * "<" character.
		 *
		 * @return The minimum number of arguments for this command.
		 * @since 0.1.0
		 */
        size_t minimum_arguments() const;
        /**
		 * Determine the maximum number of arguments for this command
		 * based on the length of help text for the arguments.
		 *
		 * @return The maximum number of arguments for this command.
		 * @since 0.1.0
		 */
        inline size_t maximum_arguments() const {
            return arguments_.size();
        }

        unsigned int                 flags_;        /*!< Shell flags that must be set for this command to be available. @since 0.1.0 */
        unsigned int                 not_flags_;    /*!< Shell flags that must not be set for this command to be available. @since 0.8.0 */
        const string_vector          name_;         /*!< Name of the command as a std::vector of strings. @since 0.1.0 */
        const string_vector          arguments_;    /*!< Help text for arguments that the command accepts as a std::vector of strings. @since 0.1.0 */
        command_function             function_;     /*!< Function to be used when the command is executed. @since 0.1.0 */
        argument_completion_function arg_function_; /*!< Function to be used to perform argument completions for this command. @since 0.1.0 */

      private:
        Command(const Command &)             = delete;
        Command & operator=(const Command &) = delete;
    };

    /**
	 * Result of a command find operation.
	 *
	 * Each matching command is returned in a map grouped by size.
	 * @since 0.1.0
	 */
    struct Match {
        std::multimap<size_t, const Command *> exact; /*!< Commands that match the command line exactly, grouped by the size of the command names. @since 0.1.0 */
        std::multimap<size_t, const Command *> partial; /*!< Commands that the command line partially matches, grouped by the size of the command names. @since 0.1.0 */
        std::vector<const Command *> all;               /*!< Commands that match the command line, in defined order. @since 0.7.6 */
    };

    /**
	 * Find commands by matching them against the command line.
	 *
	 * @param[in] shell Shell that is accessing commands.
	 * @param[in] command_line Command line parameters.
	 * @return An object describing the result of the command find
	 *         operation.
	 * @since 0.1.0
	 */
    Match find_command(Shell & shell, const CommandLine & command_line);

    /**
	 * Find the longest common prefix from a shortest match of commands.
	 *
	 * @param[in] commands All commands that matched (at least 2).
	 * @param[out] longest_name The longest common prefix as a list of
	 *                          strings.
	 * @return True if the longest common prefix is made up of whole
	 *          components, false if the last part is constructed from
	 *          a partial component.
	 * @since 0.1.0
	 */
    static bool find_longest_common_prefix(const std::multimap<size_t, const Command *> & commands, std::vector<std::string> & longest_name);

    /**
	 * Find the longest common prefix from a list of potential arguments.
	 *
	 * @param[in] arguments All potential arguments (at least 2).
	 * @return The longest common prefix, which could be empty.
	 * @since 0.1.0
	 */
    static std::string find_longest_common_prefix(const std::vector<std::string> & arguments);

    std::multimap<unsigned int, Command> commands_; /*!< Commands stored in this container, separated by context. @since 0.1.0 */
};

/**
 * Base class for a command shell.
 *
 * Must be constructed within a std::shared_ptr.
 *
 * @since 0.1.0
 */
class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Handler, public ::Stream {
  public:
    static constexpr size_t MAX_COMMAND_LINE_LENGTH = 80; /*!< Maximum length of a command line. @since 0.1.0 */
    static constexpr size_t MAX_LOG_MESSAGES        = 20; /*!< Maximum number of log messages to buffer before they are output. @since 0.1.0 */

    // added for EMS-ESP
    static constexpr uint8_t MAX_LINES = 5; /*!< Maximum lines in buffer */

    /**
	 * Function to handle the response to a password entry prompt.
	 *
	 * @param[in] shell Shell instance where the password entry prompt
	 *                  was requested.
	 * @param[in] completed Password entry at the prompt was either
	 *                      completed (true) or aborted (false).
	 * @param[in] password Password entered at the prompt (may be
	 *                     empty).
	 * @since 0.1.0
	 */
    using password_function = std::function<void(Shell & shell, bool completed, const std::string & password)>;
    /**
	 * Function to handle the end of an execution delay.
	 *
	 * @param[in] shell Shell instance where execution was delayed.
	 * @since 0.1.0
	 */
    using delay_function = std::function<void(Shell & shell)>;
    /**
	 * Function to handle a blocking operation.
	 *
	 * @param[in] shell Shell instance where execution is blocked.
	 * @param[in] stop Request to return so that the shell can stop
	 *                 (true) or continue (false).
	 * @return True if the blocking operation is finished, otherwise
	 *         false.
	 * @since 0.2.0
	 */
    using blocking_function = std::function<bool(Shell & shell, bool stop)>;

    /**
	 * Create a new Shell operating on a Stream with the given commands,
	 * default context and initial flags.
	 *
	 * The default context is put on the stack and cannot be removed.
	 *
	 * The Stream must remain valid until the Shell has been destroyed.
	 * Monitor the Shell using a std::weak_ptr and destroy the Stream
	 * only after it has expired.
	 *
	 * @param[in] stream Stream used for the input/output of this shell.
	 * @param[in] commands Commands available for execution in this shell.
	 * @param[in] context Default context for the shell.
	 * @param[in] flags Initial flags for the shell.
	 * @since 3.0.0
	 */
    Shell(Stream & stream, std::shared_ptr<Commands> commands, unsigned int context = 0, unsigned int flags = 0);

    ~Shell() = default;

    /**
	 * Loop through all registered shell objects.
	 *
	 * Call loop_one() on every Shell (if it has not been stopped).
	 * Any Shell that is stopped is then unregistered.
	 *
	 * @since 0.1.0
	 */
    static void loop_all();

    /**
	 * Perform startup process for this shell.
	 *
	 * Register as a uuid::log::Handler at the uuid::log::Level::NOTICE
	 * log level, output the banner and	register this Shell with the
	 * loop_all() set.
	 *
	 * The started() function will be called after startup is complete.
	 *
	 * Do not call this function more than once.
	 *
	 * @since 0.1.0
	 */
    void start();
    /**
	 * Perform one execution step of this shell.
	 *
	 * Depending on the current mode, either read input characters and
	 * process them or check if an execution delay has passed.
	 *
	 * @since 0.1.0
	 */
    void loop_one();
    /**
	 * Determine if this shell is still running.
	 *
	 * @return Running status of this Shell, false if the Shell has
	 *         been stopped using stop().
	 * @since 0.1.0
	 */
    bool running() const;
    /**
	 * Stop this shell from running.
	 *
	 * If the shell is currently executing a blocking function, that
	 * must complete before the shell will stop.
	 *
	 * It is not possible to restart the Shell, which must be destroyed
	 * after it has been stopped.
	 *
	 * @since 0.1.0
	 */
    void stop();

    /**
	 * Get the built-in uuid::log::Logger instance for shells.
	 *
	 * @return Logger instance.
	 * @since 0.1.0
	 */
    static const uuid::log::Logger & logger();
    /**
	 * Add a new log message.
	 *
	 * This will be put in a queue for output at the next loop_one()
	 * process. The queue has a maximum size of maximum_log_messages()
	 * and will discard the oldest message first.
	 *
	 * @param[in] message New log message, shared by all handlers.
	 * @since 0.1.0
	 */
    virtual void operator<<(std::shared_ptr<uuid::log::Message> message) override;
    /**
	 * Get the current log level.
	 *
	 * This only affects newly received log messages, not messages that
	 * have already been queued.
	 *
	 * @return The current log level.
	 * @since 0.6.0
	 */
    uuid::log::Level log_level() const;
    /**
	 * Set the current log level.
	 *
	 * This only affects newly received log messages, not messages that
	 * have already been queued.
	 *
	 * @param[in] level Minimum log level that the shell will receive
	 *                  messages for.
	 * @since 0.6.0
	 */
    void log_level(uuid::log::Level level);

    /**
	 * Get the maximum length of a command line.
	 *
	 * @return The maximum length of a command line in bytes.
	 * @since 0.6.0
	 */
    size_t maximum_command_line_length() const;
    /**
	 * Set the maximum length of a command line.
	 *
	 * Defaults to Shell::MAX_COMMAND_LINE_LENGTH.
	 *
	 * @param[in] length The maximum length of a command line in bytes.
	 * @since 0.6.0
	 */
    void maximum_command_line_length(size_t length);
    /**
	 * Get the maximum number of queued log messages.
	 *
	 * @return The maximum number of queued log messages.
	 * @since 0.6.0
	 */
    size_t maximum_log_messages() const;
    /**
	 * Set the maximum number of queued log messages.
	 *
	 * Defaults to Shell::MAX_LOG_MESSAGES.
	 *
	 * @param[in] count The maximum number of queued log messages.
	 * @since 0.6.0
	 */
    void maximum_log_messages(size_t count);
    /**
	 * Get the idle timeout.
	 *
	 * @return The idle timeout in seconds.
	 * @since 0.7.0
	 */
    unsigned long idle_timeout() const;
    /**
	 * Set the idle timeout.
	 *
	 * Defaults to 0 (no idle timeout).
	 *
	 * @param[in] timeout Idle timeout in seconds.
	 * @since 0.7.0
	 */
    void idle_timeout(unsigned long timeout);

    /**
	 * Get the context at the top of the stack.
	 *
	 * The current context affects which commands are available.
	 *
	 * @return Current shell context.
	 * @since 0.1.0
	 */
    inline unsigned int context() const {
        if (!context_.empty()) {
            return context_.back();
        } else {
            return 0;
        }
    }
    /**
	 * Push a new context onto the stack.
	 *
	 * The current context affects which commands are available.
	 *
	 * @param[in] context New context.
	 * @since 0.1.0
	 */
    inline void enter_context(unsigned int context) {
        context_.emplace_back(context);
    }
    /**
	 * Pop a context off the stack.
	 *
	 * The current context affects which commands are available.
	 *
	 * @return True if there was a context to be exited, false if there
	 *         are no more contexts to exit.
	 * @since 0.1.0
	 */
    virtual bool exit_context();

    /**
	 * Add one or more flags to the current flags.
	 *
	 * Flags are not affected by execution context. The current flags
	 * affect which commands are available (for access control).
	 *
	 * @param[in] flags Flag bits to add.
	 * @since 0.1.0
	 */
    inline void add_flags(unsigned int flags) {
        flags_ |= flags;
    }
    /**
	 * Check if the current flags include all of the specified flags
	 * and none of the specified not_flags.
	 *
	 * Flags are not affected by execution context. The current flags
	 * affect which commands are available (for access control).
	 *
	 * @param[in] flags Flag bits to check for presence of.
	 * @param[in] not_flags Flag bits to check for absence of.
	 * @return True if the current flags includes all of the specified
	 *         flags and none of the specified not_flags, otherwise false.
	 * @since 0.8.0
	 */
    inline bool has_flags(unsigned int flags, unsigned int not_flags = 0) const {
        return has_all_flags(flags) && !has_any_flags(not_flags);
    }
    /**
	 * Check if the current flags include all of the specified flags.
	 *
	 * Flags are not affected by execution context. The current flags
	 * affect which commands are available (for access control).
	 *
	 * @param[in] flags Flag bits to check for.
	 * @return True if the current flags includes all of the specified
	 *         flags, otherwise false.
	 * @since 0.8.0
	 */
    inline bool has_all_flags(unsigned int flags) const {
        return (flags_ & flags) == flags;
    }
    /**
	 * Check if the current flags include any of the specified flags.
	 *
	 * Flags are not affected by execution context. The current flags
	 * affect which commands are available (for access control).
	 *
	 * @param[in] flags Flag bits to check for.
	 * @return True if the current flags includes any of the specified
	 *         flags, otherwise false.
	 * @since 0.8.0
	 */
    inline bool has_any_flags(unsigned int flags) const {
        return (flags_ & flags) != 0;
    }
    /**
	 * Remove one or more flags from the current flags.
	 *
	 * Flags are not affected by execution context. The current flags
	 * affect which commands are available (for access control).
	 *
	 * @param[in] flags Flag bits to remove.
	 * @since 0.1.0
	 */
    inline void remove_flags(unsigned int flags) {
        flags_ &= ~flags;
    }

    /**
	 * Prompt for a password to be entered on this shell.
	 *
	 * Password entry is not visible and can be interrupted by the
	 * user.
	 *
	 * The shell must not be currently executing a blocking function.
	 *
	 * @param[in] prompt Message to display prompting for password
	 *                   input.
	 * @param[in] function Function to be executed after the password
	 *                     has been entered prior to resuming normal
	 *                     execution.
	 * @since 0.1.0
	 */
    void enter_password(const char * prompt, password_function function);

    /**
	 * Stop executing anything on this shell for a period of time.
	 *
	 * There is an assumption that 2^64 milliseconds uptime will always
	 * be enough time for this delay process.
	 *
	 * The shell must not be currently executing a blocking function.
	 *
	 * @param[in] ms Time in milliseconds to delay execution for.
	 * @param[in] function Function to be executed at a future time,
	 *                     prior to resuming normal execution.
	 * @since 0.1.0
	 */
    void delay_for(unsigned long ms, delay_function function);

    /**
	 * Stop executing anything on this shell until a future time is
	 * reached.
	 *
	 * There is an assumption that 2^64 milliseconds uptime will always
	 * be enough time for this delay process.
	 *
	 * The reference time is uuid::get_uptime_ms().
	 *
	 * The shell must not be currently executing a blocking function.
	 *
	 * @param[in] ms Uptime in the future (in milliseconds) when the
	 *               function should be executed.
	 * @param[in] function Function to be executed at a future time,
	 *                     prior to resuming normal execution.
	 * @since 0.1.0
	 */
    void delay_until(uint64_t ms, delay_function function);

    /**
	 * Execute a blocking function on this shell until it returns true.
	 *
	 * The function will be executed every time loop_one() is called,
	 * which allows commands that do not complete immediately to be run
	 * asynchronously.
	 *
	 * The shell must not be currently executing a blocking function.
	 *
	 * @param[in] function Function to be executed on every loop_one()
	 *                     until normal execution resumes.
	 */
    void block_with(blocking_function function);

    /**
	 * Check for available input.
	 *
	 * The shell must be currently executing a blocking function
	 * otherwise it will always return 0.
	 *
	 * @return The number of bytes available to read.
	 * @since 0.2.0
	 */
    int available() final override;
    /**
	 * Read one byte from the available input.
	 *
	 * The shell must be currently executing a blocking function
	 * otherwise it will always return -1.
	 *
	 * @return An unsigned char if input is available, otherwise -1.
	 * @since 0.2.0
	 */
    int read() final override;
    /**
	 * Read one byte from the available input without advancing to the
	 * next one.
	 *
	 * The shell must be currently executing a blocking function
	 * otherwise it will always return -1.
	 *
	 * @return An unsigned char if input is available, otherwise -1.
	 * @since 0.2.0
	 */
    int peek() final override;

    /**
	 * Write one byte to the output stream.
	 *
	 * @param[in] data Data to be output.
	 * @return The number of bytes that were output.
	 * @since 3.0.0
	 */
    size_t write(uint8_t data) final override;
    /**
	 * Write an array of bytes to the output stream.
	 *
	 * @param[in] buffer Buffer to be output.
	 * @param[in] size Length of the buffer.
	 * @return The number of bytes that were output.
	 * @since 3.0.0
	 */
    size_t write(const uint8_t * buffer, size_t size) final override;
    /**
	 * Does nothing.
	 *
	 * This is a pure virtual function in Arduino's Stream class, which
	 * makes no sense because that class is for input and this is an
	 * output function. Later versions move it to Print as an empty
	 * virtual function so this is here for backward compatibility.
	 *
	 * @since 3.0.0
	 */
    void flush() final override;

    using ::Print::print; /*!< Include standard Arduino print() functions. */
    /**
	 * Output a string.
	 *
	 * @param[in] data String to be output.
	 * @return The number of bytes that were output.
	 * @since 0.1.0
	 */
    size_t print(const std::string & data);
    using ::Print::println; /*!< Include standard Arduino println() functions. */
    /**
	 * Output a string followed by CRLF end of line characters.
	 *
	 * @param[in] data String to be output.
	 * @return The number of bytes that were output, including CRLF.
	 * @since 0.1.0
	 */
    size_t println(const std::string & data);
    /**
	 * Output a message.
	 *
	 * @param[in] format Format string.
	 * @param[in] ... Format string arguments.
	 * @return The number of bytes that were output.
	 * @since 0.1.0
	 */
    size_t printf(const char * format, ...) /* __attribute__((format(printf, 2, 3))) */;

    /**
	 * Output a message followed by CRLF end of line characters.
	 *
	 * @param[in] format Format string.
	 * @param[in] ... Format string arguments.
	 * @return The number of bytes that were output, including CRLF.
	 * @since 0.1.0
	 */
    size_t printfln(const char * format, ...) /* __attribute__((format (printf, 2, 3))) */;

    /**
	 * Get the available commands in the current context and with the
	 * current flags.
	 *
	 * The iterable object and its iterators are invalidated if the
	 * commands, context or flags for the shell change.
	 *
	 * @return An iterable object describing the available commands.
	 * @since 0.9.0
	 */
    Commands::AvailableCommands available_commands() const;
    /**
	 * Output a list of all available commands with their arguments.
	 *
	 * @since 0.4.0
	 */
    void print_all_available_commands();

  protected:
    /**
	 * Output ANSI escape sequence to erase the current line.
	 *
	 * @since 0.1.0
	 */
    virtual void erase_current_line();
    /**
	 * Output ANSI escape sequence to erase characters.
	 *
	 * @param[in] count The number of characters to erase.
	 * @since 0.1.0
	 */
    virtual void erase_characters(size_t count);

    /**
	 * Startup complete event.
	 *
	 * The shell is ready to start executing commands.
	 * @since 0.1.0
	 */
    virtual void started();
    /**
	 * Output the startup banner.
	 *
	 * There is no default banner.
	 * @since 0.1.0
	 */
    virtual void display_banner();
    /**
	 * Get the hostname to be included in the command prompt.
	 *
	 * Defaults to "".
	 *
	 * @return The current hostname of the device, or an empty string
	 *         for no hostname.
	 * @since 0.1.0
	 */
    virtual std::string hostname_text();
    /**
	 * Get the text indicating the current context, to be included in
	 * the command prompt.
	 *
	 * Defaults to "".
	 *
	 * @return Text indicating the current context, or an empty string
	 *         for no context description.
	 * @since 0.1.0
	 */
    virtual std::string context_text();
    /**
	 * Get the prefix to be used at the beginning of the command
	 * prompt.
	 *
	 * Defaults to "".
	 *
	 * @return Text for the beginning of the command prompt, or an
	 *         empty string.
	 * @since 0.1.0
	 */
    virtual std::string prompt_prefix();
    /**
	 * Get the prefix to be used at the end of the command prompt.
	 *
	 * Defaults to "$".
	 *
	 * @return Text for the end of the command prompt, or an empty
	 *         string.
	 * @since 0.1.0
	 */
    virtual std::string prompt_suffix();
    /**
	 * The end of transmission character has been received.
	 *
	 * A command can be invoked using invoke_command(). All other
	 * actions may result in an inconsistent display of the command
	 * prompt. If the shell is stopped without invoking a command then
	 * a blank line should usually be printed first.
	 *
	 * If an idle timeout is set then the default action (since 0.7.2)
	 * is to stop the shell, otherwise there is no default action.
	 * @since 0.1.0
	 */
    virtual void end_of_transmission();
    /**
	 * Stop request event.
	 *
	 * The shell is going to stop executing.
	 * @since 0.1.0
	 */
    virtual void stopped();

    // change for EMS-ESP to make public so can be used in test.cpp
  public:
    /**
	 * Invoke a command on the shell.
	 *
	 * This will output a prompt with the provided command line and
	 * then try to execute it.
	 *
	 * Intended for use from end_of_transmission() to execute an "exit"
	 * or "logout" command.
	 *
	 * @param[in] line The command line to be executed.
	 * @since 0.1.0
	 */
    void invoke_command(const std::string & line);

  private:
    /**
	 * Current mode of the shell.
	 *
	 * @since 0.1.0
	 */
    enum class Mode : uint8_t {
        NORMAL,   /*!< Normal command execution. @since 0.1.0 */
        PASSWORD, /*!< Password entry prompt. @since 0.1.0 */
        DELAY,    /*!< Delay execution until a future time. @since 0.1.0 */
        BLOCKING, /*!< Block execution by calling a function repeatedly. @since 0.2.0 */
    };

    /**
	 * Base class of data for a shell mode.
	 *
	 * @since 0.1.0
	 */
    class ModeData {
      public:
        virtual ~ModeData() = default;

      protected:
        ModeData() = default;
    };

    /**
	 * Data for the Mode::PASSWORD shell mode.
	 *
	 * @since 0.1.0
	 */
    class PasswordData : public ModeData {
      public:
        /**
		 * Create Mode::PASSWORD shell mode data.
		 *
		 * @param[in] password_prompt Message to display prompting for
		 *                            password input.
		 * @param[in] password_function Function to be executed after
		 *                              the password has been entered
		 *                              prior to resuming normal
		 *                              execution.
		 * @since 0.1.0
		 */
        PasswordData(const char * password_prompt, password_function && password_function);
        ~PasswordData() override = default;

        const char *      password_prompt_;   /*!< Prompt requesting password input. @since 0.1.0 */
        password_function password_function_; /*!< Function to execute after password entry. @since 0.1.0 */
    };

    /**
	 * Data for the Mode::DELAY shell mode.
	 *
	 * @since 0.1.0
	 */
    class DelayData : public ModeData {
      public:
        /**
		 * Create Mode::DELAY shell mode data.
		 *
		 * @param[in] delay_time Uptime in the future (in milliseconds)
		 *                       when the function should be executed.
		 * @param[in] delay_function Function to be executed at a
		 *                           future time, prior to resuming
		 *                           normal execution.
		 * @since 0.1.0
		 */
        DelayData(uint64_t delay_time, delay_function && delay_function);
        ~DelayData() override = default;

        uint64_t       delay_time_;     /*!< Future uptime to resume execution (in milliseconds). @since 0.1.0 */
        delay_function delay_function_; /*!< Function execute after delay. @since 0.1.0 */
    };

    /**
	 * Data for the Mode::BLOCKING shell mode.
	 *
	 * @since 0.2.0
	 */
    class BlockingData : public ModeData {
      public:
        /**
		 * Create Mode::DELAY shell mode data.
		 *
		 * @param[in] blocking_function Function to be executed on
		 *                              every loop_one() until it
		 *                              returns true.
		 * @since 0.2.0
		 */
        explicit BlockingData(blocking_function && blocking_function);
        ~BlockingData() override = default;

        blocking_function blocking_function_;         /*!< Function executed on every loop_one(). @since 0.2.0 */
        bool              consume_line_feed_ = true;  /*!< Stream input should try to consume the first line feed following a carriage return. @since 0.2.0 */
        bool              stop_              = false; /*!< There is a stop pending for the shell. @since 0.2.0 */
    };

    /**
	 * Log message that has been queued.
	 *
	 * Contains an identifier sequence to indicate when log messages
	 * could not be output because the queue discarded one or more
	 * messages.
	 *
	 * @since 0.1.0
	 */
    class QueuedLogMessage {
      public:
        /**
		 * Create a queued log message.
		 *
		 * @param[in] id Identifier to use for the log message on the queue.
		 * @param[in] content Log message content.
		 * @since 0.1.0
		 */
        QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content);
        ~QueuedLogMessage() = default;

        unsigned long                             id_;      /*!< Sequential identifier for this log message. @since 0.1.0 */
        std::shared_ptr<const uuid::log::Message> content_; /*!< Log message content. @since 0.1.0 */
    };

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

    /**
	 * Get registered running shells to be executed.
	 *
	 * @return Registered running shells to be executed.
	 * @since 0.7.4
	 */
    static std::set<std::shared_ptr<Shell>> & registered_shells();

    /**
	 * Perform one execution step in Mode::NORMAL mode.
	 *
	 * Read characters and execute commands or invoke tab completion.
	 *
	 * @since 0.1.0
	 */
    void loop_normal();
    /**
	 * Perform one execution step in Mode::PASSWORD mode.
	 *
	 * Read characters until interrupted or password entry is complete.
	 *
	 * @since 0.1.0
	 */
    void loop_password();
    /**
	 * Perform one execution step in Mode::DELAY mode.
	 *
	 * Wait until the delay uptime is reached.
	 *
	 * @since 0.1.0
	 */
    void loop_delay();
    /**
	 * Perform one execution step in Mode::BLOCKING mode.
	 *
	 * Wait until the function returns true.
	 *
	 * @since 0.2.0
	 */
    void loop_blocking();

    /**
	 * Output a prompt on the shell.
	 *
	 * Based on the current mode this will output the appropriate text,
	 * e.g. the command prompt with the current command line (for
	 * Mode::NORMAL mode).
	 *
	 * @since 0.1.0
	 */
    void display_prompt();
    /**
	 * Output queued log messages for this shell.
	 *
	 * @since 0.1.0
	 */
    void output_logs();
    /**
	 * Try to execute a command with the current command line.
	 *
	 * @since 0.1.0
	 */
    void process_command();
    /**
	 * Try to complete a command from the current command line.
	 *
	 * @since 0.1.0
	 */
    void process_completion();
    /**
	 * Finish password entry.
	 *
	 * The entered password is in the command line buffer.
	 *
	 * @param[in] completed Password entry at the prompt was either
	 *                      completed (true) or aborted (false).
	 * @since 0.1.0
	 */
    void process_password(bool completed);

    /**
	 * Check idle timeout expiry.
	 *
	 * @since 0.7.2
	 */
    void check_idle_timeout();

    /**
	 * Delete a word from the command line buffer.
	 *
	 * @param[in] display True if output to erased characters is
	 *                    required, false for no output.
	 * @since 0.1.0
	 */
    void delete_buffer_word(bool display);

    /**
	 * Output a message.
	 *
	 * @param[in] format Format string.
	 * @param[in] ap Variable arguments pointer for format string.
	 * @return The number of bytes that were output.
	 * @since 0.1.0
	 */
    size_t vprintf(const char * format, va_list ap);

    // added for EMS-ESP
    void        set_command_str(const char * str);
    std::string line_old_[MAX_LINES]; /*!< old Command line buffer.*/
    uint8_t     line_no_ = 0;
    uint8_t     cursor_  = 0; /*!< cursor position from end of line */
    uint8_t     esc_     = 0; /*!< esc sequence running */

    Stream &                  stream_;                               /*!< Stream used for the input/output of this shell. @since 3.0.0 */
    std::shared_ptr<Commands> commands_;                             /*!< Commands available for execution in this shell. @since 0.1.0 */
    std::deque<unsigned int, AllocatorPSRAM<unsigned int>> context_; /*!< Context stack for this shell. Affects which commands are available. Should never be empty. @since 0.1.0 */
    unsigned int flags_ = 0;                                         /*!< Current flags for this shell. Affects which commands are available. @since 0.1.0 */
#if UUID_CONSOLE_THREAD_SAFE
    mutable std::mutex mutex_; /*!< Mutex for queued log messages. @since 1.0.0 */
#endif
    unsigned long                                                 log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @since 0.1.0 */
    std::list<QueuedLogMessage, AllocatorPSRAM<QueuedLogMessage>> log_messages_; /*!< Queued log messages, in the order they were received. @since 0.1.0 */
    size_t                    maximum_log_messages_ = MAX_LOG_MESSAGES;          /*!< Maximum command line length in bytes. @since 0.6.0 */
    std::string               line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */
    size_t                    maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */
    unsigned char             previous_  = 0; /*!< Previous character that was entered on the command line. Used to detect CRLF line endings. @since 0.1.0 */
    Mode                      mode_      = Mode::NORMAL; /*!< Current execution mode. @since 0.1.0 */
    std::unique_ptr<ModeData> mode_data_ = nullptr;      /*!< Data associated with the current execution mode. @since 0.1.0 */
    bool                      stopped_   = false;        /*!< Indicates that the shell has been stopped. @since 0.1.0 */
    bool prompt_displayed_ = false; /*!< Indicates that a command prompt has been displayed, so that the output of invoke_command() is correct. @since 0.1.0 */
    uint64_t idle_time_    = 0;     /*!< Time the shell became idle. @since 0.7.0 */
    uint64_t idle_timeout_ = 0;     /*!< Idle timeout (in milliseconds). @since 0.7.0 */
};

/**
 * Representation of a command line, with parameters separated by
 * spaces and an optional trailing space.
 *
 * @since 0.4.0
 */
class CommandLine {
  public:
    /**
	 * Create an empty command line.
	 *
	 * @since 0.4.0
	 */
    CommandLine() = default;

    /**
	 * Parse a command line into separate parameters using built-in
	 * escaping rules.
	 *
	 * @param[in] line Command line to parse.
	 * @since 0.4.0
	 */
    explicit CommandLine(const std::string & line);

    /**
	 * Create a command line from one or more vectors of parameters.
	 *
	 * @param[in] arguments Vectors of parameters to add to the command
	 *                      line.
	 * @since 0.4.0
	 */
    explicit CommandLine(std::initializer_list<const std::vector<std::string>> arguments);

    ~CommandLine() = default;

#ifdef UNIT_TEST
    CommandLine(CommandLine &&)                                              = default;
    CommandLine & operator=(CommandLine &&)                                  = default;
    CommandLine(const CommandLine &) __attribute__((deprecated))             = default;
    CommandLine & operator=(const CommandLine &) __attribute__((deprecated)) = default;
#endif

    /**
	 * Format a command line from separate parameters using built-in
	 * escaping rules.
	 *
	 * @param[in] reserve String buffer size to preallocate.
	 * @return A command line, with escaping of characters sufficient
	 *         to reproduce the same command line parameters when
	 *         parsed.
	 * @since 0.4.0
	 */
    std::string to_string(size_t reserve = Shell::MAX_COMMAND_LINE_LENGTH) const;

    /**
	 * Get the total size of the command line parameters, taking into
	 * account the trailing space.
	 *
	 * @return The size of the command line parameters, plus 1 for
	 *         a trailing space.
	 * @since 0.4.0
	 */
    inline size_t total_size() const {
        return parameters_.size() + (trailing_space ? 1 : 0);
    }
    /**
	 * Reset this command line's data.
	 *
	 * Clears the parameter list and removes any trailing space.
	 *
	 * @since 0.4.0
	 */
    void reset();

    /**
	 * Escape all parameters when formatting the command line for
	 * output.
	 *
	 * This is the default.
	 *
	 * @since 0.5.0
	 */
    inline void escape_all_parameters() {
        escape_parameters_ = std::numeric_limits<size_t>::max();
    }
    /**
	 * Only escape the number of parameters that currently exist when
	 * formatting the command line for output.
	 *
	 * Use this before appending argument help that should not be
	 * escaped.
	 *
	 * Empty parameters will always be escaped.
	 *
	 * @since 0.5.0
	 */
    inline void escape_initial_parameters() {
        escape_parameters_ = parameters_.size();
    }
    /**
	 * Escape the first count parameters when formatting the command
	 * line for output.
	 *
	 * Use this to prevent argument help from being escaped.
	 *
	 * Empty parameters will always be escaped.
	 *
	 * @param[in] count Number of parameters to escape.
	 * @since 0.5.0
	 */
    inline void escape_initial_parameters(size_t count) {
        escape_parameters_ = count;
    }

    /**
	 * Obtain the parameters for this command line.
	 *
	 * @return A reference to the parameters.
	 * @since 0.6.0
	 */
    inline std::vector<std::string, AllocatorPSRAM<std::string>> & operator*() {
        return parameters_;
    }
    /**
	 * Obtain the parameters for this command line.
	 *
	 * @return A reference to the parameters.
	 * @since 0.6.0
	 */
    inline const std::vector<std::string, AllocatorPSRAM<std::string>> & operator*() const {
        return parameters_;
    }
    /**
	 * Obtain the parameters for this command line.
	 *
	 * @return A pointer to the parameters.
	 * @since 0.4.0
	 */
    inline std::vector<std::string, AllocatorPSRAM<std::string>> * operator->() {
        return &parameters_;
    }
    /**
	 * Obtain the parameters for this command line.
	 *
	 * @return A pointer to the parameters.
	 * @since 0.4.0
	 */
    inline const std::vector<std::string, AllocatorPSRAM<std::string>> * operator->() const {
        return &parameters_;
    }

    /**
	 * Compare a command line to another command line for equality.
	 *
	 * @param[in] lhs Left-hand side command line.
	 * @param[in] rhs Right-hand side command line.
	 * @return True if the command lines are equal, otherwise false.
	 * @since 0.4.0
	 */
    friend inline bool operator==(const CommandLine & lhs, const CommandLine & rhs) {
        return (lhs.trailing_space == rhs.trailing_space) && (lhs.parameters_ == rhs.parameters_);
    }
    /**
	 * Compare this command line to another command line for inequality.
	 *
	 * @param[in] lhs Left-hand side command line.
	 * @param[in] rhs Right-hand side command line.
	 * @return True if the command lines are not equal, otherwise false.
	 * @since 0.4.0
	 */
    friend inline bool operator!=(const CommandLine & lhs, const CommandLine & rhs) {
        return !(lhs == rhs);
    }

    bool trailing_space = false; /*!< Command line has a trailing space. @since 0.4.0 */

  private:
    std::vector<std::string, AllocatorPSRAM<std::string>> parameters_; /*!< Separate command line parameters. @since 0.4.0 */
    size_t escape_parameters_ = std::numeric_limits<size_t>::max();    /*!< Number of initial arguments to escape in output. @since 0.5.0 */
};

/**
 * Result of a command completion operation.
 *
 * Each space-delimited parameter is a separate string.
 *
 * @since 0.1.0
 */
struct Commands::Completion {
    std::list<CommandLine> help;        /*!< Suggestions for matching commands. @since 0.1.0 */
    CommandLine            replacement; /*!< Replacement matching full or partial command line. @since 0.1.0 */
};

} // namespace console

} // namespace uuid

#endif