#pragma once

#include <vector>
#include <map>
#include <string>
#include <regex>
#include <tuple>
#include <set>

inline const std::string at_s {"@"};
inline const std::string at2_s { "@@" };
inline const std::string at3_s { "@@@" };
inline const std::string bar_s { "|" };
inline const std::string open_s { R"(\()" };
inline const std::string close_s { R"(\))" };
inline const std::string karg_s { "*" };
inline const std::string read_s { "read" };
inline const std::string eval_s { "eval" };
inline const std::string cond_s { "cond" };
inline const std::string lit_s { "lit" };

inline const std::string hat_s { "^" };

inline const std::string ignore_line_s { "#" };
inline const std::string ignore_begin_s { R"(#\[)" };
inline const std::string ignore_end_s { R"(\]#)" };
inline const std::string ignore_rest_s { "##" };

inline const std::string ws_remove_s { "#-" };
inline const std::string ws_space_s { R"(#\+\d*)" };
inline const std::string ws_newline_s { R"(#/\d*)" };

// const std::string kname = "[a-zA-Z]+[a-zA-Z0-9_.]*";
// const std::string dname = "[a-zA-Z]+[a-zA-Z0-9_]*";

// const std::string k_name = R"([^^@#|\:]+)";
inline const std::string definition_name = R"([a-zA-Z][a-zA-Z0-9_.]*)";
// The name at the START of a definition may carry a comma-separated list of
// targets ("@@table.html,tex"), so its katom runs through commas.  This is
// deliberately looser than the grammar: the katomizer's job is to delimit the
// token, and parse_name() in klammer.cpp validates the list, where a
// malformed one can be reported with its location.  The comma stays out of
// definition_name itself -- that pattern also delimits klammer APPLICATIONS,
// option names and "*arg*" variables, where a comma is ordinary writer text.
// "*" is here for the same reason: "@@date.*" declares a klammer for every
// target explicitly, and "*" is a Klammertext special everywhere else.
inline const std::string definition_begin_name = R"([a-zA-Z][a-zA-Z0-9_.,*]*)";


enum class katom_t {
    space,
    word,
    newline,
    text, 
    apply_begin,
    apply_end,
    bar,
    double_bar,
    option_name,
    read_begin,
    eval_begin,
    cond_begin,
    define_begin,
    define_end,
    klammer_definition,
    klammer_instance,
    klammer_override,
    klammer_default,
    karg,
    machine_begin,
    machine_end,
    ignore_begin,
    ignore_end,
    ignore_rest,
    ignore_line,
    ws_remove,
    ws_space,
    ws_newline,
    special,
    nonascii,
    literal_begin,
    literal_end,
    ws_added,
    literal,
    eval_result,
    replaced,
    ignored,
};


inline const
std::vector<katom_t> katom_type_display_order {
    katom_t::space,
    katom_t::word,
    katom_t::newline,
    katom_t::text, 
    katom_t::apply_begin,
    katom_t::apply_end,
    katom_t::bar,
    katom_t::double_bar,
    katom_t::option_name,
    katom_t::read_begin,
    katom_t::eval_begin,
    katom_t::cond_begin,
    katom_t::define_begin,
    katom_t::define_end,
    katom_t::klammer_definition,
    katom_t::klammer_instance,
    katom_t::klammer_override,
    katom_t::klammer_default,
    katom_t::karg,
    katom_t::machine_begin,
    katom_t::machine_end,
    katom_t::ignore_begin,
    katom_t::ignore_end,
    katom_t::ignore_rest,
    katom_t::ignore_line,
    katom_t::ws_remove,
    katom_t::ws_space,
    katom_t::ws_newline,
    katom_t::special,
    katom_t::nonascii,
    katom_t::literal_begin,
    katom_t::literal_end,
    katom_t::ws_added,
    katom_t::literal,
    katom_t::eval_result,
    katom_t::replaced,
    katom_t::ignored
};

class Ktype;

