#pragma once

#ifndef DONT_INCLUDE_SLASH_PARSER_HPP
#include "slash-parser.hpp"
#endif
#include <charconv>
#include <sstream>



void SlashCommandParser::skip_white()
{
    while ((cursor_ < linelen_) && (ascii_isspace(line_[cursor_])))
    {
        cursor_++;
    }
}

std::string SlashCommandParser::read_word()
{
    int start = cursor_;
    while ((cursor_ < linelen_) && (!ascii_isspace(line_[cursor_])))
    {
        cursor_++;
    }
    std::string result = line_.substr(start, cursor_ - start);
    skip_white();
    return result;
}


SlashCommandParser::SlashCommandParser(std::string_view Line)
{
    line_ = Line;
    linelen_ = int(Line.size());
    cursor_ = 0;
    command_ = read_word();
    if ((command_.size() < 2) || (command_[0] != '/'))
    {
        error_ = "Not a slash-command";
        return;
    }
    if (!ascii_isalpha(command_[1]))
    {
        error_ = "Slash command must start with ascii alpha";
        return;
    }
    for (int i = 1; i < int(command_.size()); i++)
    {
        if (!ascii_isalnum(command_[i]))
        {
            error_ = "Slash-command must be alphanumeric";
            return;
        }
    }
}

bool SlashCommandParser::Parse(std::string_view Command, std::string_view ArgTypes)
{
    if (Command != std::string_view(command_)) return false;

    args_.clear();
    for (int i = 0; i < int(ArgTypes.size()); i++)
    {
        if (cursor_ == linelen_)
        {
            std::ostringstream oss;
            oss << "Not enough arguments for " << Command << " (need " << ArgTypes.size() << ")";
            error_ = oss.str();
            return false;
        }
        switch(ArgTypes[i])
        {
            case 's':
            {
                std::string word = read_word();
                args_.emplace_back(word);
                break;
            }
            case 'i':
            {
                std::string word = read_word();
                const char *p = word.c_str();
                const char *last = p + word.size();
                if ((p < last) && (*p == '+')) p++;
                int64_t result;
                auto r = std::from_chars(p, last, result, 10);
                if ((r.ec != std::errc()) || (r.ptr != last))
                {
                    std::ostringstream oss;
                    oss << Command << " expects arg " << i << " to be an int, not '" << word << "'";
                    error_ = oss.str();
                    return false;
                }
                args_.emplace_back(result);
                break;
            }
            case 'd':
            {
                std::string word = read_word();
                const char *p = word.c_str();
                const char *last = p + word.size();
                double result;
                auto r = std::from_chars(p, last, result);
                if ((r.ec != std::errc()) || (r.ptr != last))
                {
                    std::ostringstream oss;
                    oss << Command << " expects arg " << i << " to be a double, not '" << word << "'";
                    error_ = oss.str();
                    return false;
                }
                args_.emplace_back(result);
                break;
            }
            default:
            {
                std::ostringstream oss;
                oss << "Invalid ArgTypes parameter (" << ArgTypes << ") to Parse routine for " << Command;
                error_ = oss.str();
                return false;
            }
        }
    }

    if (cursor_ != linelen_)
    {
        error_ = "Too many arguments for command: ";
        error_ += Command;
        return false;
    }

    error_.clear();
    return true;
}

std::string SlashCommandParser::Error() const
{
    if (!error_.empty())
    {
        return error_;       
    }
    else
    {
        return std::string("Unrecognized command: ") + command_;
    }
}

std::string SlashCommandParser::StringArg(int n) const
{
    if ((n < 0) || (n >= int(args_.size()))) return "";

    return args_[n].s_;
}

int64_t SlashCommandParser::IntArg(int n) const
{
    if ((n < 0) || (n >= int(args_.size()))) return 0;

    return args_[n].i_;
}

double SlashCommandParser::DoubleArg(int n) const
{
    if ((n < 0) || (n >= int(args_.size()))) return 0.0;

    return args_[n].d_;
}
