#include <sstream>
#include <iterator>

#include "target_registry.h"
#include "error.h"
#include "log.h"
#include "util.h"
#include "show.h"
#include "log.h"
#include "katom.h"

std::string Target_registry::declare_name = "k";
std::string Target_registry::general_name = "*";
std::string Target_registry::optionset_name = "o";

Target_registry::Target_registry()
        : m_parameters(Parameter_set("name | desc :after_apply :after_write :includes :escape :resolve | transforms.rest"))
{
    // Registration order is display order.  The two that declare an interface
    // come first -- "k" a klammer's, "o" an option set's -- and then "*", the
    // targets themselves beginning after it.
    Target k(declare_name, "Description of parameters and klammer result", Locator());
    Target option_set(optionset_name, "Declaration of an option set: parameters shared by klammers", Locator());
    Target general(general_name, "All targets", Locator());
    add(k);
    add(option_set);
    add(general);
}

void Target_registry::add(Target target)
{
    (void)K::log(3, target);
    check_for_previous_definition(target.m_name, target.m_loc);
    m_targets[target.m_name] = target;
    m_names.push_back(target.m_name);
}

void Target_registry::add(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, std::vector<Katom>& katoms)
{
    (void)K::log(3, *begin, *(end - 1));
    auto [positional, optional, rest] =
        argument_split(begin + 1, end - 1, m_parameters.m_positional.size());
    auto values = m_parameters.value_map(positional, optional, rest, begin->m_loc);

    //std::cout << ktype << "Transformed: " << kreplaced << std::pair(begin, end) << "\n";
    //std::cout << values << "\n";
    Target target(values["name"], values["desc"], begin->m_loc);
    target.add_transforms(values["transforms"]);
    target.add_escapes(values["escape"]);
    target.add_resolves(values["resolve"]);
    target.add_after_apply(values["after_apply"]);
    target.m_includes = word_split(values["includes"]);
    // Inherit escapes AND typographic transforms from included targets: an
    // including target renders through the included one's syntax (pdf
    // through tex), so both tables apply there too.  Inherited entries are
    // appended after the including target's own, so a declaration can
    // override an inherited pair by declaring its source first.
    for (const auto& included : target.m_includes) {
        if (m_targets.count(included)) {
            for (const auto& esc : m_targets[included].m_escapes) {
                target.m_escapes.push_back(esc);
            }
            for (const auto& tr : m_targets[included].m_transforms) {
                target.add_transform(tr.first, tr.second);
            }
            for (const auto& res : m_targets[included].m_resolves) {
                target.m_resolves.push_back(res);
            }
        }
    }
    // Registration (and the previous-definition check) goes through
    // add(Target) -- the single registration path.
    add(target);

    for (auto& [name, defined_target] : m_targets) {
        if (is_in(name, target.m_includes)) {
            defined_target.m_provides.push_back(target.m_name);
        }
    }

    // std::for_each(begin, end, [](Katom& k) { k.m_type = katom_t::replaced; });
    modify_type(katom_t::replaced, begin, end);
    auto next_iter = end;
    ignore_whitespace(next_iter, katoms);
}


void Target_registry::check_for_previous_definition(const std::string& name, const Locator& loc) const
{
    if (has(name)) {
        const Target& current = m_targets.at(name);
        throw Target_error("Target \"" + name + "\" is already defined:\n  " + current.m_loc.desc(),
                           loc);
    }
}

bool Target_registry::has(const std::string& target_name) const
{
    // Membership comes from the map; m_names exists only to preserve
    // definition order for describe().
    return m_targets.count(target_name) > 0;
}


Target Target_registry::get(const std::string& target_name, const Locator& loc) const
{
    if (has(target_name) || target_name == Target_registry::general_name) {
        return m_targets.at(target_name);
    } else {
        throw Target_error("Target " + target_name + " does not exist", loc);
    }
}

void Target_registry::transform(const std::string& target_name, katom_list& katoms) const
{
    (void)K::log(3);
    m_targets.at(target_name).transform(katoms);
}

std::vector<std::string> Target_registry::user_defined() const
{
    return collect_if(
        m_names, [](const auto& name) {
            return name != Target_registry::declare_name
                && name != Target_registry::general_name
                && name != Target_registry::optionset_name; });
}

// The targets a klammer body can be applied under.  Neither "k" nor "o"
// produces output: both declare an interface and describe it.
std::vector<std::string> Target_registry::applicable() const
{
    return collect_if(
        m_names, [](const auto& name) {
            return name != Target_registry::declare_name
                && name != Target_registry::optionset_name; });
}

std::string Target_registry::describe(int margin, bool long_format,
                                      const strings_t& defined_outside) const
{
    // The built-in pseudo-targets belong to the language rather than to any
    // klammerset, so a filtered listing keeps them.
    auto shown = [&](const std::string& name) {
        if (defined_outside.empty()) return true;
        if (name == declare_name || name == general_name || name == optionset_name) {
            return true;
        }
        return !is_in(m_targets.at(name).m_loc.m_filename, defined_outside);
    };
    std::string tab(margin, ' ');
    std::stringstream ss {};
    std::vector<std::string> descs {};
    for (const std::string& name : m_names) {
        descs.push_back(m_targets.at(name).m_desc);
    }
    auto name_width = max_length(m_names);
    auto desc_width = max_length(descs);
    for (const std::string& name : m_names) {
        if (!shown(name)) continue;
        const Target& t = m_targets.at(name);
        if (long_format) {
            ss << tab << std::setfill(' ') << std::setw(name_width) << std::right << name << "  "
               << std::setw(desc_width) << std::left << t.m_desc << "  "
               << t.m_loc.str() << "\n";            
        } else {
            ss << tab << std::setfill(' ') << std::setw(name_width) << name << sp_arrow << t << "\n";
        }
    }
    return ss.str();
}

/*
void Targets::describe()
{
    std::string intro = 
        "A \"target\" specifies the output format of Klammertext processing. "
        "Targets are identified by the typical filename extension of the format. "
        "A klammer defines how it converts its arguments to the appropriate structure for one or more targets. "
        "The special target \"k\" is used for a klammer definition that describes that klammer's "
        "arguments and purpose in the various targets for which it is defined. "
        "If the klammer definition does not specify a target, the klammer can be used for any target.";
    std::cout << "Klammertext targets\n\n" << justify(intro) << "\n\n";
    for (std::string name : names) {
        if (name == Target::any_target_name)
            continue;
        targets[name]->describe();
        std::cout << "\n";
    }
}
*/
