#include <numeric>

#include "file.h"
#include "argv.h"
#include "error.h"
#include "log.h"
#include "util.h"
#include "show.h"

std::string Argv::delimiter = "--";

std::ostream& operator<<(std::ostream& os, const Arg& arg)
{
    os << "<" << arg.m_type << " " << arg.m_name
       << " " << q_(arg.m_value) << ">";
    return os;
}

std::string wrap_around(const std::string& text, std::size_t indent, std::size_t width=96)
{
    std::string result {};
    std::size_t current = indent;
    std::string margin(indent, ' ');
    for (const std::string& word : word_split(text)) {
        if (current + 1 + word.size() > width) {
            result += "\n" + margin;
            current = indent;
        }
        result += word + " ";
        current += word.size() + 1;
    }
    return result;
}

std::string flag_name(const std::string& name)
{
    std::string result {};
    if (name.size() == 1) {
        result = "-" + name;
    } else {
        result = "--" + name;
    }
    return result;
}

std::string Arg::symbol()
{
    std::string result = m_name;
    if (m_type != "req") {
        result = flag_name(m_name);
        /*
        if (m_name.size() == 1) {
            result = "-" + m_name;
        } else {
            result = "--" + m_name;
        }
        */
    } else {
        result = "<" + result + ">";
    }
    return result;
}


void Arg::make_regex(const std::string& key)
{
    std::string pat {};
    if (regex_symbols.find(key) != regex_symbols.end()) {
        pat = regex_symbols[key];
        m_rgx_symbol = key;
        // std::cout << "rgx_symbol: " << m_rgx_symbol << "\n";
    } else if (key.find("(") != std::string::npos) {
        pat = key;
    }
    if (pat.size() == 0) {
        throw Definition_error("No regex pattern defined for \"" + key + "\".");
    }
    m_pattern = pat;
    m_rgx = std::regex(m_pattern);
}


void Argv::update_width(Arg arg)
{
    m_syntax_size = std::max(m_syntax_size, arg.m_syntax.size());
}

std::string get_regex_desc(const std::string& desc)
{
    if (regex_desc.find(desc) != regex_desc.end()) {
        return regex_desc[desc];
    } else {
        return desc;
    }
}

void Argv::flag(const std::string& name, const std::string& desc)
{
    (void)K::log(2, name, desc);
    Arg arg {};
    arg.m_type = "flag";
    arg.m_name = name;
    arg.m_desc = get_regex_desc(desc);
    arg.m_rgx = std::regex(R"((\w+))");
    arg.m_syntax = arg.symbol();
    arg.m_value = "false";
    m_args[name] = arg;
    m_names.push_back(name);
    m_flag_names.push_back(name);
    m_hyphen_markers.push_back(flag_name(name));
    update_width(arg);
}

void Argv::req(const std::string& name, const std::string& desc,
               const std::string& regex_pattern, bool required)
{
    (void)K::log(2, name, desc, regex_pattern);
    Arg arg {};
    // "posopt" is a positional that may be absent.  It stays in m_req_names so
    // it keeps its POSITION; only its absence is tolerated.
    arg.m_type = required ? "req" : "posopt";
    arg.m_name = name;
    arg.m_desc = get_regex_desc(desc);
    arg.make_regex(regex_pattern);
    arg.m_syntax = required ? "<" + name + ">" : "[<" + name + ">]";
    m_args[name] = arg;
    m_names.push_back(name);
    m_req_names.push_back(name);
    update_width(arg);
}

void Argv::opt(const std::string& name, const std::string& desc, const std::string& parameter, const std::string& default_value, const std::string& regex_pattern)
{
    (void)K::log(2, name, desc, parameter, default_value, regex_pattern);
    Arg arg {};
    arg.m_type = "opt";
    arg.m_name = name;
    arg.m_parameter = parameter;
    arg.m_default_value = default_value;
    arg.m_value = default_value;
    arg.m_desc = get_regex_desc(desc);
    arg.make_regex(regex_pattern);
    arg.m_syntax = arg.symbol() + " <" + arg.m_parameter + ">";
    m_args[name] = arg;
    m_names.push_back(name);
    m_opt_names.push_back(name);
    m_hyphen_markers.push_back(flag_name(name));
    update_width(arg);
}

