#include <iostream>
#include <iomanip>
#include <algorithm>
#include <utility>

#include "locator.h"
#include "ktype.h"
#include "log.h"
#include "file.h"
#include "show.h"
#include "util.h"

/*
bool is_error(katom_t t)
{
    return
        t == katom_t::undefined ||
        t == katom_t::no_matching_end ||
        t == katom_t::no_matching_begin ||
        t == katom_t::karg_error ||
        t == katom_t::klammer_definition_type_error ||
        t == katom_t::eval_error;
}
*/

bool is_active(katom_t type)
{
    return type != katom_t::ignored
        && type != katom_t::replaced;
}

bool is_printable(katom_t type)
{
    return printable_katom_types.find(type) != printable_katom_types.end();
}

bool is_deftype(katom_t type)
{
    return
        type == katom_t::klammer_definition ||
        type == katom_t::klammer_instance ||
        type == katom_t::klammer_override ||
        type == katom_t::klammer_default;
}

std::string pattern_display(Ktype t)
{
    std::string pat{};
    if (t.m_type == katom_t::space) {
        pat = "<space>";
    } else if (t.m_type == katom_t::newline) {
        pat = "\\n";
    } else if (t.m_type == katom_t::ws_space) {
        pat = "#+ or #+<number>";
    } else if (t.m_type == katom_t::ws_newline) {
        pat = "#/ or #/<number>";
    } else if (t.m_type == katom_t::special) {
        pat = "^@,  ^|, ^#, ^:, or ^^";
    } else if (t.m_type == katom_t::apply_end) {
        pat = "@ or <name>@";
    } else if (t.m_type == katom_t::define_end) {
        pat = "@@ or <name>@@";
    } else if (t.m_type == katom_t::machine_end) {
        pat = "@@@ or <name>@@@";
    } else if (t.m_type == katom_t::ws_added) {
        pat = "<space> or \\n";
    } else if (t.m_type == katom_t::word) {
        pat = "<only-letters>";
    } else if (t.m_type == katom_t::text) {
        pat = "<no-special-chars>";
    } else if (t.m_type == katom_t::nonascii) {
        pat = "^<code> or ^<code>^";
    } else {
        pat = t.m_pattern;
        pat = string_replace(pat, definition_name, "<name>");
        pat = string_replace(pat, "\\", "");
    }
    return pat;
}

std::string regex_display(Ktype t)
{
    std::string rgx{};
    if (t.m_type == katom_t::space) {
        rgx = "<space>";
    } else if (t.m_type == katom_t::newline) {
        rgx = "\\n";
    } else {
        rgx = t.m_pattern;
    }
    return rgx;
}

Ktype find_katom_type(katom_t type)
{
    for (auto t : katom_types) {
        if (t.m_type == type) {
            return t;
        }
    }
    throw Internal_error("Unknown katom_t: " + std::to_string((int)type));
}

void describe_katoms(bool show_regex)
{
    size_t name_w = 0;
    size_t pattern_w = 0;
    size_t regex_w = 0;
    size_t description_w = 0;
    std::stringstream ss {};
    ss << R"(
A "katom" is an individual element in the Klammertext input text.
Each katom has a type, listed below. You can see how Klammertext
divides up text into katoms with the "kdiag" command.)" << "\n\n";
    if (show_regex) {
        ss << R"(The "Regex" column is the argument to the std::regex C++ function.)";
    } else {
        ss << R"(In the katom patterns, "<name>" is a word that begins with a letter
            and only contains letters, numbers, or the underscore (_) or period (.) characters. )";
    }
    ss << "A <number> is an integer greater than or equal to 1. "
        "A <code> is one of the non-ASCII codes that are displayed by the command "
        "\"kdesc -c\".";

    for (auto t : katom_types) {
        name_w = std::max(name_w, t.m_name.size());
        pattern_w = std::max(pattern_w, pattern_display(t).size());
        regex_w = std::max(regex_w, regex_display(t).size());
        description_w = std::max(description_w, t.m_description.size());
    }
    pattern_w++;
    regex_w++;

    std::cout << justify(ss.str()) << "\n\n"
              << std::setfill(' ') << boldblack << std::right << std::setw(6) << "Index"
              << std::left
              << std::setw(name_w) << " Name" << "  ";
    if (show_regex) {
        std::cout << std::setw(regex_w) << " Regex" << "   ";
    } else {
        std::cout << std::setw(pattern_w) << " Pattern" << "  ";
    }
    std::cout << std::setw(description_w) << "Description"
              << "\n" << black;

    for (auto typ : katom_type_display_order) {
        auto t = find_katom_type(typ);
        std::cout << " " << std::right << std::setw(3)
                  << static_cast<int>(t.m_type) << "   "
                  << std::left
                  << std::setw(name_w) << t.m_name << "  ";
        if (show_regex) {
            std::cout << std::setw(regex_w) << regex_display(t) << "  ";
        } else {
            std::cout << std::setw(pattern_w) << pattern_display(t) << " ";
        }

        std::cout << std::setw(description_w) << t.m_description << "\n";
    }
    std::cout << "\n";
}

void describe_rewrite_patterns()
{
    if (verbose_level > 0) {
        std::string desc_text = "doc/katom_patterns.desc";
        std::cout << "\n"
                  << justify(string_from_file(klammertext_filename(desc_text)))
                  << "\n\n";
    }
    size_t width1 = 0;
    size_t width2 = 0;
    for (auto [desc, pattern, replace] : katom_rewrite_rules) {
        width1 = std::max(width1, desc.size());
        width2 = std::max(width2, pattern.m_pattern.size());
    }
    width1 += 2;
    width2 += 2;

    std::cout << boldblack << std::setfill(' ')
              << std::setw(width1) << std::left << "  Description"
              << std::setw(width2) << std::left << "  Pattern"
              << "  Replacement" << black << "\n";

    for (auto [desc, pattern, replace] : katom_rewrite_rules) {
        desc[0] = toupper(desc[0]);
        std::cout << "  "
                  << std::left
                  << std::setw(width1) << desc
                  << std::setw(width2) << pattern.m_pattern
                  << replace
                  << "\n";
    }
    if (verbose_level > 0) {
        std::string notes_text = "doc/katom_patterns.notes";
        std::cout << "\n\n"
                  << justify(string_from_file(klammertext_filename(notes_text)))
                  << "\n\n";
    }
}

std::string type_to_name(katom_t type)
{
    auto var = std::find_if(katom_types.begin(), katom_types.end(),
                            [&] (Ktype t) { return t.m_type == type; });
    return var->m_name;
}
