#include "keyword_comp.h"

#include <vector>
#include "sentinel_runtime_state.h"

using namespace std;

USE_DEBUG_FLAG(D_KEYWORD);

static const string whitespaces = " \t";

static string
getSubStrNoPadding(const string &str, uint start, uint end)
{
    auto r_start = str.find_first_not_of(whitespaces, start);
    auto r_end = str.find_last_not_of(whitespaces, end-1);

    if (r_end==string::npos || r_start==string::npos || r_start>r_end) {
        dbgWarning(D_KEYWORD)
            << "Can't extract substring from '"
            << str
            << "', padded start: "
            << r_start
            <<  ", padded end: "
            << r_end;
        throw KeywordError("Found an empty section in the '"+ str + "'");
    }

    return str.substr(r_start, r_end-r_start+1);
}

static vector<string>
split(const string &str, const string &delim, uint start = 0)
{
    vector<string> res;
    uint part_start = start;
    bool escape = false;
    bool in_string = false;

    for (uint index = start; index<str.size(); index++) {
        if (escape) {
            escape = false;
            continue;
        }

        switch (str[index]) {
            case '\\': {
                escape = true;
                break;
            }
            case '"': {
                in_string = !in_string;
                break;
            }
            default:
                if (!in_string && delim.find(str[index])!=string::npos) {
                    if (part_start == index) {
                        dbgTrace(D_KEYWORD) << "Encountered consecutive delimiter in: " << str;
                    } else {
                        res.push_back(getSubStrNoPadding(str, part_start, index));
                    }
                    part_start = index+1;
                }
        }
    }

    if (escape||in_string) {
        dbgWarning(D_KEYWORD)
            << "Failed to split "
            << str
            << ". Split ended in "
            << (escape ? "escape" : "middle of string");
        throw KeywordError("Split has ended in the middle of the parsing");
    }

    if (str.find_first_not_of(whitespaces, part_start)!=string::npos) {
        res.push_back(getSubStrNoPadding(str, part_start, str.size()));
    }

    return res;
}

KeywordAttr::KeywordAttr(const string &str) : params(split(str, whitespaces))
{
}

KeywordParsed::KeywordParsed(string const &keyword) {
    auto index = keyword.find_first_of(':');
    if (index!=string::npos) {
        for (auto &str : split(keyword, ",", index+1)) {
            attr.push_back(KeywordAttr(str));
        }
    } else {
        index = keyword.size();
    }

    name = getSubStrNoPadding(keyword, 0, index);
    if (name.find_first_of(whitespaces)!=string::npos) {
        throw KeywordError("'" + name + "' - cannot be a keyword name");
    }
}

uint
SentinelRuntimeState::getOffset(const std::string &) const
{
    return 0;
}

// LCOV_EXCL_START Reason: this function is tested in one_element_list_negative_test but marked as not covered.
uint
SentinelRuntimeState::getVariable(uint var_id) const
{
    dbgAssert(false) << AlertInfo(AlertTeam::CORE, "keywords") << "Could not find the variable ID: " << var_id;
    return 0;
}
// LCOV_EXCL_STOP

class SentinelKeyword : public SingleKeyword
{
public:
    MatchStatus
    isMatch() const
    {
        SentinelRuntimeState curr_state;
        return runNext(&curr_state);
    }

private:
    // LCOV_EXCL_START Reason: Unreachable function.
    MatchStatus
    isMatch(const I_KeywordRuntimeState *state) const override
    {
        return runNext(state);
    }
    // LCOV_EXCL_STOP
};

class KeywordComp::Impl : Singleton::Provide<I_KeywordsRule>::From<KeywordComp>
{
public:
    Maybe<shared_ptr<VirtualRule>>
    genRule(const string &rule)
    {
        shared_ptr<VirtualRule> res;
        try {
            res = KeywordsRuleImpl::genRule(rule);
        } catch (const KeywordError &e) {
            return genError(e.getErr());;
        }
        return move(res);
    }

private:
    class KeywordsRuleImpl : public VirtualRule
    {
    public:
        bool isMatch() const override { return start.isMatch() == MatchStatus::Match; }

        static unique_ptr<KeywordsRuleImpl>
        genRule(const string &rule)
        {
            auto res = make_unique<KeywordsRuleImpl>();

            auto pos = rule.find_last_not_of(whitespaces);
            if (pos==string::npos) {
                // Empty rule
                return res;
            }

            if (rule[pos]!=';') throw KeywordError(rule + " - end of text pass rule");

            VariablesMapping known_vars;

            auto key_vec = split(rule, ";");
            for (auto &keyword : key_vec) {
                res->start.appendKeyword(getKeywordByName(keyword, known_vars));
            }

            return res;
        }

    private:
        SentinelKeyword start;
    };
};

KeywordComp::KeywordComp() : Component("KeywordComp"), pimpl(make_unique<KeywordComp::Impl>()) {}

KeywordComp::~KeywordComp() {}

string I_KeywordsRule::keywords_tag = "keywords_rule_tag";