void Argv::var(const std::string& name, const std::string& desc,
               const std::string& parameter)
{
    (void)K::log(2, name, desc);
    Arg arg {};
    arg.m_type = "var";
    arg.m_name = name;
    arg.m_desc = get_regex_desc(desc);
    arg.m_syntax = arg.symbol();
    if (!parameter.empty()) {
        arg.m_syntax += " [<" + parameter + ">]";
    }
    m_args[name] = arg;
    m_names.push_back(name);
    m_var_names.push_back(name);
    m_hyphen_markers.push_back(flag_name(name));
    update_width(arg);
}

void Argv::parse_vars(strings_t& words, string_map& named_args)
{
    for (const std::string& name : m_var_names) {
        auto it = std::ranges::find(words, flag_name(name));
        named_args[name] = "";
        if (it == words.end()) {
            continue;
        }
        m_given.insert(name);
        auto first = it + 1;
        auto last = first;
        while (last != words.end() && !(*last).empty() && (*last)[0] != '-') {
            last++;
        }
        named_args[name] = join(strings_t(first, last), " ");
        m_vectors[name] = strings_t(first, last);
        words.erase(it, last);
    }
}

void Argv::usage_line(Arg arg)
{
    std::cout.fill(' ');
    std::cout << "  " << std::left << std::setw(m_syntax_size) << arg.m_syntax << "  "
              << wrap_around(arg.m_desc, m_syntax_size + 6) << "\n";
}

// A positional argument, whether or not it may be absent.  Both kinds are
// listed under "Arguments:" and must therefore be skipped when the options are
// listed -- testing only for "req" printed an optional positional twice, once
// in each section (kdiag's "[<input>]").
static bool is_positional(const std::string& type)
{
    return type == "req" || type == "posopt";
}

void Argv::usage(const std::string& command)
{
    std::cout << "\nUsage: " << command << " ";
    for (const std::string& name : m_req_names) {
        std::cout << m_args[name].m_syntax << " ";
    }
    if (m_opt_names.size() + m_flag_names.size() > 5) {
        std::cout << "[<optional-arguments>]\n";
    } else {
        for (const std::string& name : m_names) {
            if (is_positional(m_args[name].m_type)) {
                continue;
            }
            std::cout << "[" + m_args[name].m_syntax + "] ";
        }
        std::cout << "\n";
    }
    if (!m_req_names.empty()) {
        //std::cout << "\n" << plural("Argument", m_req_names.size()) << ":\n";
        std::cout << "\n" << "Arguments:\n";
        for (const std::string& req_name : m_req_names) {
            usage_line(m_args[req_name]);
        }
    }
    int flag_count = m_flag_names.size();
    int opt_count = m_opt_names.size();
    if (flag_count > 0 || opt_count > 0) {
        std::cout << "\n" << plural("Option", flag_count + opt_count) << ":\n";
    }

    for (const auto& name : m_names) {
        if (is_positional(m_args[name].m_type)) {
            continue;
        }
        usage_line(m_args[name]);
    }
    std::cout << "\n";
}


void Argv::check_flags_and_options(const std::string& command, strings_t& words)
{
    std::vector<std::string> not_defined {};
    for (auto word : words) {
        if (word[0] == '-' && word != Argv::delimiter && !is_in(word, m_hyphen_markers)) {
            not_defined.push_back(word);
        }
    }
    if (!not_defined.empty()) {
        std::stringstream ss {};
        ss << "The following flags were not defined for command " << q_(command) << ":\n";
        for (auto w : not_defined) {
            ss << "  " << w << "\n";
        }
        throw Argument_error(ss.str(), Locator::none());
    }
}


void Argv::parse_flags(strings_t& words, string_map& named_args)
{
    std::vector<std::string> flag_args {};
    for (std::string flag : m_flag_names) {
        // std::cout << "Flag: " << flag << "\n";
        if (is_in(flag_name(flag), words)) {
            flag_args.push_back(flag);
            remove_element(words, flag_name(flag));
            named_args[flag] = "true";
        } else {
            named_args[flag] = "false";
        }
    }
    /*
    std::vector<std::string> not_defined {};
    for (auto word : words) {
        if (word[0] == '-' && word != Argv::delimiter) {
            not_defined.push_back(word);
        }
    }
    if (!not_defined.empty()) {
        std::stringstream ss {};
        ss << "The following flags were not defined for command " << q_(command) << ":\n";
        for (auto w : not_defined) {
            ss << "  " << w << "\n";
        }
        throw Argument_error(ss.str(), Locator::none());
    }
    */
    // std::cout << "Flags found: " << flag_args << "\n";
}

