#include <algorithm>

#include "klammer.h"
#include "show.h"
#include "log.h"
#include "argument_set.h"
#include "eval.h"
#include "util.h"
#include "character.h"

using namespace std::literals;

std::regex Klammer::name_re = std::regex(R"((\w+)(?:\.((?:\w+|\*)(?:,(?:\w+|\*))*))?)");

std::tuple<std::string, strings_t, bool>
parse_name(const Target_registry& targets, const Katom& name_katom)
{
    std::string name_with_target = trim_char(name_katom.m_text, '@');
    std::smatch match {};

    if (!std::regex_match(name_with_target, match, Klammer::name_re)) {
        throw Parsing_error(
            "The klammer name \"" + name_with_target + "\" is not correctly defined.\n\n"
            "The form is \"<klammer-name>\" for general klammers or \"<klammer-name>.<target-name>\" "
            "for a specialized target.  Several targets that share one body are written as a "
            "comma-separated list: \"<klammer-name>.<target-name>,<target-name>\".  The klammer "
            "defined as \"<klammer-name>.k\" specifies the "
            "arguments and contains a description of the klammer in the definition body.",
            name_katom.m_loc);
    }
    std::string klammer_name = match[1];
    std::string target_part = match[2];
    if (target_part.empty()) {
        // No suffix at all: the general target, but not a STATEMENT about
        // coverage.  This is the writer's macro form -- someone defining a
        // repeated phrase is not building a klammer set -- so the third
        // result is false and the coverage analysis treats the body on its
        // merits rather than as an assertion.
        return { klammer_name, { Target_registry::general_name }, false };
    }
    strings_t target_names = regex_split(target_part, std::regex(","));
    strings_t seen {};
    for (const auto& target_name : target_names) {
        if (!targets.has(target_name)) {
            throw Target_error(
                "The target \"" + target_name + "\" in klammer definition \"" + name_with_target + "\" "
                "is not defined.\n\nEnter \"kdesc -t\" to see the targets defined by the Standard Klammer Set.",
                name_katom.m_loc);
        }
        if (is_in(target_name, seen)) {
            throw Target_error(
                "The target \"" + target_name + "\" is named more than once in klammer definition \"" +
                name_with_target + "\".",
                name_katom.m_loc);
        }
        seen.push_back(target_name);
    }
    // A ".k" declaration states ONE interface for every target, and a ".o"
    // declares an option set; neither produces output, so neither has any
    // meaning as a member of a list of output targets.
    if (target_names.size() > 1) {
        for (const auto& reserved :
             { Target_registry::declare_name, Target_registry::optionset_name }) {
            if (is_in(reserved, target_names)) {
                throw Target_error(
                    "The klammer definition \"" + name_with_target + "\" names \"" + reserved +
                    "\" in a list of targets.  A \"." + reserved + "\" definition declares an "
                    "interface for all targets rather than producing output for one, so it must "
                    "be written on its own.",
                    name_katom.m_loc);
            }
        }
    }
    // "*" written out is an ASSERTION: this klammer works for every target,
    // including targets that do not exist yet.  A list of "all the targets
    // defined today" cannot say that, and the difference matters the moment a
    // new target is added.  It may not appear IN a list -- "all targets and
    // also html" is either redundant or a misunderstanding.
    bool general_declared = is_in(Target_registry::general_name, target_names);
    if (general_declared && target_names.size() > 1) {
        throw Target_error(
            "The klammer definition \"" + name_with_target + "\" names \"" +
            Target_registry::general_name + "\" in a list of targets.  \"" +
            Target_registry::general_name + "\" already means every target, so it "
            "must be written on its own.",
            name_katom.m_loc);
    }
    return { klammer_name, target_names, general_declared };
}