inline std::map<katom_t, std::string> katom_type_names {};
inline std::map<katom_t, std::string> katom_type_descs {};
inline std::vector<katom_t> katom_type_list {};

class Ktype {
public:
//    Ktype() = default;
    Ktype(katom_t type, std::string name, std::string pattern, std::string description, bool use_equal = false)
            : m_type(type),
              m_name(name),
              m_pattern(pattern),
              m_description(description),
              m_use_equal(use_equal),
              m_rgx(std::regex(pattern)) {
        katom_type_list.push_back(type);
        katom_type_names.insert({m_type, m_name});
        katom_type_descs.insert({m_type, m_description});
    }
    bool match(const std::string& s) const {
        return m_use_equal ? s == m_pattern : std::regex_match(s, m_rgx);
    }
    katom_t m_type;
    std::string m_name;
    std::string m_pattern;
    std::string m_description;
    bool m_use_equal;
    std::regex m_rgx;
};

inline const
std::vector<Ktype> katom_types {
    Ktype(katom_t::space, "space", " ", "One space character", false),
    Ktype(katom_t::word, "word", "[a-zA-Z]+", "Text only containing letters", false),
    Ktype(katom_t::newline, "newline", "\n", "One newline character", false),
    Ktype(katom_t::bar, "bar", "\\|", "Bar character used as positional argument separator", false),
    Ktype(katom_t::double_bar, "double-bar", "\\|\\|", "Separator for compound positional arguments", false),
    Ktype(katom_t::read_begin, "read-begin", at_s+read_s, "Beginning of the file input klammer", false),
    Ktype(katom_t::eval_begin, "eval-begin", at_s+eval_s, "Beginning of the evaluation klammer", false),
    Ktype(katom_t::cond_begin, "cond-begin", at_s+cond_s, "Beginning of the conditional (if/then/else) klammer", false),
    Ktype(katom_t::apply_begin, "apply-begin", at_s + definition_name, "Beginning of a klammer call"),
    Ktype(katom_t::apply_end, "apply-end", "(" + definition_name + ")?" + at_s, "End of a klammer call"),
    Ktype(katom_t::option_name, "option-name", ":" + definition_name, "Optional argument name"),
    Ktype(katom_t::define_begin, "define-begin", at2_s + definition_begin_name, "Beginning of a klammer definition"),
    Ktype(katom_t::define_end, "define-end", "(" + definition_name + ")?" + at2_s, "End of a klammer definition"),
    Ktype(katom_t::klammer_default, "klammer-default", "::::", "Define klammer default value for possible override"),
    Ktype(katom_t::klammer_override, "klammer-override", ":::", "Override existing klammer definition"),
    Ktype(katom_t::klammer_definition, "klammer-definition", ":", "Klammer definition, including parameters"),
    Ktype(katom_t::klammer_instance, "klammer-instance", "::", "Klammer definition using previously defined parameters"),

    Ktype(katom_t::karg, "klammer-arg", "\\*" + definition_name +"\\*", "Klammer argument in klammer body definition"),
    Ktype(katom_t::machine_begin, "machine-begin", at3_s + definition_name, "Beginning of a processor modification definition"),
    Ktype(katom_t::machine_end, "machine-end", "(" + definition_name + ")?" + at3_s, "End of a processor modification definition"),
    Ktype(katom_t::ignore_begin, "ignore-begin", ignore_begin_s, "Beginning of text to remove"),
    Ktype(katom_t::ignore_end, "ignore-end", ignore_end_s, "End of text to remove"),
    Ktype(katom_t::ignore_rest, "ignore-rest", ignore_rest_s, "Remove all text to the end of file or string"),
    Ktype(katom_t::ignore_line, "ignore-line", ignore_line_s, "Remove all text to the first newline, inclusive"),
    Ktype(katom_t::ws_remove, "ws-remove", ws_remove_s, "Remove all whitespace at this point"),
    Ktype(katom_t::ws_space, "ws-space", ws_space_s, "Remove all whitespace, leaving <number> spaces (default: 1)"),
    Ktype(katom_t::ws_newline, "ws-newline", ws_newline_s, "Remove all whitespace, leaving <number> newlines (default: 1)"),
    Ktype(katom_t::special, "special-char", R"(\^[@|#^:*])", "Klammertext special character treated as regular text"),
    //Ktype(katom_t::nonascii, "non-ascii-char", R"(\^\w(?:.|\^))", "Non-ASCII character"),
    Ktype(katom_t::nonascii, "non-ascii-char", R"((\^(\w(?:.|\^)))|(\^[A-Fa-f0-9]{1,6}\^))", "Non-ASCII character"),

    // Ktype(katom_t::word, "word", R"([a-z][a-z0-9_]*)", "Lower-case letters, numbers, or underscore"),
    // Ktype(katom_t::text, "text",

    Ktype(katom_t::literal_begin, "literal-begin", R"(\^')", "Begin unprocessed text"),
    Ktype(katom_t::literal_end, "literal-end", R"('\^)", "End unprocessed text"),
    Ktype(katom_t::text, "text", R"([^^@#|]+)", "No special characters, whitespace, or \":\" at the beginning"), // spaces, @, #, |, or ^, 

    Ktype(katom_t::ws_added, "ws-added", "<space> or \\n", "Added whitespace characters from #+ and #/"),
    Ktype(katom_t::literal, "literal", "", "Literal katom (changed from its original type by ^'...'^)"),
    Ktype(katom_t::eval_result, "eval-result", "", "Katom produced by @eval klammer"),
    
    Ktype(katom_t::replaced, "replaced", "", "A katom replaced by definitions, applications, or file input"),
    Ktype(katom_t::ignored, "ignored", "", "A katom ignored by the action of a \"#\" katom"),
//    Ktype(katom_t::undefined, "undefined", "", "Undefined pattern")
};