void Argv::parse_optional(strings_t& words, string_map& named_args)
{
    // msg() << "parse_optional: " << words << "\n";
    std::map<std::string, std::string> opt_args {};
    for (std::string opt : m_opt_names) {
        // std::cout << "Opt: " << opt << sp_arrow << m_args[opt].m_pattern << "\n";
        auto it = std::ranges::find(words, flag_name(opt));
        if (it != words.end()) {
            // msg() << "words: " << words.size() << " " << words << "\n";
            size_t index = std::distance(words.begin(), it);
            // std::cout << "  Found: " << words[index] << "\n";
            //std::vector<std::string> opt_args = {};
            index++;
            // An option declared with opt() takes a value, so the word after
            // the flag must exist.  Written last with nothing after it -- a
            // bare "kdesc -v", or "ktext doc.kt -t" -- this read past the end
            // of the vector and the command died with SIGSEGV, naming
            // nothing.  (Two bounds checks used to sit here, commented out;
            // they would have returned a HALF-PARSED option rather than
            // reporting the mistake, so this reports it instead.)  A valueless
            // option is declared with flag(), not opt().
            if (index >= words.size()) {
                throw Argument_error(
                    "The option " + flag_name(opt) + " needs a value: " +
                    m_args[opt].m_syntax + ".  Enter \"" +
                    file_basename(command_name) + "\" for the list of arguments.",
                    Locator::none());
            }
            std::string opt_arg = words[index] + " ";
            index++;
            std::regex opt_regex = m_args[opt].m_rgx;

            // Only a LIST-valued option may span several argv words
            // (--klammersets a b c; --katoms type index).  A single-valued
            // option takes exactly ONE argv element -- argv boundaries are
            // authoritative -- so a positional written after it is not
            // swallowed into the value.  The former unconditional run
            // consumed everything up to the next dash-word: "-s 'text'
            // doc.kt" folded doc.kt into the -s TEXT (and rendered its
            // pathname), and "--klammersets none doc.kt" consumed the input
            // filename, so ktext reported that none was given.  -s must be
            // usable in any argv position: its role with a file argument is
            // to modify the interpretation of that file, and it is
            // evaluated first regardless of where it is written.  A list
            // value stops at the "--" delimiter (it begins with '-'), which
            // remains the escape for filenames after a list option.
            bool list_valued = m_args[opt].m_rgx_symbol == "'list'"
                            || m_args[opt].m_rgx_symbol == "'katom_display'";
            while (list_valued && index < words.size() && words[index][0] != '-'
                   && std::regex_match(trim(opt_arg + words[index]), opt_regex)

                ) {
                // opt_args.push_back(words[index++]);
                opt_arg += words[index++] + " " ;
            }
            opt_arg = trim(opt_arg);
            // std::cout << "    Value: " << opt_arg << "[" << index << "]\n";
            opt_args[opt] = opt_arg;
            words.erase(it, words.begin() + index);
            // std::cout << "    Remaining:  " << words << "\n";
            named_args[opt] = opt_arg;
        } else { // Not found

        }
    }
    // std::cout << "opt_args:\n";
    // if (opt_args.empty()) {
    //     std::cout << "[none]";
    // } else {
    //     std::cout << opt_args;
    // }
    // std::cout << "\n";
}

void Argv::parse_positional(const std::string& command, //strings_t words,
                            std::string pos_args, string_map& named_args)
{
    for (const std::string& req : m_req_names) {
        auto arg = m_args[req];
        auto [substring, rest, found] = regex_split_prefix(arg.m_rgx, pos_args);
        if (!found) {
            // An optional positional simply stays empty; only a required one
            // is an error.
            if (arg.m_type == "posopt") {
                named_args[req] = "";
                continue;
            }
            std::stringstream ss {};
            ss << "The argument " << q_(req) << " was not found in:\n  " << command;
            throw Argument_error(ss.str(), Locator::none());
        }
        named_args[req] = substring;
        // Drop the whitespace that separated this positional from the next.
        // regex_split_prefix() requires its match at position 0 and returns
        // the remainder verbatim, so without this the SECOND required
        // argument is always "not found": its pattern is offered " second"
        // and cannot match a leading space.
        //
        // Trimming here rather than at the top of the loop is deliberate: the
        // first positional still receives pos_args exactly as before, so a
        // command with ONE required argument -- which is every command that
        // ships (ktext's `filenames`, kdiag's `input`) -- parses
        // bit-identically. Only the case that never worked changes.
        size_t next = rest.find_first_not_of(" \t");
        pos_args = (next == std::string::npos) ? "" : rest.substr(next);
    }
    // std::cout << "Remaining words: " << words << "\n" << pos_args << "\n";
}
    