std::tuple<Katom, Parameter_set, katom_list, Locator>
parse_definition_katoms(
    const std::string& klammer_name, const std::string& target_name,
    const Argtype_registry& argtypes, Option_set_registry& option_sets,
    katom_iter& begin, katom_iter& end)
{
    (void)K::log(3, *begin, *(end - 1));
    katom_iter deftype = std::find_if(
        begin, end, [](const Katom& k) { return is_deftype(k.m_type); });
    if (deftype == end) {
        throw Argument_error(
            "The klammer definition does not contain a definition separator that specifies\n"
            "how the klammer should be defined. The klammer syntax is:\n\n"
            "    @@<name>[.<target>] <parameters> :    <body> @@   definition\n"
            "    @@<name>[.<target>]              ::   <body> @@   instance (uses .k parameters)\n"
            "    @@<name>[.<target>]              :::  <body> @@   override existing definition\n"
            "    @@<name>[.<target>] <parameters> :::: <body> @@   default (can be overridden)",
            begin->m_loc);

    }
    katom_list parameter_katoms(begin, deftype);
    // Resolve text-removal markers (#, #[...]#, ##) in the parameter list.
    // The interior of a @@...@@ definition is held verbatim until now (so a
    // literal klammer body receives its content untouched), which means the
    // parameter declarations still contain any removal markers the writer
    // used.  Apply removal here, to the parameters only -- the body is left
    // verbatim by add_target_definition.
    mark_ignored_katoms(parameter_katoms);
    std::erase_if(parameter_katoms,
                  [](const Katom& k) { return k.m_type == katom_t::ignored; });
    parameter_katoms = trim_whitespace(parameter_katoms);
    // "::" and ":::" take their parameters from the ".k" declaration, so a
    // parameter list written with them is a mistake -- EXCEPT for an option
    // set, which has no separate declaration to inherit from: a set is its
    // own declaration, so an override restates what it declares.
    bool inherits_parameters = target_name != Target_registry::optionset_name;
    if (inherits_parameters &&
        (deftype->m_type == katom_t::klammer_instance ||
         deftype->m_type == katom_t::klammer_override) &&
        !parameter_katoms.empty()) {
        std::string sym = deftype->m_type == katom_t::klammer_instance ? "::" : ":::";
        throw Definition_error(
            "The \"" + klammer_name + "\" klammer uses the \"" + sym + "\" symbol but defines parameters.",
            begin->m_loc);
    }
    // Replace the option sets used in the parameter list with the parameters
    // they declare.  This is where the difference between an option set and a
    // klammer lies: a set is resolved HERE, as the parameter list is read,
    // rather than in the fixed-point apply loop, so what it contributes is
    // present when the list is parsed.  Any other klammer application in a
    // parameter list is rejected by the same pass.
    option_set_uses_t option_set_uses =
        expand_option_sets(parameter_katoms, klammer_name, target_name, option_sets);
    Parameter_set parameters(parameter_katoms, argtypes);
    stamp_option_set_uses(parameters, option_set_uses);

    katom_list body_katoms(deftype + 1, end - 1);
    body_katoms = trim_whitespace(body_katoms);
    return { *deftype, parameters, body_katoms, begin->m_loc };
}

void Klammer::add_target_definition(
    const std::string& target_name, const Argtype_registry& argtypes,
    Option_set_registry& option_sets, katom_iter begin, katom_iter end)
{
    (void)K::log(3, *begin, *(end-1));
    auto [deftype, parameters, body, loc] =
        parse_definition_katoms(m_name, target_name, argtypes, option_sets, begin, end);
    // msg() << "Klammer " << m_name << " add: " << target_name << "\n";
    // parameters.describe_parameters();

    std::regex variable_re(R"(\*(\w+)\*)");
    int i = 0;
    variable_map_t varmap {};
    for (const auto& k : body) {
        std::smatch match {};
        std::string txt = k.m_text;
        while (std::regex_search(txt, match, variable_re) &&
            (k.m_type == katom_t::karg || k.m_type == katom_t::text)) {
            //std::cout << "  txt: " << txt << "\n";
            std::string varname = match[1];
            //m_varmap[target_name][varname].push_back(i);
            varmap[varname].push_back(i);
            txt = std::regex_replace(txt, std::regex(R"(\*)" + varname + R"(\*)"), "");
        }
        i++;
    }
    // m_body[target_name] = body;
    m_defs.push_back({target_name, deftype.m_initial_type, parameters, body, varmap, begin->m_loc});
    m_defloc[target_name] = begin->m_loc;
    m_defmode[target_name] = defmode_from_katom(deftype.m_initial_type);
    // std::cout << "Klammer " << m_name << "." << target_name << ": " << m_defloc[target_name] << "\n";
}

