#include <stdlib.h>
#include <cstdio>
#include <algorithm>
#include <cctype> 
#include <iterator>
#include <set>
#include <sstream>
#include <utility>
#include <regex>

#include "util.h"
#include "show.h"

std::string trim_left(const std::string& s)
{
    std::string result = s;
    result.erase(result.begin(), std::find_if(result.begin(), result.end(), [](unsigned char ch) {
        return !std::isspace(ch);
    }));
    return result;
}

std::string trim_right(const std::string& s)
{
    std::string result = s;
    result.erase(std::find_if(result.rbegin(), result.rend(), [](unsigned char ch) {
        return !std::isspace(ch);
    }).base(), result.end());
    return result;
}

std::string trim(std::string s)
{
    return trim_left(trim_right(std::move(s)));
}

std::string trim_char_left(std::string s, char remove)
{
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [&](char c) { return c != remove; }));
    return s;
}

std::string trim_char_right(std::string s, char remove)
{
    s.erase(std::find_if(s.rbegin(), s.rend(), [&](char c) { return c != remove; }).base(), s.end());
    return s;
}

std::string trim_char(std::string s, char remove)
{
    return trim_char_left(trim_char_right(std::move(s), remove), remove);
}


std::string escape_regex(const std::string& input)
{
    std::string result;
    result.reserve(input.length() * 2); // Reserve space for potential escapes
    
    for (char c : input) {
        // Escape special regex characters
        if (std::string("\\^$.|?*+()[{}]").find(c) != std::string::npos) {
            result += '\\';
        }
        result += c;
    }
    return result;
}

std::string string_replace(const std::string& source, const std::string& old_str, const std::string& new_str)
{
    return std::regex_replace(source, std::regex(escape_regex(old_str)), new_str);
    /*
    std::string result { source };
    auto pos = result.find(old_str);
    std::string old_str_e = old_str; // escape_regex(old_str);
    while (pos != std::string::npos) {
        // result = result.replace(pos, old_str.size(), new_str);
        result = result.replace(pos, old_str_e.size(), new_str);
        // pos = result.find(old_str);
        pos = result.find(old_str_e);
        // std::cout << "    " << result << "\n";
    }
    return result;
    */
}

bool contains(const std::string& str, const std::string& substr)
{
    return str.find(substr) != std::string::npos;
}

bool contains(const std::vector<std::string>& strings, const std::string& element)
{
    return std::find(strings.begin(), strings.end(), element) != strings.end();
}

std::string regex_escape(const std::string& s)
{
    /*
    regex special { R"([\$.|?*+(){})" }; // ^ is reserved
    return regex_replace(s, special, "\\[&$]");
    */
    std::set chars { '\\', '|', '(', ')', '{', '}', '[', ']', '$', '^' };
    std::string result {};
    for (char c : s) {
        if (chars.find(c) != chars.end())
            result += "\\";
        result += c;
    }
    return result;
}

strings_t regex_split(const std::string& s, const std::regex& re, bool trim_parts)
{
    strings_t result = {};
    if (s.empty()) {
        return result;
    }
    auto it = std::sregex_token_iterator(s.begin(), s.end(), re, -1);
    while (it != std::sregex_token_iterator()) {
        std::string part { *it };
        if (trim_parts)
            part = trim(part);
        result.push_back(part);
        it++;
    }
    return result;
}


strings_t word_split(const std::string& s)
{
    return regex_split(s, std::regex("\\s+"));
}

std::string collapse_whitespace(const std::string& s)
{
    std::string result {};
    bool in_space = true;                 // leading whitespace is dropped
    for (char c : s) {
        if (std::isspace(static_cast<unsigned char>(c)) != 0) {
            in_space = true;
        } else {
            if (in_space && !result.empty()) {
                result += ' ';
            }
            in_space = false;
            result += c;
        }
    }
    return result;
}

bool contains_fold(const std::string& haystack, const std::string& needle)
{
    auto fold = [](const std::string& s) {
        std::string result {};
        for (char c : s) {
            result += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
        }
        return result;
    };
    return fold(haystack).find(fold(needle)) != std::string::npos;
}

bool is_in(const std::string& s, const strings_t& v)
{
    return find(v.begin(), v.end(), s) != v.end();
}

bool is_not_in(const std::string& s, const strings_t& v)
{
    return find(v.begin(), v.end(), s) == v.end();
}

