#include <Algorithm/Algorithm.hpp>

#include <cassert>
#include <stdexcept>
#include <regex>
#include <utility>
#include <map>

#include <common/EvalHelper.h>

Algorithm::Algorithm(std::string p_fullName)
    : fullName(std::move(p_fullName))
{
    auto pos = this->fullName.rfind(':');
    assert(pos != std::string::npos && pos != this->fullName.size() - 1);
    this->name = this->fullName.substr(pos + 1);

    size_t start = 0;
    size_t end = this->fullName.find(':');
    while (end != std::string::npos)
    {
        this->groups.push_back(this->fullName.substr(start, end - start));
        start = end + 2;
        end = this->fullName.find(':', start);
    }
}

size_t Algorithm::getInputCount() const {
    if (this->overloads.empty()) {
        throw std::runtime_error { std::string("Input count has not yet been determined for algorithm ") + this->fullName };
    }

    return this->overloads[0].parameterTypes.size();
}

void Algorithm::addOverload(Algorithm::Overload overload) {
    if (!this->overloads.empty() && this->overloads.back().parameterTypes.size() != overload.parameterTypes.size()) {
        throw std::runtime_error { std::string("Inconsistent number of parameters for algorithm ") + this->fullName };
    }

    this->overloads.push_back(overload);
}

bool Algorithm::canAddOverload(const Algorithm::Overload& overload) {
    for (const auto& paramType: overload.parameterTypes) {
        if (paramType.find("automaton") != 0 && paramType.find("grammar") != 0 && paramType.find("regexp") != 0) {
            return false;
        }
    }

    if ( ! overload.returnType.starts_with ( "automaton" ) && ! overload.returnType.starts_with ( "grammar" ) && ! overload.returnType.starts_with ( "regexp" ) ) {
        return false;
    }

    if (this->overloads.empty()) {
        return true;
    }

    return this->overloads.back().parameterTypes.size() == overload.parameterTypes.size();
}

std::string Algorithm::getPrettyName() const {
    std::u8string str = u8"\u03B5";
    std::string eps ( str.begin ( ), str.end ( ) );
    static std::map<std::string, std::string> prettyNames = {
        {"automaton::simplify::SingleInitialState", "Single Initial"},
        {"automaton::simplify::EpsilonRemoverIncoming", eps + "-Remove In"},
        {"automaton::simplify::EpsilonRemoverOutgoing", eps + "-Remove Out"},
        {"automaton::simplify::UselessStatesRemover", "Remove Useless"},
        {"automaton::simplify::UnreachableStatesRemover", "Remove Unreachable"},
        {"automaton::transform::Compaction", "Compact"},
        {"automaton::transform::AutomatonIteration", "Iterate"},
        {"automaton::transform::AutomatonIterationEpsilonTransition", eps + "-Iterate"},
        {"automaton::transform::AutomataConcatenation", "Concatenate"},
        {"automaton::transform::AutomataConcatenationEpsilonTransition", eps + "-Concatenate"},
        {"automaton::transform::AutomataUnionCartesianProduct", "Union"},
        {"automaton::transform::AutomataUnionEpsilonTransition", eps + "-Union"},
        {"automaton::transform::AutomataIntersectionCartesianProduct", "Intersect"},
    };
    auto it = prettyNames.find(this->fullName);
    if (it != prettyNames.end())
        return it->second;

    return std::regex_replace(this->name, std::regex {"([a-z])([A-Z])"}, "$1 $2");
}

std::shared_ptr<abstraction::Value>
Algorithm::execute(const std::vector<std::shared_ptr<abstraction::Value>>& params) const {
    ext::vector<std::shared_ptr<abstraction::Value>> extParams(params.begin(), params.end());
	abstraction::TemporariesHolder environment;

    return abstraction::EvalHelper::evalAlgorithm(environment, this->fullName,
                                              {},
                                              extParams,
                                              abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT);
}

Algorithm::Overload::Overload(ext::string p_returnType,
                              std::vector<ext::string> p_parameterTypes)
        : returnType(std::move(p_returnType))
        , parameterTypes(std::move(p_parameterTypes)) {}