void Klammer::remove_target_definition(const std::string& target_name)
{
    std::erase_if(m_defs,
        [&target_name](const auto& def) { return def.target == target_name; });
    m_defloc.erase(target_name);
    m_defmode.erase(target_name);
    m_body.erase(target_name);
    m_varmap.erase(target_name);
}

// Rationalize multiple definitions

// ONE definition in the source can register more than once.  A target that
// "provides" another registers both (the SKS's pdf includes tex, so
// "@@fraktur.tex : ..." becomes a tex definition and a pdf one), and so does
// every member of a comma-separated target list.  Counting or listing those
// registrations reports work the author did not do: "2 definitions" for a
// single line, followed by that same line printed twice -- which sends the
// reader hunting for a second definition that does not exist.
//
// These two report what was WRITTEN.  Registrations are grouped by source
// location, and a location that produced several targets names them, so the
// count and the listing agree with the file.
using location_group = std::pair<std::string, strings_t>;

std::vector<location_group> group_by_location(const auto& components)
{
    std::vector<location_group> groups {};
    for (const auto& c : components) {
        std::string loc = c.loc.desc();
        auto it = std::find_if(groups.begin(), groups.end(),
                               [&loc](const location_group& g) { return g.first == loc; });
        if (it == groups.end()) {
            groups.push_back({loc, {c.target}});
        } else {
            it->second.push_back(c.target);
        }
    }
    return groups;
}

int written_count(const auto& components)
{
    return static_cast<int>(group_by_location(components).size());
}

std::string error_list(const std::string& label, const auto& components, const std::string& after="")
{
    std::stringstream ss {};
    ss << label << ":\n";
    for (const auto& [loc, targets] : group_by_location(components)) {
        ss << "  " << loc;
        if (targets.size() > 1) {
            ss << "  (targets " << join(targets, ", ") << ")";
        }
        ss << "\n";
    }
    ss << after;
    return ss.str();
}

auto Klammer::target_defs(const std::vector<std::string>& target_names)
{
    std::vector<Klammer::components> defs {};
    for (const auto& target : target_names) {
        auto target_defs = collect_if(
            m_defs, [&target](const auto& def) { return def.target == target; });
        defs.insert(defs.end(), target_defs.begin(), target_defs.end());
    }
    return defs;
}

auto Klammer::instance_defs()
{
    return collect_if(
        m_defs,
        [] (const auto& def) {
            return def.deftype == katom_t::klammer_instance
                || def.deftype == katom_t::klammer_override; });
}


void Klammer::disallow_instances() //Klammer::components declaration)
{
    auto instances = instance_defs();
    if (!instances.empty()) {
        int icount = written_count(instances);
        std::stringstream ss {};
        ss << "There " << to_be(icount) << " " << icount << " "
           << plural("instance", icount) << " (defined by \"::\"), but "
           << "no declarations (defined by a \".k\" target)";
        throw Definition_error(
            error_list(ss.str(), instances), instances[0].loc);
    }
}

bool Klammer::copy_to_instances(const Target_registry& targets)
{
    auto instances = instance_defs();
    if (!instances.empty()) {
        auto definitions = collect_if(
            m_defs,
            [] (const auto& def) {
                return def.deftype != katom_t::klammer_instance
                    && def.deftype != katom_t::klammer_override; });
        int dcount = written_count(definitions);
        if (dcount != 1) {
            int icount = written_count(instances);
            std::stringstream ss {};
            ss << "There " << to_be(icount) << " " << icount << " "
               << plural("instance", icount) << " (defined by \"::\"), but "
               << dcount << " "<< plural("definition", dcount) << " (defined by \":\")";
            throw Definition_error(
                error_list(ss.str(), definitions), definitions[0].loc);
        } else {
            copy_components(definitions[0].parameters, m_defs, targets);
            return true;
        }

    } else {
        return false;
    }
}

