/*
 * Functions used for logging.
 *
 * Copyright IBM Corp. 2022
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */
/* Must be included before any other header */
#include "config.h"

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

#include "types.h"
#include "common.h"
#include "log.h"

void pvattest_log_increase_log_lvl(int *log_lvl)
{
	if (*log_lvl >= PVATTEST_LOG_LVL_MAX)
		return;
	*log_lvl = *log_lvl << 1;
}

void pvattest_log_error(const char *format, ...)
{
	va_list argp;

	va_start(argp, format);
	g_logv(NULL, PVATTEST_LOG_LVL_ERROR, format, argp);
	va_end(argp);
}

void pvattest_log_warning(const char *format, ...)
{
	va_list argp;

	va_start(argp, format);
	g_logv(NULL, PVATTEST_LOG_LVL_WARNING, format, argp);
	va_end(argp);
}

void pvattest_log_info(const char *format, ...)
{
	va_list argp;

	va_start(argp, format);
	g_logv(NULL, PVATTEST_LOG_LVL_INFO, format, argp);
	va_end(argp);
}

void pvattest_log_debug(const char *format, ...)
{
	va_list argp;

	va_start(argp, format);
	g_logv(NULL, PVATTEST_LOG_LVL_DEBUG, format, argp);
	va_end(argp);
}

static void _log_print(FILE *stream, const char *prefix, const char *message, const char *postfix)
{
	g_autofree char *prefix_empty = NULL, *new_msg = NULL;
	size_t prefix_len = strlen(prefix);
	char **message_v;

	if (!prefix || prefix_len == 0) {
		printf("%s%s", message, postfix);
		return;
	}

	message_v = g_strsplit(message, "\n", 0);
	prefix_empty = g_malloc0(prefix_len + 2);

	snprintf(prefix_empty, prefix_len + 2, "\n%*c\b", (int)prefix_len, ' ');
	new_msg = g_strjoinv(prefix_empty, message_v);

	fprintf(stream, "%s%s%s", prefix, new_msg, postfix);

	g_strfreev(message_v);
}

static void _log_logger(GLogLevelFlags level, const char *message, int log_level,
			gboolean use_prefix, const char *postfix)
{
	const char *prefix = "";

	/* filter out messages depending on debugging level */
	if ((level & PVATTEST_LOG_LVL_DEBUG) && log_level < PVATTEST_LOG_LVL_DEBUG)
		return;

	if ((level & PVATTEST_LOG_LVL_INFO) && log_level < PVATTEST_LOG_LVL_INFO)
		return;

	if (use_prefix && level & (G_LOG_LEVEL_WARNING | PVATTEST_LOG_LVL_WARNING))
		prefix = _("WARNING: ");

	if (use_prefix && level & (G_LOG_LEVEL_ERROR | PVATTEST_LOG_LVL_ERROR))
		prefix = _("ERROR: ");

	if (use_prefix && level & (G_LOG_LEVEL_DEBUG | PVATTEST_LOG_LVL_DEBUG))
		prefix = _("DEBUG: ");

	if (level & (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR | PVATTEST_LOG_LVL_WARNING |
		     PVATTEST_LOG_LVL_ERROR))
		_log_print(stderr, prefix, message, postfix);
	else
		_log_print(stdout, prefix, message, postfix);
}

/**
 * prefixes type. and adds a "\n" ad the end.
 */
void pvattest_log_default_logger(const char *log_domain G_GNUC_UNUSED, GLogLevelFlags level,
				 const char *message, void *user_data)
{
	int log_level = *(int *)user_data;

	_log_logger(level, message, log_level, TRUE, "\n");
}

/*
 * writes message as it is if log level is high enough.
 */
void pvattest_log_plain_logger(const char *log_domain G_GNUC_UNUSED, GLogLevelFlags level,
			       const char *message, void *user_data)
{
	int log_level = *(int *)user_data;

	_log_logger(level, message, log_level, FALSE, "");
}

void pvattest_log_bytes(const void *data, size_t size, size_t width, const char *prefix,
			gboolean beautify, GLogLevelFlags log_lvl)
{
	const uint8_t *data_b = data;

	pv_wrapped_g_assert(data);

	if (beautify)
		g_log(PVATTEST_BYTES_LOG_DOMAIN, log_lvl, "%s0x0000  ", prefix);
	else
		g_log(PVATTEST_BYTES_LOG_DOMAIN, log_lvl, "%s", prefix);
	for (size_t i = 0; i < size; i++) {
		g_log(PVATTEST_BYTES_LOG_DOMAIN, log_lvl, "%02x", data_b[i]);
		if (i % 2 == 1 && beautify)
			g_log(PVATTEST_BYTES_LOG_DOMAIN, log_lvl, " ");
		if (i == size - 1)
			break;
		if (i % width == width - 1) {
			if (beautify)
				g_log(PVATTEST_BYTES_LOG_DOMAIN, log_lvl, "\n%s0x%04lx  ", prefix,
				      i + 1);
			else
				g_log(PVATTEST_BYTES_LOG_DOMAIN, log_lvl, "\n%s", prefix);
		}
	}
	g_log(PVATTEST_BYTES_LOG_DOMAIN, log_lvl, "\n");
}

int pvattest_hexdump(FILE *stream, GBytes *bytes, const size_t width, const char *prefix,
		     const gboolean beautify)
{
	const uint8_t *data;
	size_t size;

	pv_wrapped_g_assert(bytes);
	pv_wrapped_g_assert(stream);

	data = g_bytes_get_data(bytes, &size);
	pv_wrapped_g_assert(data);

	if (beautify) {
		if (fprintf(stream, "%s0x0000  ", prefix) < 0)
			return -1;
	} else {
		if (fprintf(stream, "%s", prefix) < 0)
			return -1;
	}
	for (size_t i = 0; i < size; i++) {
		if (fprintf(stream, "%02x", data[i]) < 0)
			return -1;
		if (i % 2 == 1 && beautify) {
			if (fprintf(stream, " ") < 0)
				return -1;
		}
		if (i == size - 1)
			break;
		if (width == 0)
			continue;
		if (i % width == width - 1) {
			if (beautify) {
				if (fprintf(stream, "\n%s0x%04lx  ", prefix, i + 1) < 0)
					return -1;
			} else {
				if (fprintf(stream, "\n%s", prefix) < 0)
					return -1;
			}
		}
	}
	return 0;
}

void pvattest_log_GError(const char *info, GError *error)
{
	pv_wrapped_g_assert(info);

	if (!error)
		return;

	pvattest_log_error("%s:\n%s", info, error->message);
}