strings_t find_all(const std::string& str, const std::regex& pattern, int match_group)
{
    std::sregex_iterator end {};
    strings_t result;
    for (std::sregex_iterator p {str.begin(), str.end(), pattern}; p!= end; ++p)
        result.push_back((*p)[match_group]);
    return result;
}

strings_t find_all(const std::string& str, const std::string& pattern, int match_group)
{
    return find_all(str, std::regex(pattern), match_group);
}

strings_t split_into_paragraphs(const std::string& s)
{
    std::string t {trim(s)};
    std::string marker { "_PAR_" };
    t = trim(std::regex_replace(t, std::regex(R"(\n *(\n *)+)"), marker)) + marker;
    //return find_all(t, regex(R"(((\s|.)*?)" + marker + ")"), 1);
   return find_all(t, std::regex(R"(((\s|.)*?)_PAR_)"), 1);

}

std::string add_margin(const std::string& s, unsigned int margin_size)
{
    auto margin = std::string(margin_size, ' ');
    return trim_right(
        margin + std::regex_replace(s, std::regex(R"(\n)"), '\n' + margin));
}

std::string justify_string(const std::string& s, unsigned int width=80, bool french_spacing=false)
{
    strings_t words = find_all(s, R"([^\s]+)");
    std::stringstream ss {};
    std::stringstream line {};
    for (std::string w : words) {
        if (line.str().size() + w.size() + 1 > width) {
            ss << line.str() << '\n';
            line.str("");
        } 
        if (not french_spacing and w[w.size()-1] == '.')
            w += " ";
        line << w << " ";
    }
    if (!line.str().empty())
        ss << line.str();
    std::string result = trim(ss.str());
    result = std::regex_replace(result, std::regex("~"), " ");
    return result;
}

// The error-message formatting contract (2026-08-21): a message carries no
// decisions about line breaks EXCEPT by indentation.
//   * a line beginning with whitespace is VERBATIM -- an example, a pattern,
//     a signature, a list entry -- emitted untouched: no folding, no
//     wrapping, no "~" substitution (a pattern may contain a literal ~);
//   * blank lines separate blocks (runs collapse to one);
//   * everything else is prose: consecutive lines fold into one paragraph
//     and are wrapped to the width.
// So prose stays machine-wrapped however the source hand-wraps it, and the
// one legitimate exception is marked in the one place it cannot be missed.
// This replaced a per-call do_justify flag on the Error constructors, whose
// decision lived apart from the text it governed.  Line-based by hand: no
// std::regex over the whole message (the ambiguous-alternation hazard).
std::string justify(
    const std::string& input_text, unsigned int text_width, unsigned int margin_width)
{
    text_width -= margin_width;
    strings_t lines {};
    {
        const std::string text = trim(input_text);
        std::string::size_type from = 0;
        while (from <= text.size()) {
            auto nl = text.find('\n', from);
            if (nl == std::string::npos) {
                lines.push_back(text.substr(from));
                break;
            }
            lines.push_back(text.substr(from, nl - from));
            from = nl + 1;
        }
    }
    std::string result {};
    strings_t prose {};
    bool pending_blank = false;
    auto emit = [&](const std::string& block) {
        if (!result.empty()) {
            result += pending_blank ? "\n\n" : "\n";
        }
        result += block;
        pending_blank = false;
    };
    auto flush_prose = [&]() {
        if (!prose.empty()) {
            emit(justify_string(join(prose, " "), text_width));
            prose.clear();
        }
    };
    for (const std::string& line : lines) {
        if (trim(line).empty()) {                       // block separator
            flush_prose();
            pending_blank = !result.empty();
        } else if (line[0] == ' ' || line[0] == '\t') { // verbatim line
            flush_prose();
            emit(trim_right(line));
        } else {                                        // prose
            prose.push_back(trim(line));
        }
    }
    flush_prose();
    result += "\n";
    if (margin_width > 0) {
        result = add_margin(result, margin_width);
    }
    return result;
}

std::string join(const strings_t& ss, const std::string& separator)
{
    if (ss.empty()) {
        return std::string();
    } else if (ss.size() == 1) {
        return ss[0];
    } else {
        std::stringstream strm {};
        std::copy(ss.begin(), ss.end() - 1, 
                 std::ostream_iterator<std::string>(strm, separator.c_str()));
        strm << ss.back();
        return strm.str();
    }
}

