#include <ranges>
#include <algorithm>
#include <utility>

#include "show.h"
#include "log.h"
#include "katom.h"
#include "argtype_registry.h"
#include "argument_set.h"
#include "util.h"

bool operator==(Parameter_set lhs, Parameter_set rhs)
{
    return as_string(lhs.m_katoms.begin(), lhs.m_katoms.end(), true) ==
        as_string(rhs.m_katoms.begin(), rhs.m_katoms.end(), true);
}

std::regex parameter_regex(bool optional=false)
{
    // The type component may carry a numeric type parameter, e.g.
    // rows.rest(2) (see resolve_argtype below).  In the optional
    // three-part form the type may be empty (:paper_size..tex).
    std::string type = R"(\w+(?:\(\d+\))?)";
    std::string opt_type = R"(\w*(?:\(\d+\))?)";
    std::string pattern = R"((?:([A-Za-z]\w*))|(?:([A-Za-z]\w*)\.()" + type +
        R"())|(?:([A-Za-z]\w*)\.()" + type + R"()\.(\w+)))";
    if (optional) {
        pattern = R"((?::([A-Za-z]\w*))|(?::([A-Za-z]\w*)\.()" + type +
            R"())|(?::([A-Za-z]\w*)\.()" + opt_type + R"()\.(\w+)))";
    }
    // (void)K::log(3, pattern);
    return std::regex(pattern);
}

// Look up an argument type, specializing a parameterized use such as
// rest(2): the base type is copied, its type parameter set, and its
// display name extended, so kdesc signatures show rows.rest(2).
static Argtype resolve_argtype(
    const std::string& type_text, const Argtype_registry& argtypes, const Locator& loc)
{
    static const std::regex parameterized(R"((\w+)\((\d+)\))");
    std::smatch match {};
    if (std::regex_match(type_text, match, parameterized)) {
        Argtype argtype = argtypes.get(match[1], loc);
        argtype.m_parameter = match[2];
        argtype.m_name += "(" + std::string(match[2]) + ")";
        return argtype;
    }
    return argtypes.get(type_text, loc);
}

// True for the rest type in any parameterization (rest, rest(2), ...).
static bool is_rest(const Argtype& argtype)
{
    return argtype.m_name == "rest" || argtype.m_name.rfind("rest(", 0) == 0;
}

Parameter_set::Parameter_set(const std::string parameter_string)
{
    (void)K::log(3);
    Argtype_registry argtypes {};
    parse_parameters(
        katomize(line_split(parameter_string), Locator().str()),
        argtypes);
}

 
Parameter_set::Parameter_set(const std::vector<Katom>& katoms)
        : m_katoms(katoms)
{
    (void)K::log(3);
    Argtype_registry argtypes {};
    parse_parameters(m_katoms, argtypes);
}

Parameter_set::Parameter_set(const std::vector<Katom>& katoms, const Argtype_registry& argtypes)
        : m_katoms(katoms)
{
    (void)K::log(3);
    parse_parameters(m_katoms, argtypes);
}

// Parameter parsing

Parameter parse_positional_parameter(const katom_list& katoms, const Argtype_registry& argtypes)
{
    // (void)K::log(3, katoms);
    if (katoms.size() > 1) {
        throw Argument_error("Multiple katoms for positional argument:\n  " +
                             as_string(katoms.begin(), katoms.end(), true) +
                             "\nPositional arguments are separated by the bar (|) character.",
                             katoms[0].m_loc);
    }
    Katom k = katoms[0];
    std::string name = k.m_text;
    std::smatch match {};
    if (!std::regex_match(name, match, parameter_regex())) {
        throw Argument_error(
            "The structure of the word \"" + name + "\" is not correct for a positional parameter",
            k.m_loc);
    } else {
        std::string match_name = std::string(match[1]) + std::string(match[2]) + std::string(match[4]);
        std::string match_type = std::string(match[3]) + std::string(match[5]);
        std::string match_target = match[6];
        if (match_type.empty()) {
            match_type = "string";
        }
        return Parameter(match_name, resolve_argtype(match_type, argtypes, k.m_loc), k.m_loc);
    }
}


