#include <set>
#include <regex>
#include <sstream>

#include "argtype.h"
#include "error.h"
#include "util.h"

Argtype::Argtype(const std::string& name, const std::string& desc, const std::string& symbolic_pattern,
                 const std::string& pattern,
                 const std::string& default_value, const std::string& alone_value,
                 const std::string& python_cast, const modify_string_f& python_format,
                 const Locator& loc)
    : m_name(name)
    , m_desc(desc)
    , m_symbolic_pattern(symbolic_pattern)
    , m_pattern(pattern)
    , m_default(default_value)
    , m_alone(alone_value)
    , m_python_cast(python_cast)
    , m_python_format(python_format)
    , m_regex(std::regex(pattern))
    , m_loc(loc)
{
}

static bool empty_value(const strings_t& v)
{
    return v.empty() || (v.size() == 1 && v[0].empty());
}

std::string pyformat_string(const strings_t& v)
{
    std::string p = v.empty() ? "" : v[0];
    p = string_replace(p, "\\", "\\\\");
    p = string_replace(p, "\"", "\\\"");
    p = string_replace(p, "\n", "\\n");
    return "\"" + p + "\"";
}

std::string pyformat_bool(const strings_t& v)
{
    if (empty_value(v)) {
        return "None";
    }
    std::string value = v[0];
    std::set<std::string> true_values { "1", "true", "True", "yes" };
    std::set<std::string> false_values { "0", "false", "False", "no" };
    if (true_values.contains(value)) {
        return "True";
    } else if (false_values.contains(value)) {
        return "False";
    } else {
        throw Argument_error("The argument\"" + value + "\" is not a Boolean value");
    }
}

std::string pyformat_number(const strings_t& value)
{
    if (empty_value(value)) {
        return "None";
    }
    return value[0];
}

std::string pylist(strings_t words)
{
    std::transform(words.begin(), words.end(), words.begin(),
                   [](const std::string& s) { return pyformat_string({s}); });
    // std::string(...) on the leading literal, not "[" + ... : a literal on the
    // LEFT of + selects operator+(const char*, string&&), which is insert(0,
    // ...) -- it shifts the whole string to make room.  The explicit temporary
    // selects the append overload instead.  Cheaper, and it silences a gcc-12
    // -Wrestrict FALSE POSITIVE (it reports a 2**63-byte overlap at a negative
    // offset, from value-range propagation failing to bound size() after
    // inlining; g++-15 and clang-18 do not).  Verified 2 -> 0 warnings on
    // gcc 12.2.0 with the container's flags.
    return std::string("[") + join(words, ", ") + "]";
}

std::string pyformat_list(const strings_t& value)
{
    strings_t words {};
    for (const std::string& v : value) {
        for (const std::string& word : word_split(v)) {
            if (!word.empty()) {
                words.push_back(word);
            }
        }
    }
    return pylist(words);
}

std::string pyformat_dlist(const strings_t& value)
{
    strings_t items {};
    for (const std::string& v : value) {
        if (v.empty()) {
            continue;
        }
        for (const std::string& item : dlist_split(v)) {
            items.push_back(item);
        }
    }
    return pylist(items);
}

std::string Argtype::python_value(const std::string& var_name, std::vector<std::string> value, size_t name_size)
{
    std::stringstream ss {};
    ss << "    " << std::left << std::setw(name_size) << var_name << " = ";
    if (m_python_format) {
        ss << m_python_format(value);
    } else if (m_python_cast.empty() || m_python_cast == "str") {
        // String-family types (and untyped variables): plain quoted string.
        ss << pyformat_string(value);
    } else if (!m_parameter.empty()) {
        // Parameterized type: bind the type parameter as N around the
        // cast, e.g. (lambda N: <cast>)(2)("A | B || C | D").
        ss << "(lambda N: " << m_python_cast << ")(" << m_parameter << ")("
           << pyformat_string(value) << ")";
    } else {
        // User-defined :python_cast expression, applied to the raw value.
        // The cast is applied to an empty value too, so e.g. a split lambda
        // yields [] for an unsupplied argument.
        ss << m_python_cast << "(" << pyformat_string(value) << ")";
    }
    return ss.str();
}