void Klammer::copy_components(
    const Parameter_set& parameters, const std::vector<Klammer::components>& cs,
    const Target_registry& targets)
{
    (void)K::log(4);
    for (const auto& target_name : targets.m_names) {
        if (target_name == Target_registry::declare_name ||
            target_name == Target_registry::general_name ||
            target_name == Target_registry::optionset_name) {
            continue;
        }
        m_parameters = parameters;
    }
    for (const auto& c : cs) {
        m_body[c.target] = c.body;
        m_body_generic[c.target] = (c.target == Target_registry::general_name);
        m_varmap[c.target] = c.varmap;
    }
}

// Verify that there is no more than one general klammer definition

void Klammer::check_for_multiple_general_klammers()
{
    auto general_klammers = target_defs({Target_registry::general_name});
    if (general_klammers.size() > 1) {
        throw Definition_error(
            error_list(
                "There is more than one general klammer (a klammer in which no target is defined)",
                general_klammers),
            general_klammers[0].loc);
    }
}    

void Klammer::check_for_declaration_and_definitions()
{
    auto declares = target_defs({Target_registry::declare_name});
    if (!declares.empty()) {
        std::vector<Klammer::components> definitions {};
        for (const auto& def : m_defs) {
            if (def.target != Target_registry::declare_name) {
                if (def.deftype == katom_t::klammer_definition ||
                    def.deftype == katom_t::klammer_default) {
                    definitions.push_back(def);
                }
            }
        }
        if (!definitions.empty()) {
            int defsize = written_count(definitions);
            std::string desc = defsize == 1 ? "a definition that declares its own parameters"
                : std::to_string(defsize) + " definitions that declare their own parameters";
            throw Definition_error(
                error_list("A klammer has both a \".k\" declaration and " + desc +
                           ".\nWrite \"::\" instead of \":\" so the definition takes its parameters "
                           "from the declaration",
                           definitions),
                declares[0].loc);
        }
    }
}

// If a general definition exists, use it for targets not defined, but check signatures

void Klammer::copy_general_klammer_to_undefined(const Target_registry& targets)
{
    (void)K::log(4);
    auto general_klammers = target_defs({Target_registry::general_name});
    auto declares = target_defs({Target_registry::declare_name});

    // Check matching signatures (though this case already handled)
    if (general_klammers.size() == 1) {
        if (declares.empty() && !m_parameters.m_katoms.empty()) {
            auto general_parameters = general_klammers[0].parameters;
            if (general_parameters != m_parameters) {
                // m_parameters.describe_parameters();
                throw Definition_error(
                    error_list("The general parameters are different than the defined parameters",
                               general_klammers),
                    m_parameters.m_katoms[0].m_loc);
            }
        }
        const auto& [target, deftype, parameters, body, varmap, loc] = general_klammers[0];
        if (m_parameters.m_katoms.empty()) {
            m_parameters = parameters;
        }
        for (const auto& target_name : targets.m_names) {
            // std::cout << "General copy, considering " << target_name << "\n";
            // "k" and "o" declare interfaces rather than produce output, so a
            // general body is never copied to them.
            if (m_body.count(target_name) == 0 &&
                target_name != Target_registry::declare_name &&
                target_name != Target_registry::optionset_name) {
                // std::cout << "  Copying to " << target_name << "\n";
                m_body[target_name] = body;
                m_body_generic[target_name] = true; // general body -> writer content
                m_defloc[target_name] = loc;
                m_varmap[target_name] = varmap;
            }
        }
    }
}

// Three declaration cases: none, one, many

