// SPDX-License-Identifier: MIT

#include "diagnostics.hpp"

#include <optional>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string>
#include <utility>

#include "helpers.hpp"
#include "style.hpp"
#include "util.hpp" // parseNumber

void warnx(char const *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	vwarnx(fmt, args);
	va_end(args);
}

void vwarnx(char const *fmt, va_list args) {
	style_Set(stderr, STYLE_YELLOW, true);
	fputs("warning: ", stderr);
	style_Reset(stderr);
	vfprintf(stderr, fmt, args);
	putc('\n', stderr);
}

void errorx(char const *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	verrorx(fmt, args);
	va_end(args);
}

void verrorx(char const *fmt, va_list args) {
	style_Set(stderr, STYLE_RED, true);
	fputs("error: ", stderr);
	style_Reset(stderr);
	vfprintf(stderr, fmt, args);
	putc('\n', stderr);
}

void fatalx(char const *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	vfatalx(fmt, args);
	va_end(args);
}

void vfatalx(char const *fmt, va_list args) {
	style_Set(stderr, STYLE_RED, true);
	fputs("FATAL: ", stderr);
	style_Reset(stderr);
	vfprintf(stderr, fmt, args);
	putc('\n', stderr);
}

void WarningState::update(WarningState other) {
	if (other.state != WARNING_DEFAULT) {
		state = other.state;
	}
	if (other.error != WARNING_DEFAULT) {
		error = other.error;
	}
}

std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::string &flag) {
	// Check for prefixes that affect what the flag does
	WarningState state;
	if (flag.starts_with("error=")) {
		// `-Werror=<flag>` enables the flag as an error
		state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED};
		flag.erase(0, literal_strlen("error="));
	} else if (flag.starts_with("no-error=")) {
		// `-Wno-error=<flag>` prevents the flag from being an error,
		// without affecting whether it is enabled
		state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED};
		flag.erase(0, literal_strlen("no-error="));
	} else if (flag.starts_with("no-")) {
		// `-Wno-<flag>` disables the flag
		state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
		flag.erase(0, literal_strlen("no-"));
	} else {
		// `-W<flag>` enables the flag
		state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
	}

	// Check if there is an "equals" sign followed by a decimal number
	// Ignore an equals sign at the very end of the string
	auto equals = flag.find('=');
	// `-Wno-<flag>` and `-Wno-error=<flag>` negation will apply to all levels of a parametric
	// warning. An `=` parameter cannot be specified for `-Wno-*` negated warnings.
	if (state.state != WARNING_ENABLED || equals == flag.npos || equals == flag.size() - 1) {
		return {state, std::nullopt};
	}

	// If the rest of the string is a decimal number, it's the parameter value
	char const *ptr = flag.c_str() + equals + 1;
	uint64_t param = parseNumber(ptr, BASE_10).value_or(0);

	// If we reached the end of the string, truncate it at the '='
	if (*ptr == '\0') {
		flag.resize(equals);
		// `-W<flag>=0` is equivalent to `-Wno-<flag>`
		if (param == 0) {
			state.state = WARNING_DISABLED;
		}
	}

	return {state, param > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(param)};
}