class Rgx {
 public:
    explicit Rgx(std::string pattern)
    : m_pattern(pattern)
    , m_regex(std::regex(pattern)) {}
    std::string m_pattern;
    std::regex m_regex;
};

inline const
std::vector<std::tuple<std::string, Rgx, std::string>> katom_rewrite_rules {
    { "bar precedence", Rgx(R"((.*?)(\^\|)(.*))"), "$1 $2 $3" },
    { "literal without space",     Rgx(R"((\^')(.*?)('\^))"), "$1 $2 $3" },
    { "literal start to the left", Rgx(R"((.+)(\^'))"), "$1 $2" },
    { "literal start to the right", Rgx(R"((\^')(.+))"), "$1 $2" },
    { "literal end to the left",  Rgx(R"(('\^)(.+))"), "$1 $2" },
    { "literal end to the right",  Rgx(R"((.+)('\^))"), "$1 $2" },
    //{ "successive non-ascii characters", Rgx(R"((.?)(\^\w[-\"^`'~hcbrdwa])(\^\w)(.?))"), "$1 $2 $3 $4" },
    // { "special character", Rgx(R"((.*?)(\^[@\|\^#\*])(.*))"), "$1 $2 $3" },
    { "special character", Rgx(R"((.*?)(\^[@|^#*:])(.*))"), "$1 $2 $3" },
    { "non-diacritic non-ascii", Rgx(R"((.*?)(\^[a-zA-Z0-9]{5}\^)(.*?))"), "$1 $2 $3" },
    { "non-diacritic non-ascii 2", Rgx(R"((.*?)(\^[a-zA-Z0-9]{1,4}\^)(.*?))"), "$1 $2 $3" },
    { "diacritic non-ascii", Rgx(R"((.*?)(\^[a-zA-z][^^])(.*?))"), "$1 $2 $3" },
    { "double bar separator", Rgx(R"(([^\s\^]+?)(\|\|)(.*))"), "$1 $2 $3" },
    { "bar separator", Rgx(R"(([^\s\^]+?)(\|)(.*))"), "$1 $2 $3" },
    { "embedded ignore begin", Rgx(R"((.*?)#\[(.*))"), "$1 #[ $2" },
    { "embedded ignore end", Rgx(R"((.*?)\]#(.*))"), "$1 ]# $2" },
    { "embedded remove whitespace", Rgx(R"((.*?)#-(.*))"), "$1 #- $2" },
    { "single argument for special character", Rgx(R"((@\w+)-(\^[@\|:\*\^])@?)"), "$1 $2 @" },
    // { "special character", Rgx(R"((.*?)(\^[@\|:\*\^])(.*))"), "$1 $2 $3" },
    { "klammer body arguments", Rgx(R"((.*?)(\*\w+\*)(.*))"), "$1 $2 $3" },
    { "trailing punctuation", Rgx(R"((\w?)@([().,:;?'!]))"), "$1@ $2" },
    { "trailing punctuation", Rgx(R"(@([().,:;?'!].*))"), "@ $1" },
    { "trailing punctuation, shortcut", Rgx(R"((@\w+)-(\w+)([().,:;?'!][^\s]*))"), "$1 $2 @ $3" },
    // { "single argument shortcut", Rgx(R"((@\w+)-([^\s@]+)@?)"), "$1 $2 @" },
    { "no argument klammer", Rgx(R"((@\w+)@)"), "$1 @" },
    { "left parenthesis", Rgx(R"(([(])@(\w+))"), "$1 @$2" },
    { "embedded klammer", Rgx(R"(([^@]+)(@[^@]+@)([^@]+))"), "$1 $2 $3" },
    { "embedded klammer to the right", Rgx(R"(([^@]+)(@[^@]+@))"), "$1 $2" },
    { "embedded klammer to the left", Rgx(R"((@[^@]+@)([^@]+))"), "$1 $2" },
    { "embedded add whitespace", Rgx(R"(([^\s])(#\+\d*)([^\s]))"), "$1 $2 $3" },
    { "embedded add whitespace to the left", Rgx(R"(([^\s])(#\+\d*))"), "$1 $2" },
    { "embedded add whitespace to the right", Rgx(R"((#\+\d*)([^\s]))"), "$1 $2" },
    { "embedded ignore on the left", Rgx(R"(#([^-+/\s]))"), "# $1" },
    { "embedded ignore on the right", Rgx(R"(([^\s])#)"), "$1 #" },
};


