#include <stdarg.h>
#include <stdio.h>
#include <memory>
#include <string>

#include "config.h"
#include "src/msg/msg.h"
#include "src/msg/ver_to_vernum.h"
#include "src/options/opt.h"

extern const char* help; // autogenerated

namespace re2c {

void error(const char* fmt, ...) {
    fprintf(stderr, RE2C_PROG ": error: ");

    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);

    fprintf(stderr, "\n");
}

void Msg::print_location(const loc_t& loc) const {
    const char* f = filenames[loc.file].c_str();
    switch (locfmt) {
    case LOCFMT_GNU:
        fprintf(stderr, "%s:%u:%u: ", f, loc.line, loc.coln);
        break;
    case LOCFMT_MSVC:
        fprintf(stderr, "%s(%u,%u): ", f, loc.line, loc.coln);
        break;
    }
}

void Msg::error(const loc_t& loc, const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    verror(loc, fmt, args);
    va_end(args);
}

void Msg::verror(const loc_t& loc, const char* fmt, va_list args) {
    // If an error has already been reported, do not report another one. This prevents parser from
    // reporting a generic "syntax error" after lexer reports a precise one.
    if (error_seen) return;
    error_seen = true;

    print_location(loc);
    fprintf(stderr, "error: ");
    vfprintf(stderr, fmt, args);
    fprintf(stderr, "\n");
}

void error_arg(const char* option) {
    error("expected argument to option %s", option);
}

void Msg::warning_start(const loc_t& loc, bool error) {
    print_location(loc);
    const char* msg = error ? "error" : "warning";
    fprintf(stderr, "%s: ", msg);
}

void Msg::warning_end(const char* type, bool error) {
    if (type != nullptr) {
        const char* prefix = error ? "error-" : "";
        fprintf(stderr, " [-W%s%s]", prefix, type);
    }
    fprintf(stderr, "\n");
}

void Msg::warning(const char* type, const loc_t& loc, bool error, const char* fmt, ...) {
    warning_start(loc, error);

    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);

    warning_end(type, error);
}

Ret usage() {
    fprintf(stdout, "%s", help);
    return Ret::EXIT;
}

Ret vernum() {
    const std::string vn = ver_to_vernum(PACKAGE_VERSION);
    if (vn.empty()) {
        RET_FAIL(error("failed to convert version to vernum"));
    }
    printf("%s\n", vn.c_str());
    return Ret::EXIT;
}

Ret version() {
#ifdef RE2C_DEBUG
    printf("re2c %s (debug)\n", PACKAGE_VERSION);
#else
    printf("re2c %s\n", PACKAGE_VERSION);
#endif
    return Ret::EXIT;
}

std::string incond(const std::string& cond) {
    std::string s;
    if (!cond.empty()) {
        s += "in condition '";
        s += cond;
        s += "' ";
    }
    return s;
}

} // namespace re2c
