#define _GNU_SOURCE

#include "fatal.h"
#include "logger.h"
#include "jacommon.h"

/* Default program name if not set externally */
// extern char *progname = "Job Arranger Server";

/* Utility to get signal name */
const char *get_signal_name(int sig) {
	switch (sig) {
		case SIGALRM: return "SIGALRM";
		case SIGILL:  return "SIGILL";
		case SIGFPE:  return "SIGFPE";
		case SIGSEGV: return "SIGSEGV";
		case SIGBUS:  return "SIGBUS";
		case SIGQUIT: return "SIGQUIT";
		case SIGINT:  return "SIGINT";
		case SIGTERM: return "SIGTERM";
		case SIGPIPE: return "SIGPIPE";
		case SIGUSR1: return "SIGUSR1";
		default:      return "unknown";
	}
}

/* Utility to get register name by index (x86_64, Linux) */
const char *get_register_name(int reg) {

	switch (reg) {
#ifdef REG_R8
		case REG_R8:     return "r8";
#endif
#ifdef REG_R9
		case REG_R9:     return "r9";
#endif
#ifdef REG_R10
		case REG_R10:    return "r10";
#endif
#ifdef REG_R11
		case REG_R11:    return "r11";
#endif
#ifdef REG_R12
		case REG_R12:    return "r12";
#endif
#ifdef REG_R13
		case REG_R13:    return "r13";
#endif
#ifdef REG_R14
		case REG_R14:    return "r14";
#endif
#ifdef REG_R15
		case REG_R15:    return "r15";
#endif
#ifdef REG_RDI
		case REG_RDI:    return "rdi";
#endif
#ifdef REG_RSI
		case REG_RSI:    return "rsi";
#endif
#ifdef REG_RBP
		case REG_RBP:    return "rbp";
#endif
#ifdef REG_RBX
		case REG_RBX:    return "rbx";
#endif
#ifdef REG_RDX
		case REG_RDX:    return "rdx";
#endif
#ifdef REG_RAX
		case REG_RAX:    return "rax";
#endif
#ifdef REG_RCX
		case REG_RCX:    return "rcx";
#endif
#ifdef REG_RSP
		case REG_RSP:    return "rsp";
#endif
#ifdef REG_RIP
		case REG_RIP:    return "rip";
#endif
#ifdef REG_EFL
		case REG_EFL:    return "efl";
#endif
#ifdef REG_CSGSFS
		case REG_CSGSFS: return "csgsfs";
#endif
#ifdef REG_ERR
		case REG_ERR:    return "err";
#endif
#ifdef REG_TRAPNO
		case REG_TRAPNO: return "trapno";
#endif
#ifdef REG_OLDMASK
		case REG_OLDMASK:return "oldmask";
#endif
#ifdef REG_CR2
		case REG_CR2:    return "cr2";
#endif
		default:         return "unknown";
	}
}

/* Fully self-contained fatal info logger */
void print_fatal_info(int sig, siginfo_t *siginfo, void *context) {
	ucontext_t *uctx = (ucontext_t *)context;
	void *pc = NULL;

#if defined(REG_RIP)
#	define GET_REG(uctx, reg) (uctx)->uc_mcontext.gregs[reg]
	pc = (void *)GET_REG(uctx, REG_RIP);
#elif defined(REG_EIP)
#	define GET_REG(uctx, reg) (uctx)->uc_mcontext.gregs[reg]
	pc = (void *)GET_REG(uctx, REG_EIP);
#else
#	define GET_REG(uctx, reg) 0
#endif

	write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, "====== Fatal information test: ======");
	write_log("JAFATAL100002", error_message, ERROR_MSG_SIZE, get_signal_name(sig), sig);

#if defined(REG_RIP) || defined(REG_EIP)
	write_log("JAFATAL100003", error_message, ERROR_MSG_SIZE, pc);
	write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, "=== Registers: ===");
	for (int i = 0; i < NGREG; i++) {
		write_log("JAFATAL100004", error_message, ERROR_MSG_SIZE,
		        get_register_name(i),
		        (unsigned long)GET_REG(uctx, i),
		        (unsigned long)GET_REG(uctx, i),
		        (long)GET_REG(uctx, i));
	}
#else
	write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, "Program counter not available for this architecture");
#endif

	/* Backtrace */
	write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, "=== Backtrace: ===");
	void *bcktrc[60];
	int bcktrc_sz = backtrace(bcktrc, 60);
	char **bcktrc_syms = backtrace_symbols(bcktrc, bcktrc_sz);

	if (bcktrc_syms == NULL) {
		write_log("JAFATAL100005", error_message, ERROR_MSG_SIZE, strerror(errno));
		for (int i = 0; i < bcktrc_sz; i++) {
			write_log("JAFATAL100006", error_message, ERROR_MSG_SIZE, bcktrc_sz - i - 1, bcktrc[i]);
		}
	} else {
		for (int i = 0; i < bcktrc_sz; i++) {
			write_log("JAFATAL100007", error_message, ERROR_MSG_SIZE, bcktrc_sz - i - 1, bcktrc_syms[i]);
		}
		free(bcktrc_syms);
	}

	/* Memory map */
	write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, "=== Memory map: ===");
	FILE *fd = fopen("/proc/self/maps", "r");
	if (fd) {
		char line[1024];
		while (fgets(line, sizeof(line), fd)) {
			line[strcspn(line, "\n")] = '\0';  // Strip newline
			write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, line);
		}
		fclose(fd);
	} else {
		write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, "fopen /proc/self/maps");
	}

#if defined(REG_RIP) || defined(REG_EIP)
	write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, "================================");
	write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, "Please consider attaching a disassembly listing to your bug report.");
	write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, "This listing can be produced with, e.g., objdump -DSswx jobarg_server ");
#endif
	write_log("JAFATAL100001", error_message, ERROR_MSG_SIZE, "================================");
}