std::map<std::string, std::string>
Argv::classify_arguments(int argc, char* argv[], bool full_parse)
{
    (void)K::log(2, argc);
    if (argc == 1) {
        return {};
    }
    std::map<std::string, std::string> named_args {};
    std::vector<std::string> words(argv + 1, argv + argc);
    if (full_parse) {
        check_flags_and_options(argv[0], words);
    }
    parse_vars(words, named_args);
    parse_flags(words, named_args);
    parse_optional(words, named_args);
    parse_positional(argv_to_string(argc, argv), join(words, " "), named_args);
    words.erase(std::remove(words.begin(), words.end(), Argv::delimiter), words.end());
    if (m_req_names.size() == 1) {
        // A single positional argument owns all remaining words; keep the
        // original argv boundaries alongside the joined named_args value.
        m_vectors[m_req_names[0]] = words;
    }
    // std::cout << "Named args:\n" << named_args << "\n";
    return named_args;
}

void Argv::check_required(
    const std::vector<std::string>& req_args, const std::string& command)
{
    (void)K::log(2);
    auto required = m_req_names.size();
    auto given = req_args.size();

    if (required > given) {
        std::string missing = m_req_names[required - given - 1];
        if (given == 0 && (m_args[missing].m_rgx_symbol == "'list'"
                           || m_args[missing].m_type == "posopt")) {
            return;
        }
        std::stringstream ss {};
        ss << "The required argument \"" << missing
           << "\" was not provided in command \"" << command << "\"";
        throw Argument_error(ss.str());
    } else if (required < given) {
        std::stringstream ss {};
        ss << "Too many required arguments were given for command \""
           << command << "\" (" << required << " needed but "
           << given << " given" << ")";
        throw Argument_error(ss.str());
    }
}

void Argv::check_flags(const string_map& arg_map, const std::string& command)
{
    (void)K::log(3);
    // std::cout << "arg_map:\n" << arg_map << "\n";

    strings_t undefined {};
    for (const auto& pair : arg_map) {
        auto [key, value] = pair;
        // std::cout << "m_type: " << m_args[key].m_type << "\n";
        if (key[0] != '_') {
            if (m_args[key].m_type != "req"
                && !contains(m_flag_names, key)
                && !contains(m_opt_names, key)) {
                undefined.push_back(key);
            }
        }
    }
    if (!undefined.empty()) {
        std::stringstream ss {};
        ss << "Undefined arguments given for command \"" << command << "\":";
        for (const std::string& undef : undefined) {
            ss << " " << flag_name(undef);
        }
        throw Argument_error(ss.str(), Locator::none());
    }
}


void Argv::parse(int argc, char* argv[], bool full_parse)
{
    (void)K::log(2);
    command_name = argv[0];
    // No describe() here.  A call sat at this point, before any value has
    // been assigned, so it could only ever print a table of "<none>" -- and
    // because set_verbose_level() parses a throwaway Argv before the real
    // one, EVERY command would print that table on every run.  Silencing
    // Argv::describe() itself was the wrong half of the fix: it also silenced
    // the callers that legitimately want it (kdesc -v, argv_test).  The
    // caller decides; parse() does not print.
    auto input_args = classify_arguments(argc, argv, full_parse);
    // std::cout << "parse() classify:\n" << input_args << "\n";

    for (auto [key, value] : input_args) {
        m_args[key].m_value = value;
    }

    // std::cout << "m_flag_names: " << m_flag_names << "\n";
    /*
    for (const std::string& name : m_flag_names) {
        if (input_args.find(name) != input_args.end()) {
            m_args[name].m_value = "true";
        }
    }
    */
    /*
    for (const std::string& name : m_opt_names) {
        if (input_args.find(name) != input_args.end()) {
            std::smatch match {};
            if (std::regex_match(input_args[name], match, m_args[name].m_rgx)) {
                m_args[name].m_value = input_args[name];
            } else {
                usage(file_basename(argv[0]));
                throw Argument_error(
                    "Incorrect value for option \"" + name + "\":\n" + m_args[name].m_desc + "\n",
                    Locator());
            }
        }
    }


    std::cout << "input_args:\n" << input_args << "\n";
    if (full_parse) {
        for (auto [key, value] : input_args) {
            auto arg = m_args[key];
            if (arg.m_type == "req") {
                m_args[key].m_value = value;
            }
        }
    */
        /*
        std::vector<std::string> required_args {};
        for (auto [key, value] : input_args) {
            // std::cout << "  full_parse: key: " << key << "  value: " << value << "\n";
            if (key[0] == '_' && !trim(value).empty()) {
                required_args.push_back(value);
            }
        }
        check_required(required_args, command_name);

        for (unsigned int i = 0; i < required_args.size(); ++i) {
            m_args[m_req_names[i]].m_value = required_args[i];
        }
        */
        //check_flags(input_args, command_name);
//    }

//    std::cout << "FINAL:\n"
//              << m_args;

}