std::string join(int argc, char* argv[], const std::string& separator)
{
    std::string result {};
    for (int i = 0; i < argc; ++i) {
        // Append directly (see argv_to_string): avoids a temporary and the same
        // GCC 12 -Wrestrict false positive.
        result += argv[i];
        result += separator;
    }
    return result;
}

std::string argv_to_string(int argc, char* argv[])
{
    if (argc == 0) {
        return "";
    }
    std::string result = argv[0];
    for (int i = 1; i < argc; ++i) {
        // Append the pieces directly rather than building a `" " + string(...)`
        // temporary: identical result, avoids an allocation, and sidesteps a
        // GCC 12 -Wrestrict false positive on the temporary's memcpy.
        result += ' ';
        result += argv[i];
    }
    return result;
}


std::string plural(const std::string& word, int count)
{
    std::string result {word};
    if (count != 1) {
        if (*(word.end()-1) == 'y')
            result = word.substr(0, word.size()-2) + "ies";
        else
            result = word + "s";
    }
    return result;
}    

std::string plural(const std::string& word, const strings_t& things)
{
    std::string result { word };
    if (things.size() != 1) {
        if (*(word.end()-1) == 'y')
            result = word.substr(0, word.size()-2) + "ies";
        else
            result = word + "s";
    }
    return result;
}    

std::string to_be(int count, bool present)
{
    std::string result {};
    if (count > 1) {
        if (present) {
            result = "are";
        } else {
            result = "were";
        }
    } else {
        if (present) {
            result = "is";
        } else {
            result = "was";
        }
    }
    return result;
}

int max_length(const strings_t& ss)
{
    size_t result = 0;
    for_each(ss.begin(), ss.end(),
             [&result](const std::string& s) { result = std::max(result, s.size()); });
    return result;
}


/*
std::vector<std::string> map_key_lengths(std::map<std::string, auto> map)
{
    int result = 0;
    for (auto const& item: map) {
        result = std::max(result, item.first.size());
    }
}


int 
std::map<int, int> m;
std::vector<int> key, value;
for(std::map<int,int>::iterator it = m.begin(); it != m.end(); ++it) {
  key.push_back(it->first);
  value.push_back(it->second);
  std::cout << "Key: " << it->first << std::endl;
  std::cout << "Value: " << it->second << std::endl;
}
*/


std::vector<std::pair<std::string, std::string>> environment_variables(bool allow_empty_definitions)
{
    // std::cout << "read_environment:\n";
    std::vector<std::pair<std::string, std::string>> result {};
    extern char **environ;
    for (int i = 0; environ[i]; i++) {
        auto parts = regex_split(environ[i], std::regex("="), true);
        if (!allow_empty_definitions && parts.size() < 2) {
            throw Internal_error(
                "Incorrect environment variable format:\n  " + std::string(environ[i]),
                Locator());
        }
        std::string name = parts[0];
        parts.erase(parts.begin());
        std::string value = join(parts, "=");
        // std::cout << name << sp_arrow << value << "\n";
        result.push_back({name, value});
    }
    return result;
}

std::string replace_environment_variables(const std::string& str)
{
    if (str.find('{') == std::string::npos || str.find('}') == std::string::npos) {
        return str;
    }
    if (str.find('{') == std::string::npos || str.find('\n') != std::string::npos) {
        return str;
    }

    if (str.size() < 3) {
        return str;
    }
    std::regex variable_re(R"((.*?)\{([A-Z_]+)\})");
    std::sregex_iterator end {};
    std::string result {};
    //sregex_iterator q {};
    size_t endpos = 0;
    for (std::sregex_iterator p {str.begin(), str.end(), variable_re}; p!= end; ++p) {
        std::smatch m = *p;
        std::string prefix = m[1];
        std::string var = m[2];
        endpos = m.position() + m.length();
        std::string envvar = get_env_var(var);
        result += prefix + envvar;
    }
    if (endpos < str.size() - 1) {
        result += str.substr(endpos);
    }
    return result;
}

