/*
 *  JoeyDev
 *  Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
 *
 *  This software is provided 'as-is', without any express or implied
 *  warranty.  In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *
 *  1. The origin of this software must not be misrepresented; you must not
 *     claim that you wrote the original software. If you use this software
 *     in a product, an acknowledgment in the product documentation would be
 *     appreciated but is not required.
 *  2. Altered source versions must be plainly marked as such, and must not be
 *     misrepresented as being the original software.
 *  3. This notice may not be removed or altered from any source distribution.
*/


#include "common.h"
#include "compiler.h"
#include "libtcc.h"
#include "sigsegv.h"
#include "project.h"
#include "messages.h"
#include "utils.h"
#include "array.h"


#if HAVE_SIGSEGV_RECOVERY
#include <setjmp.h>
#include <signal.h>

#if defined _WIN32 && !defined __CYGWIN__
//  Windows doesn't have sigset_t.
typedef int sigset_t;
#define sigemptyset(set)
#define sigprocmask(how,set,oldset)
#endif

volatile int runPass   = 0;
jmp_buf      runRecipe;
sigset_t     runSigSet;

static void sigHandlerContinuation(void *arg1, void *arg2, void *arg3);
int         sigHandler(void *faultAddress, int serious);

#endif


char **___recipeTargets = NULL;


static void compilerErrorHandler(void *opaque, const char *msg);
void        recipeAddTarget(char *target);
void        recipeMessage(char *format, ...);


static void compilerErrorHandler(void *opaque, const char *msg) {
	char *isWarning = strstr(msg, " warning: ");

	(void)opaque;

	message(isWarning == NULL ? MSG_ERROR : MSG_WARN, "%s", msg);
}


// All the paths passed in here are expected to be complete and absolute.
int compilerRunRecipe(char *recipe, char *input, char *outputPath, void *context) {
	TCCState *s;
	char     *oldLocation;
	char      c;
	int       x;
	int       result = -255;
	int       (*entry)(char *, char *);

	___recipeTargets = NULL;

	s = tcc_new();
	if (!s) {
		// Something bad happened.
		return -1;
	}

	// __resourcePath comes in with a trailing slash.  Temporarily remove it.
	x = (int)strlen(__resourcePath) - 1;
	c = __resourcePath[x];
	__resourcePath[x] = 0;
	tcc_set_lib_path(s, __resourcePath);
	tcc_add_sysinclude_path(s, __resourcePath);
	__resourcePath[x] = c;

	tcc_set_options(s, "-Wall -Wno-write-strings");
	tcc_set_error_func(s, stdout, compilerErrorHandler);
	tcc_set_output_type(s, TCC_OUTPUT_MEMORY);

	if (tcc_add_file(s, recipe) < 0) {
		// Errors in code.
		tcc_delete(s);
		return -4;
	}

	tcc_add_symbol(s, "fputc", fputc);
	tcc_add_symbol(s, "qsort", qsort);

	tcc_add_symbol(s, "___recipeTargets", ___recipeTargets);
	tcc_add_symbol(s, "recipeAddTarget", recipeAddTarget);
	tcc_add_symbol(s, "recipeMessage", recipeMessage);
	tcc_add_symbol(s, "utilCreateString", utilCreateString);

	if (tcc_relocate(s, TCC_RELOCATE_AUTO) < 0) {
		// Something bad happened.
		tcc_delete(s);
		return -2;
	}

	entry = tcc_get_symbol(s, "recipe");
	if (!entry) {
		// Something bad happened.
		tcc_delete(s);
		return -3;
	}

	getcwd(__utilFilenameBuffer, FILENAME_MAX);
	oldLocation = strdup(__utilFilenameBuffer);
	chdir(outputPath);

#if HAVE_SIGSEGV_RECOVERY
	sigset_t emptySet;

	runPass = 0;

	if (sigsegv_install_handler(&sigHandler) < 0)
		return -5;

	sigemptyset(&emptySet);
	sigprocmask(SIG_BLOCK, &emptySet, &runSigSet);

	switch (setjmp(runRecipe)) {
		case 0:
			result = entry(input, outputPath);
		case 1:
			sigprocmask(SIG_SETMASK, &runSigSet, NULL);
			sigsegv_install_handler(NULL);
			if (runPass != 0) {
				message(MSG_SEVERE, "%s caused a segmentation fault!", recipe);
			}
			break;
	}

#else

	result = entry(input, outputPath);

#endif

	chdir(oldLocation);
	DEL(oldLocation);

	while (arrlen(___recipeTargets) > 0) {
		if (projectAddToTree(context, ___recipeTargets[0])) {
			utilSetDirty((WindowDataT *)context, TRUE);
		}
		DEL(___recipeTargets[0]);
		arrdel(___recipeTargets, 0);
	}
	ARRFREE(___recipeTargets);

	tcc_delete(s);

	return result;
}


#if HAVE_SIGSEGV_RECOVERY
static void sigHandlerContinuation(void *arg1, void *arg2, void *arg3) {
	(void)arg1;
	(void)arg2;
	(void)arg3;
	longjmp(runRecipe, runPass);
}


int sigHandler(void *faultAddress, int serious) {
	(void)faultAddress;
	(void)serious;

	runPass++;
	return sigsegv_leave_handler(sigHandlerContinuation, NULL, NULL, NULL);
}
#endif

void recipeAddTarget(char *target) {
	arrput(___recipeTargets, strdup(target));
}


void recipeMessage(char *format, ...) {
	va_list    args;
	char      *string;

	va_start(args, format);
	string = utilCreateStringVArgs(format, args);
	va_end(args);

	message(MSG_INFO, "%s", string);

	DEL(string);
}