std::string Argv::get(const std::string& name, bool missing_is_error)
{
    if (m_args.count(name) > 0) {
        return m_args.at(name).m_value;
    } else if (missing_is_error) {
        throw Argument_error("Command-line argument \"" + name + "\" is not defined");
    } else {
        return "";
    }
}

bool Argv::as_bool(const std::string& name)
{
    (void)K::log(2, name);
    return get(name) == "true";
}

int Argv::as_int(const std::string& name)
{
    (void)K::log(2, name);
    auto value = get(name);
    if (!std::regex_match(value, std::regex(R"([-+]?\d+)"))) {
        throw Argument_error("Argument \"" + value + "\" is not an integer");
    }
    return std::stoi(value);
}

int Argv::as_integer_range(const std::string& name, int low, int high)
{
    (void)K::log(2, name);
    auto value = get(name);
    std::stringstream ss {};
    ss << "Argument \"" << value << "\" is not an integer in the range of "
       << low << " to " << high;
    if (!std::regex_match(value, std::regex(R"([-+]?\d+)"))) {
        throw Argument_error(ss.str());
    }
    int result = std::stoi(value);
    if (result < low || result > high) {
        throw Argument_error(ss.str());
    }
    return result;
}

int Argv::as_verbosity(const std::string& name)
{
    (void)K::log(2, name);
    auto value = get(name);
    if (!std::regex_match(value, std::regex(regex_symbols["'verbosity'"]))) {
        throw Argument_error("Argument \"" + value + "\" is not a verbosity level",
                             Locator::none());
    }
    return std::stoi(value);
}

std::string Argv::as_string(const std::string& name)
{
    (void)K::log(2, name);
    auto result = get(name);
    return result;
}

strings_t Argv::as_vector(const std::string& name)
{
    (void)K::log(2, name);
    if (m_vectors.count(name) > 0) {
        return m_vectors.at(name);
    }
    // No stored boundaries (e.g. an opt, whose value is a single argv
    // word): the value is one element, spaces and all -- never re-split.
    std::string value = get(name);
    if (value.empty()) {
        return {};
    }
    return { value };
}

std::pair<std::string, strings_t> Argv::as_input(const std::string& name, bool allow_empty)
{
    (void)K::log(2, name);
    const std::string& input_arg = get(name);
    std::regex filename_rgx(R"(([./\w]+\.kt?))");
    strings_t input_filenames = find_all(input_arg, filename_rgx, 1);
    const std::string& input_text = trim(std::regex_replace(input_arg, filename_rgx, ""));
    if (input_text.empty() && input_filenames.empty()) {
        if (allow_empty) {
            return {{},{}};
        } else {
            throw Argument_error("No input text or filenames specified");
        }
    }
    (void)K::log(2, "Input text", input_text, 1);
    for (const auto& f : input_filenames) {
        (void)K::log(2, "Input filename", f);
    }
    return { input_text, input_filenames };
}

void Argv::describe(std::ostream& os)
{
    std::size_t width = std::accumulate(
        m_names.begin(), m_names.end(), 0,
        [&] (size_t w, const std::string& name) {
            return std::max(w, m_args[name].symbol().size()); });

    for (const std::string& name : m_names) {
        std::string value = m_args[name].m_value;
        if (value.empty()) {
            value = "<none>";
        }
        std::stringstream ss {};
        ss << "  "<< std::setfill(' ') << std::setw(width)
           << m_args[name].symbol() << " : " << value;
        // The line was built and then dropped, so this printed nothing at
        // all -- which is why "kdesc -v 1" showed no arguments and argv_test,
        // whose whole job is to display what Argv parsed, was silent.  The
        // callers already decide whether to call it (the commands gate it on
        // verbose_level > 0), so it prints unconditionally here.
        os << ss.str() << "\n";
    }
}