namespace {

// A compact, plain-text rendering of ONE definition's parameter list, for
// diagnostics that must show how two definitions differ.
// Klammer::signature_text() cannot serve here: it renders the rationalized
// m_parameters, which is exactly what does not exist yet when the per-target
// lists disagree.
std::string parameter_signature(const Parameter_set& parameters)
{
    std::string result {};
    auto add = [&result](const std::string& s) {
        if (!result.empty()) result += " ";
        result += s;
    };
    auto typed = [](const Parameter& p) {
        return p.m_argtype.m_name == default_argtype
            ? p.m_name : p.m_name + "." + p.m_argtype.m_name;
    };
    bool first = true;
    for (const auto& pos : parameters.m_positional) {
        // std::string(...) on the leading literal: see the note in
        // argtype.cpp::pylist -- append rather than insert-at-front, and it
        // avoids a gcc-12 -Wrestrict false positive.
        add(first ? typed(pos) : std::string("| ") + typed(pos));
        first = false;
    }
    for (const auto& rest : parameters.m_rest)
        add(typed(rest));
    for (const auto& opt : parameters.m_optional)
        add(std::string(":") + typed(opt)
            + (opt.m_default.empty() ? "" : " " + opt.m_default));
    return result.empty() ? "(no parameters)" : result;
}

}  // namespace

void Klammer::no_declarations(const Target_registry& targets)
{
    (void)K::log(4);
    // std::cout << boldblack << "No declarations\n" << black;
    check_for_multiple_general_klammers();

    std::vector<Klammer::components> defs {};
    std::vector<std::string> target_names = targets.applicable();
    std::vector<Parameter_set> all_parameter_sets {};
    // Are all parameters the same?
    for (const auto& def : m_defs) {
        if (std::ranges::find(target_names, def.target) != target_names.end()) {
            // std::cout << "  Found: " << def.target << "\n";
            all_parameter_sets.push_back(def.parameters);
        } else {
            // std::cout << "  Not found: " << def.target << "\n";            
        }
    }
    if (!all_equal<Parameter_set>(all_parameter_sets)) {
        // Show each target's own parameter list, not just its location: with
        // more than two targets the designer would otherwise have to diff the
        // definitions by hand to find which one drifted.
        std::stringstream ss {};
        ss << "The parameters of klammer \"" << m_name
           << "\" are not the same for every target, "
              "and there is no declaration (.k) target to define them once:\n";
        for (const auto& def : m_defs) {
            if (std::ranges::find(target_names, def.target) == target_names.end())
                continue;
            std::string target = def.target;
            target.resize(std::max(target.size(), size_t(4)), ' ');
            ss << "  Target: " << target << "  Parameters: " << parameter_signature(def.parameters)
               << "\n    " << def.loc.desc() << "\n";
        }
        ss << "Use a .k target to declare the parameters and describe the klammer,\n"
              "and \"::\" with no parameters for each target's definition.";
        throw Definition_error(ss.str(), m_defs[0].loc);
    } else {
        // std::cout << "  All equal\n";
        copy_components(m_defs[0].parameters, m_defs, targets);
    }
    copy_general_klammer_to_undefined(targets);
}

void Klammer::one_declaration(const Target_registry& targets, const Klammer::components& declare)
{
    (void)K::log(4);
    // std::cout << boldblack << "One declaration\n" << black;
    check_for_multiple_general_klammers();
    check_for_declaration_and_definitions();
    copy_components(declare.parameters, m_defs, targets);
    copy_general_klammer_to_undefined(targets);
}

void Klammer::many_declarations(const std::vector<Klammer::components>& declares)
{
    (void)K::log(4);
    // std::cout << boldblack << "Many declarations\n" << black;
    throw Definition_error(
        error_list("More than one declaration (.k) klammer", declares),
        declares[0].loc);
}



void Klammer::rationalize(const Target_registry& targets)
{
    (void)K::log(3, m_name);
    auto declares = target_defs({Target_registry::declare_name});
    auto declare_count = declares.size();
    if (declare_count == 0) {
        disallow_instances();
        if (!copy_to_instances(targets)) {
            no_declarations(targets);
        }
    } else if (declare_count == 1) {
        one_declaration(targets, declares[0]);
    } else {
        many_declarations(declares);
    }
}    


std::string klammer_name_from_katom(const std::string& s, const Locator& loc)
{
    std::regex rgx(R"(@(\w+).*)");
    std::smatch match {};
    if (std::regex_match(s, match, rgx)) {
        return match[1];
    } else {
        throw Definition_error("The form of the klammer name " + q_(s) + " is not correct", loc);
    }
}