Parameter parse_optional_parameter(const katom_list& katoms, const Argtype_registry& argtypes)
{
    //(void)K::log(3);
    Katom k = katoms[0];
    std::string default_value {};
    if (katoms.size() > 1) {
        default_value = to_string(katoms.cbegin() + 1, katoms.cend(), true);
    }

    std::string name = k.m_text;
    std::smatch match {};
    if (!std::regex_match(name, match, parameter_regex(true))) {
        throw Argument_error(
            "The structure of the word \"" + name +
            "\" is not correct for an optional parameter",
            k.m_loc);
    } else {
        std::string match_name = std::string(match[1]) + std::string(match[2]) + std::string(match[4]);
        std::string match_type = std::string(match[3]) + std::string(match[5]);
        std::string match_target = match[6];
        if (match_type.empty()) {
            match_type = "string";
        }
        Parameter parameter(match_name, resolve_argtype(match_type, argtypes, k.m_loc),
                            k.m_loc, true, default_value);
        // Two-level default resolution: a default declared in the
        // parameter list wins; otherwise the argument type's :default
        // fills in.  Both are validated here, at definition time, so an
        // invalid default cannot reach an application.
        if (default_value.empty() && !parameter.m_argtype.m_default.empty()) {
            parameter.m_default = parameter.m_argtype.m_default;
            parameter.m_default_from_type = true;
        } else if (!default_value.empty()) {
            Parameter_set::validate(parameter, default_value, k.m_loc);
        }
        return parameter;
    }
}

void check_for_missing_parameter(const katom_list& katoms)
{
    (void)K::log(3, katoms.size());
    // Yeah, yeah, "algorithms."
    auto ki = katoms.begin();
    while (ki < katoms.end() - 1) {
        ki = std::find_if(ki, katoms.end(), [](const Katom& k) {
            return k.m_type == katom_t::bar; });
        if (ki == katoms.end()) {
            break;
        }
        auto kstart = ki;
        ki = std::find_if(ki + 1, katoms.end(), [](const Katom& k) {
            return !k.is_whitespace(); });
        if (ki == katoms.end()) {
            throw Argument_error(
                "A parameter list ends with a bar character", kstart->m_loc);
        }
        auto type_after_bar = ki->m_type;
        if (type_after_bar == katom_t::bar) {
            throw Argument_error(
                "A parameter name was missing between two bar characters", kstart->m_loc);
        } else if (type_after_bar == katom_t::option_name) {
            throw Argument_error(
                "A parameter name was missing between a bar character and an option name",
                kstart->m_loc); 
        }
        ++ki;
    }
}

bool is_boundary(katom_list::const_iterator ki)
{
    return ki->m_type == katom_t::option_name || ki->m_type == katom_t::bar;
}

std::vector<std::vector<Katom>>
function_symbol_parts(katom_list::const_iterator kbegin, katom_list::const_iterator kend)
{
    std::vector<std::vector<Katom>> parts;
    katom_list part {};
    auto ki = kbegin;
    while (ki != kend && ki->is_whitespace()) {
        ki++;
    }
    if (ki == kend) {
        return {};
    }
    if (ki->m_type != katom_t::option_name) {
        part.push_back(Katom("|", katom_t::bar, kbegin->m_loc));
    }
    while (ki < kend) {
        if (!part.empty() && is_boundary(ki)) {
            parts.push_back(trim(part));
            part = {};
        }
        part.push_back(*ki);
        ki++;
    }
    if (!part.empty()) {
        parts.push_back(trim(part));
    }
    // std::cout << "PARTS:\n";
    // for (size_t i = 0; i < parts.size(); i++) {
    //    std::cout << i << sp_arrow << parts[i] << "\n";
    // }
    return parts;
}

katom_list trim_part(katom_list part)
{
    return trim(part, {katom_t::space, katom_t::newline, katom_t::bar});
}

std::tuple<katom_lists,katom_lists>
parameter_split(katom_list::const_iterator kbegin, katom_list::const_iterator kend)
{
    (void)K::log(3); //, "begin:", *kbegin, "end:", *(kend - 1));
    // "distance:", std::distance(kbegin, kend));
    auto parts = function_symbol_parts(kbegin, kend);
    // std::cout << "parts: " << parts << "\n";
    katom_lists positional {};
    katom_lists optional {};
    for (auto p : parts) {
        if (p[0].m_type == katom_t::option_name) {
            optional.push_back(p);
        } else {
            positional.push_back(trim_part(p));
        }
    }
    return {positional, optional};
}    

void Parameter_set::parse_parameters(const katom_list& katoms, const Argtype_registry& argtypes)
{
    (void)K::log(3, trim(katoms));
    if (katoms.empty()) {
        return;
    }
    check_for_missing_parameter(katoms);
    auto [positional, optional] = parameter_split(katoms.cbegin(), katoms.cend());
    for (auto req : positional) {
        auto pos = parse_positional_parameter(req, argtypes);
        if (is_rest(pos.m_argtype)) {
            m_rest.push_back(pos);
        } else {
            m_positional.push_back(pos);
        }
    }
    for (auto opt : optional) {
        auto param = parse_optional_parameter(opt, argtypes);
        if (std::ranges::count(m_optional_names, param.m_name) > 0) {
            throw Argument_error(
                "Optional parameter \":" + param.m_name + "\" already defined",
                katoms[0].m_loc);
        }
        m_optional.push_back(param);
        m_optional_names.push_back(param.m_name);
    }
    if (!m_rest.empty()) {
        m_positional_count = m_positional.size();
    }
}