inline const
std::set<katom_t> printable_katom_types {
    katom_t::word,
    katom_t::space,
    katom_t::newline,
    katom_t::literal,
    katom_t::ws_added
};

inline const
std::set<katom_t> open_level {
    katom_t::literal_begin,
    katom_t::read_begin,
    katom_t::eval_begin,
    katom_t::cond_begin,
    katom_t::apply_begin,
    katom_t::define_begin,
    katom_t::machine_begin,
    katom_t::ignore_begin
};

inline const
std::set<katom_t> close_level {
    katom_t::literal_end,
    katom_t::apply_end,
    katom_t::apply_end,
    katom_t::apply_end,
    katom_t::apply_end,
    katom_t::define_end,
    katom_t::machine_end,
    katom_t::ignore_end
};

inline
std::map<katom_t, katom_t> katom_spans {
    { katom_t::literal_begin, katom_t::literal_end },
    { katom_t::read_begin, katom_t::apply_end },
    { katom_t::eval_begin, katom_t::apply_end },
    { katom_t::cond_begin, katom_t::apply_end },
    { katom_t::apply_begin, katom_t::apply_end },
    { katom_t::define_begin, katom_t::define_end },
    { katom_t::machine_begin, katom_t::machine_end },
    { katom_t::ignore_begin, katom_t::ignore_end }
};


bool is_active(katom_t t);
bool is_printable(katom_t type);

void describe_katoms(bool show_regex = false);
void describe_rewrite_patterns();

bool is_deftype(katom_t type);

std::string type_to_name(katom_t type);
