// SPDX-License-Identifier: MIT

#include "link/warning.hpp"

#include <inttypes.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include "diagnostics.hpp"
#include "style.hpp"

#include "link/fstack.hpp"
#include "link/lexer.hpp"

// clang-format off: nested initializers
Diagnostics<WarningLevel, WarningID> warnings = {
    .metaWarnings = {
        {"all",          LEVEL_ALL       },
        {"everything",   LEVEL_EVERYTHING},
    },
    .warningFlags = {
        {"assert",       LEVEL_DEFAULT   },
        {"div",          LEVEL_ALL       },
        {"obsolete",     LEVEL_DEFAULT   },
        {"shift",        LEVEL_ALL       },
        {"shift-amount", LEVEL_ALL       },
        // Parametric warnings
        {"truncation",   LEVEL_DEFAULT   },
        {"truncation",   LEVEL_EVERYTHING},
    },
    .paramWarnings = {
        {WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 1},
    },
    .state = DiagnosticsState<WarningID>(),
    .nbErrors = 0,
};
// clang-format on

static void printDiag(
    FileStackNode const *src,
    uint32_t lineNo,
    char const *fmt,
    va_list args,
    char const *type,
    StyleColor color,
    char const *flagfmt,
    char const *flag
) {
	style_Set(stderr, color, true);
	fprintf(stderr, "%s: ", type);
	style_Reset(stderr);
	vfprintf(stderr, fmt, args);
	if (flagfmt) {
		style_Set(stderr, color, true);
		putc(' ', stderr);
		fprintf(stderr, flagfmt, flag);
	}
	putc('\n', stderr);

	if (src) {
		src->printBacktrace(lineNo);
	}
}

[[noreturn]]
static void abortLinking(char const *verb) {
	style_Set(stderr, STYLE_RED, true);
	fprintf(
	    stderr,
	    "Linking %s with %" PRIu64 " error%s\n",
	    verb ? verb : "aborted",
	    warnings.nbErrors,
	    warnings.nbErrors == 1 ? "" : "s"
	);
	style_Reset(stderr);
	exit(1);
}

void warning(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	printDiag(src, lineNo, fmt, args, "warning", STYLE_YELLOW, nullptr, nullptr);
	va_end(args);
}

void warning(char const *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	printDiag(nullptr, 0, fmt, args, "warning", STYLE_YELLOW, nullptr, nullptr);
	va_end(args);
}

void error(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	printDiag(src, lineNo, fmt, args, "error", STYLE_RED, nullptr, nullptr);
	va_end(args);

	warnings.incrementErrors();
}

void error(char const *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	printDiag(nullptr, 0, fmt, args, "error", STYLE_RED, nullptr, nullptr);
	va_end(args);

	warnings.incrementErrors();
}

void scriptError(char const *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	printDiag(nullptr, 0, fmt, args, "error", STYLE_RED, nullptr, nullptr);
	va_end(args);

	lexer_TraceCurrent();

	warnings.incrementErrors();
}

[[noreturn]]
void fatal(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	printDiag(src, lineNo, fmt, args, "FATAL", STYLE_RED, nullptr, nullptr);
	va_end(args);

	warnings.incrementErrors();
	abortLinking(nullptr);
}

[[noreturn]]
void fatal(char const *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	printDiag(nullptr, 0, fmt, args, "FATAL", STYLE_RED, nullptr, nullptr);
	va_end(args);

	warnings.incrementErrors();
	abortLinking(nullptr);
}

[[noreturn]]
void fatalTwo(
    FileStackNode const &src1,
    uint32_t lineNo1,
    FileStackNode const &src2,
    uint32_t lineNo2,
    char const *fmt,
    ...
) {
	va_list args;
	style_Set(stderr, STYLE_RED, true);
	fputs("FATAL: ", stderr);
	style_Reset(stderr);
	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	va_end(args);
	putc('\n', stderr);

	src1.printBacktrace(lineNo1);
	fputs("    and also:\n", stderr);
	src2.printBacktrace(lineNo2);

	warnings.incrementErrors();
	abortLinking(nullptr);
}

void requireZeroErrors() {
	if (warnings.nbErrors != 0) {
		abortLinking("failed");
	}
}

void warning(FileStackNode const *src, uint32_t lineNo, WarningID id, char const *fmt, ...) {
	char const *flag = warnings.warningFlags[id].name;
	va_list args;

	va_start(args, fmt);

	switch (warnings.getWarningBehavior(id)) {
	case WarningBehavior::DISABLED:
		break;

	case WarningBehavior::ENABLED:
		printDiag(src, lineNo, fmt, args, "warning", STYLE_YELLOW, "[-W%s]", flag);
		break;

	case WarningBehavior::ERROR:
		printDiag(src, lineNo, fmt, args, "error", STYLE_RED, "[-Werror=%s]", flag);

		warnings.incrementErrors();
		break;
	}

	va_end(args);
}