void describe_arguments(
    const std::string& label,
    const std::vector<std::vector<Katom>>& positional,
    const std::vector<std::vector<Katom>>& optional,
    const std::vector<Katom>& rest)
{
    std::cout << label << ":\n"
              << "  positional: " << positional << "\n"
              << "    optional: " << optional << "\n"
              << "        rest: " << rest << "\n";
}


void Parameter_set::describe_parameters()
{
    (void)K::log(3);
    std::cout << "  positional: ";
    if (!m_positional.empty()) {
        for (auto p : m_positional) {
            std::cout << p << " ";
        }
    } else {
        std::cout << "[none]";
    }
    std::cout << "\n    optional: ";
    if (!m_optional.empty()) {
        for (auto p : m_optional) {
            std::cout << p << " ";
        }
    } else {
        std::cout << "[none]";
    }
    std::cout << "\n        rest: ";
    if (!m_rest.empty()) {
        std::cout << kall << m_rest << kreset << "\n";
    } else {
        std::cout << "[none]";
    }
    std::cout << "\n";
}

std::tuple<katom_lists,katom_lists,katom_list>
argument_split(katom_list::const_iterator kbegin, katom_list::const_iterator kend,
               long unsigned int positional_limit)
{
    // (void)K::log(3, "begin:", *(kbegin+1), "end:", *(kend - 1),
    //             "distance:", std::distance(kbegin, kend), "limit:", positional_limit);
    (void)K::log(3);
    // msg() << std::pair(kbegin, kend) << "\n";
    auto parts = function_symbol_parts(kbegin, kend);

    katom_lists positional {};
    katom_lists optional {};
    katom_list rest {};

    for (auto p : parts) {
        if (p[0].m_type == katom_t::option_name) {
            optional.push_back(p);
        } else if (positional.size() < positional_limit) {
            positional.push_back(trim_part(p));
        } else {
            // Parts were trimmed, so adjacent parts would abut their bar
            // katoms (a row separator "||" next to an empty cell's "|"
            // would serialize as "|||").  A space keeps the writer's
            // bar/double-bar distinction parseable.
            if (!rest.empty()) {
                rest.push_back(Katom(" ", katom_t::space, p[0].m_loc));
            }
            rest.insert(rest.end(), p.begin(), p.end());
        }
    }
    return {positional, optional, trim_part(rest)};
}    

// Parameter/argument mapping

void Parameter_set::check_positional(const katom_lists& positional_arguments, const Locator& loc)
{
    (void)K::log(3, "required:", m_positional.size(), positional_arguments.size()); //, positional_arguments);
    auto positional_count = m_positional.size();
    auto given_count = positional_arguments.size();
    if (positional_count > given_count) {
        // std::cout << "Given less than required\n";
        std::vector<Parameter> missing(m_positional.begin() + given_count, m_positional.end());
        //std::cout << "missing: " << missing << "\n";
        auto missing_count = missing.size();
        std::stringstream ss {};
        ss << "Positional " << plural("argument", missing_count) << " " << to_be(missing_count)
           << " missing:\n";
        std::cout << ss.str();
        for (auto arg : missing) {
        ss << "  " << arg.m_name << "\n";
        }
        //std::cout << ss.str();  
        throw Argument_error(ss.str(), loc);
    } else if (positional_count < given_count) {
        //std::cout << "DESCRIBE\n";
        //describe_parameters();
        std::stringstream ss {};
        ss << "Too many positional arguments were given; "
           << positional_count << " needed but " << given_count << " given";
        throw Argument_error(ss.str(), loc);
    }
}

std::map<std::string, std::string>
Parameter_set::check_optional(const katom_lists& optional_arguments, const Locator& loc)
{
    (void)K::log(3, optional_arguments.size());
    std::vector<std::string> optional_names_used {};
    std::map<std::string, std::string> values {};
    for (const auto& opt : optional_arguments) {
        std::string name(opt[0].m_text, 1);
        if (std::ranges::count(m_optional_names, name) == 0) {
            throw Argument_error("Optional argument \":" + name + "\" not defined", loc);
        }
        if (std::ranges::count(optional_names_used, name) > 0) {
            throw Argument_error("Optional argument \":" + name + "\" already provided "
                                 + "with a value of:\n" + values[name], loc);
        }
        katom_list value_katoms(opt.begin()+1, opt.end());
        std::string value = trim(to_string(value_katoms));
        values[name] = value;
        optional_names_used.push_back(name);
     }
    return values;
}