void label(const std::string& s)
{
    int w = 13;
    std::cout << std::right << std::setw(w) << std::setfill(' ') << s << ": ";
}

void show_args(const std::string& label_text, const std::vector<Argument>& arguments)
{
    if (!arguments.empty()) {
        label(label_text);
        for (const auto& a : arguments) {
            std::cout << a << " ";
        }
        std::cout << '\n';
    }
}

bool Klammer::defined_outside(const strings_t& files) const
{
    for (const auto& def : m_defs) {
        if (!is_in(def.loc.m_filename, files)) {
            return true;
        }
    }
    return false;
}

strings_t Klammer::get_target_names() const
{
    strings_t names {};
    for (const auto& [target, body] : m_body) {
        std::stringstream ss {};
        // ss << name << target.m_loc.m_line;
        ss << target;
        names.push_back(ss.str());
    }
    return names;  // return join(names, ","s);
}

strings_t Klammer::get_locations()
{
    strings_t locs {};
    for (auto [target, locator] : m_defloc) {
        std::cout << target << right_arrow << locator << "\n";

    }
    return {};
}

std::string Klammer::signature_text() const
{
    std::string result {};
    bool has_pos = false;
    bool has_opt = false;
    for (const auto& pos : m_parameters.m_positional) {
        result += pos.m_name;
        std::string type = pos.m_argtype.m_name;
        if (type != default_argtype) {
            result += "." + type;
        }
        result += " | ";
        has_pos = true;
    }
    if (result.size() >= 2)
        result.resize(result.size() - 2);
    auto opt_count = m_parameters.m_optional.size();
    if (opt_count <= 3 && !has_pos) {
        result += " ";
    }
    for (const auto& opt : m_parameters.m_optional) {
        if (opt_count > 3) {
            result += "\n   :" + opt.m_name;
        } else {
            result += ":" + opt.m_name;
        }
        std::string type = opt.m_argtype.m_name;
        if (type != default_argtype) {
            result += "." + type;
        }
        // result += ":" + opt.m_name + "." + opt.m_argtype.m_name;
        if (!opt.m_default.empty()) {
            result += " " + italic_on() + opt.m_default + italic_off();
        }
        result += " ";
        has_opt = true;
    }
    result = trim_right(result);
    if (has_pos) {
        result = " " + result;
    }
    if (opt_count > 3) {
        result += "\n";
    } else if (has_opt or has_pos) {
        result += " ";
    }
    result += "@\n";
    return result;
}


std::string Klammer::description_text() const
{
    std::string result = " [" + m_name + ": no description]";
    if (m_body.contains("k")) {
        result = to_string(m_body.at("k"), true);
        result = justify(result, 80, 1);
    }
    return result;
}


// Which option sets this klammer's parameters came from, and which of their
// defaults it overrode.  A reader of the signature sees the effective
// interface; this says how much of it the klammer shares with other klammers,
// which is the reason for declaring a set in the first place.
std::string Klammer::option_set_text() const
{
    std::vector<std::string> sets {};
    std::map<std::string, std::vector<std::string>> overridden {};
    for (const auto& opt : m_parameters.m_optional) {
        if (opt.m_option_set.empty()) continue;
        if (!is_in(opt.m_option_set, sets)) {
            sets.push_back(opt.m_option_set);
        }
        if (opt.m_default_overridden) {
            overridden[opt.m_option_set].push_back(":" + opt.m_name);
        }
    }
    if (sets.empty()) return "";
    std::vector<std::string> descriptions {};
    for (const auto& set : sets) {
        std::string desc = set;
        if (overridden.count(set) > 0) {
            desc += " (" + join(overridden[set], ", ") + " defaulted here)";
        }
        descriptions.push_back(desc);
    }
    return "\n Option sets: " + join(descriptions, ", ");
}

std::string Klammer::describe(int margin) const
{
    std::string result {};
    result += "@" + m_name + signature_text() + description_text() + option_set_text();
    result = add_margin(result, margin) + "\n";
    return result;
}