std::string abbrev(const std::string& s, unsigned int max_length, bool remove_newlines)
{
    std::string result {s};
    if (s.size() > max_length) {
        if (remove_newlines) {
            result = trim(result);
            result = std::regex_replace(result, std::regex("\n"), broken_bar);
        }
        int suffix_size = 8;
        // Plain, deliberately.  This string is spliced into another and shown
        // later, on a stream this function cannot know -- so a colour baked in
        // here could reach a pipe or a file.  It also fixes an arithmetic
        // error that the colours caused: ellipsis.size() counted the 14
        // characters of the two escape sequences as visible width, so the
        // abbreviation was cut 14 characters shorter than max_length asked.
        std::string ellipsis { "[...]" };
        int prefix_end = max_length - suffix_size - static_cast<int>(ellipsis.size());
        result = result.replace(result.begin() + prefix_end,
                                result.end() - suffix_size,
                                ellipsis);
    }
    return result;
}

void remove_element(std::vector<std::string>& ss, const std::string& removed)
{
    ss.erase(std::remove_if(ss.begin(), ss.end(), 
                            [&removed](const std::string& s) { return s == removed; }),
             ss.end());
}

void remove_duplicates(strings_t& ss)
{
    // https://en.cppreference.com/w/cpp/algorithm/unique
    std::sort(ss.begin(), ss.end());
    auto last = std::unique(ss.begin(), ss.end());
    ss.erase(last, ss.end());
}

std::string display_string(const std::string& s, unsigned int width, bool replace_newlines)
{
    std::string suffix { "..." };
    std::string result { s };
    if (replace_newlines)
        result = std::regex_replace(result, std::regex("\n"), "/");
    auto rlen = result.length();
    auto slen = suffix.length();
    if ((rlen > slen) and (rlen  - slen) > width) {
        result = result.replace(result.begin()+width, result.end(), suffix); //substr(0, width) + suffix;
    }
    //result = '"' + result + '"';
    return result;
}

std::pair<std::string,std::string> extract_parameter_type(const std::string& parameter_name)
{
    std::regex name_pat { R"((\w+)\.(\w+))" };
    std::smatch match {};
    std::string type_name = "string";
    std::string name = parameter_name;
    if (std::regex_match(parameter_name, match, name_pat)) {
        name = match[1];
        type_name = match[2];
    }
    return { name, type_name };
}

std::tuple<std::string, std::string, bool> regex_split_prefix(const std::regex& pattern, const std::string& text)
{
    std::smatch match;
    if (std::regex_search(text, match, pattern) && match.position() == 0) {
        // Match found at the beginning of the string
        std::string matched = match.str();
        std::string remainder = text.substr(matched.length());
        return { matched, remainder, true };
    } else {
        // No match at the beginning
        return { "", text, false };
    }
}

std::vector<std::string> dlist_split(const std::string& s)
{
    // If a [^\w] character surrounded by spaces exists in s, it is the delimiter.
    // If not, the first space-delimited word is the delimiter.
    if (s.find(" ") == std::string::npos) { // Only one element.
        //std::vector<std::string> result {s};
        //return result;
        return {s};
    } else {
        std::smatch match{};
        std::string elements_str{s};
        std::string delimiter {};
        if (std::regex_search(s, match, std::regex(R"(\s+([^\w])\s+)"))) {
            delimiter = match[1];
        } else {
            std::string::const_iterator iter =
                std::find_if(s.cbegin(), s.cend(), [](char c) { return c == ' '; });
            delimiter = std::string(s.cbegin(), iter);
            elements_str = std::string(iter, s.cend());
        }
        std::vector<std::string> elements {
            regex_split(elements_str, std::regex(delimiter), true) };
        return elements;
    }
}

std::string get_env_var(const std::string& var) {
    std::lock_guard<std::mutex> lock(env_mutex);
    const char* val = getenv(var.c_str());
    return val ? std::string(val) : "";
}

std::string freplace(const std::string& src, const std::regex& pattern,
                     const std::function<std::string(std::smatch)>& func)
{
    std::string result {};
    std::smatch match;

    bool found = std::regex_search(src.begin(), src.end(), match, pattern);
    auto pos = src.begin();
    // int n = 0;
    while (found) {
        std::string part(pos, pos + match.position());
        result += part;
        result += func(match);
        pos += match.position(0) + match.length(0);
        found = std::regex_search(pos, src.end(), match, pattern);
        // n++;
    }
    std::string part(pos, pos + match.position());
    result += part;
    // std::cout << "Found " << n << " matches\n";
    return result;
}

std::string exec(const char* cmd)
{
    std::array<char, 128> buffer;                                                                                                       
    std::string result;
    FILE* pipe = popen(cmd, "r");
    if (!pipe) throw std::runtime_error("popen() failed");
    while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
        result += buffer.data();
    }
    pclose(pipe);
    return result;
}
