////////////////////////////////////////////////////////////////////////////////////
//
// A slash-command parser. The intended use is as follows:
//
// SlashCommandParser parser(Command);
//
// if      parser.Parse("/command1", "syntax1") { ... handle command1 ... }
// else if parser.Parse("/command2", "syntax2") { ... handle command2 ... }
// else if parser.Parse("/command3", "syntax3") { ... handle command3 ... }
// else { ... print an error message ... }
//
////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include <string>
#include <vector>
#include <string_view>
#include <map>
#include <cstdint>

class SlashCommandParser
{
    public:
        struct Value
        {
            std::string s_;
            int64_t i_;
            double d_;
            Value(const std::string &s) : s_(s), i_(0), d_(0) {}
            Value(int64_t i) : s_(), i_(i), d_(0) {}
            Value(double d) : s_(), i_(0), d_(d) {}
        };
        std::vector<Value> args_;
    
    private:
        int cursor_;
        std::string command_;
        std::string line_;
        int linelen_;
        std::string error_;

        static bool ascii_isupper(char c) { return (c >= 'A') && (c <= 'Z'); }
        static bool ascii_islower(char c) { return (c >= 'a') && (c <= 'z'); }
        static bool ascii_isdigit(char c) { return (c >= '0') && (c <= '9'); }
        static bool ascii_isalpha(char c) { return ascii_isupper(c) || ascii_islower(c); }
        static bool ascii_isalnum(char c) { return ascii_isalpha(c) || ascii_isdigit(c); }
        static bool ascii_isspace(char c) { return (c==0)||(c==' ')||(c=='\t')||(c=='\r')||(c=='\n')||(c=='\f')||(c=='\v'); }

        void skip_white();
        std::string read_word();
        void not_enough_args(const std::string &cmd);
        

    public:
        // Initialize the slash-command parser with the full command line.
        //
        // The command line is stored.  The first keyword (the slash-command
        // itself) is extracted and stored.  If the command-line doesn't
        // start with a slash-command, an error message is stored.
        // 
        // After initializing the parser, you should call Parse once
        // for every known command.
        //
        SlashCommandParser(std::string_view Line);
        
        // Parse: Try to parse a slash-command.
        //
        // The intent is that you should call 'Parse' once for every
        // known command.  Hopefully, one of these 'Parse' commands
        // will return true.  If not, then the command couldn't be parsed.
        //
        // You must pass in the name of a slash-command (eg, "/compile")
        // followed by the argument types that the command accepts.
        // 
        // The argument types are represented as a string containing the
        // characters i (for int64), s (for string), and d (for double).
        // For example, if ArgTypes is "sid", then the command expects a
        // string, then an int64, then a double.
        //
        // If the command matches, and the arguments parse correctly,
        // returns true, stores the parsed arguments, and clears the
        // stored error message.
        //
        // If the command matches, but the syntax is wrong, returns false
        // and stores the error message.
        //
        // If the command doesn't match, returns false.
        // 
        bool Parse(std::string_view Command, std::string_view ArgTypes);

        // Error: Declare that parsing has failed, and get an error message.
        //
        // You call this if you tried calling Parse on every known command,
        // and none of the Parse calls returned true.  Returns an error
        // message indicating what went wrong.
        //
        std::string Error() const;
        
        // Get the Nth argument.
        //
        // After Parse returns true, the parsed arguments are stored in the
        // SlashCommandParser.  You can use these functions to fetch the
        // parsed arguments.
        //
        // For example, if you call Parse("/hello", "sid") and it returns
        // true, you should then call StringArg(0), then IntArg(1), then
        // DoubleArg(2) to get all the arguments.
        //
        std::string StringArg(int n) const;
        int64_t IntArg(int n) const;
        double DoubleArg(int n) const;
};