const std::map<std::string, std::string>
Parameter_set::value_map(
    const katom_lists& positional, const katom_lists& optional, const katom_list& rest,
    const Locator& loc)
{
    (void)K::log(3, "positional:", positional.size(), "optional:", optional.size(), "rest:", rest.size());
    std::map<std::string, std::string> values {};
    check_positional(positional, loc);
    for (size_t i = 0; i < m_positional.size(); ++i) {
        auto param = m_positional[i];
        std::string arg = as_string(positional[i].begin(), positional[i].end(), true);
        values[param.m_name] = arg;
    }
    auto optional_values = check_optional(optional, loc);
    for (auto [key, value] : optional_values) {
        // check_optional returns only the options that were actually
        // written, so an empty value here means the name was written alone
        // (":number" rather than ":number 10") — distinct from the option
        // being absent, which is filled from the default below.  The
        // argument type supplies the alone value; bool declares "true",
        // which is what makes a bare boolean option mean true.
        const Parameter* parameter = find(key);
        if (value.empty() && parameter && !parameter->m_argtype.m_alone.empty()) {
            value = parameter->m_argtype.m_alone;
        }
        values[key] = value;
    }
    for (auto opt : m_optional) {
        values.try_emplace(opt.m_name, opt.m_default);
    }
    if (active(rest)) {
        if (!m_rest.empty()) {
            values[m_rest[0].m_name] = as_string(rest.begin(), rest.end(), true);
        } else {
            std::stringstream ss {};
            ss << "More positional arguments were given (" << positional.size() + rest.size()
               << ") than defined (" << m_positional.size() << ")";
            throw Argument_error(ss.str(), loc);
        }
    }
    for (const auto& [name, value] : values) {
        const Parameter* parameter = find(name);
        if (parameter) {
            validate(*parameter, value, loc);
        }
    }
    return values;
}

// Check an argument value against its argument type's pattern.  An empty
// value (an unsupplied optional argument without a default) is not checked.
// The error message includes the argument type's description from its
// @@@argtype definition, so the .k description text is what the writer
// sees when a complicated value (e.g. a table line specification) is wrong.
// The value-size limit guards against std::regex stack overflow: the
// libstdc++ executor recurses per character, so a pattern applied to a
// very large value crashes.  Typed argument values are short; large
// values are content (rest, :text) whose types match everything and are
// excluded by matches_all() anyway.
const size_t validation_size_limit = 4096;

void Parameter_set::validate(
    const Parameter& parameter, const std::string& value, const Locator& loc)
{
    if (value.empty() || value.size() > validation_size_limit) {
        return;
    }
    const Argtype& argtype = parameter.m_argtype;
    if (argtype.matches_all()) {
        return;
    }
    if (!std::regex_match(value, argtype.m_regex)) {
        std::stringstream ss {};
        ss << "The value \"" << value << "\" given for the argument \""
           << parameter.m_name << "\" does not match the \"" << argtype.m_name
           << "\" argument type.\n\nThe \"" << argtype.m_name << "\" argument type describes "
           // A .k description is prose: collapse its source layout (line
           // breaks, continuation indentation) so it re-flows with the
           // sentence -- indented .k lines would otherwise read as verbatim.
           << collapse_whitespace(argtype.m_desc) << "\n";
        throw Argument_error(ss.str(), loc);
    }
}

const Parameter* Parameter_set::find(const std::string& name) const
{
    for (const auto& params : {&m_positional, &m_optional, &m_rest}) {
        for (const Parameter& p : *params) {
            if (p.m_name == name) {
                return &p;
            }
        }
    }
    return nullptr;
}

// Parameter/argument substitution

std::string replace_arguments(
    const std::map<std::string, std::string>& values,
    const std::string& parameterized_text, const Locator& loc)
{
    (void)K::log(3);
    std::string result = parameterized_text;
    for (auto [name, value] : values) {
        result = string_replace(result, '*' + name + '*', value);
    }
    auto matches = find_all(result, std::regex(R"((\*.*?\*))"));
    std::vector<std::string> unmatched;
    unmatched.reserve(matches.size());
    std::copy(matches.begin(), matches.end(), std::back_inserter(unmatched));

    auto unmatched_count = unmatched.size();
    if (unmatched_count > 0) {
        std::stringstream ss {};
        ss << "Undefined " << plural("argument", unmatched_count) << " in klammer:\n";
        for (const auto& arg : unmatched) {
            ss << "  " << arg << "\n";
        }
        ss << "To prevent the \"*\" character from specifying an argument, "
           << "precede it with the \"^\" character.";
        throw Argument_error(ss.str(), loc);
    }
    return result;
}
