#include "System.h"

#ifndef _WIN32

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <dirent.h>
#include <execinfo.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

#include "Log.h"

/* Since munmap requires the size of the file, it's saved here so we still have
 * it when it comes time to unmap the file. TODO: This is a horrible hack
 * because I am too lazy to change my platform API. */
static size_t saved_mapsize = 0;

void *OS_MapFile(const char *path)
{
	struct stat st;
	int fd;
	void *result = NULL;
	
	if (saved_mapsize != 0) {
		Msg_Fatal("This code sucks and can only map one file at a time!");
		return NULL;
	}

	fd = open(path, 0);
	if (fd == -1)
	{
		Msg_Error("Unable to open '%s' for mapping: %s\n", path, strerror(errno));
		return NULL;
	}

	if (fstat(fd, &st) != 0)
	{
		Msg_Error("Unable to get info when mapping file '%s': %s\n", path, strerror(errno));
		goto done;
	}

	result = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
	if (result == MAP_FAILED)
	{
		Msg_Error("Unable to map file '%s': %s\n", path, strerror(errno));
	}

	saved_mapsize = st.st_size;
done:
	close(fd);
	return result;
}

void OS_UnmapFile(void *data)
{
	munmap(data, saved_mapsize);
	saved_mapsize = 0;
}

bool OS_DeleteFile(const char *filename)
{
	if (remove(filename) != 0) {
		fprintf(stderr, "Unable to delete '%s': %s\n", filename, strerror(errno));
		return false;
	}
	return true;
}

void OS_DeleteAllInDir(const char *path)
{
	struct dirent *entry;
	struct stat filestat;
	char *pathbuf;

	DIR *folder = opendir(path);
	if (folder == NULL)
	{
		Msg_Error("Unable to read folder '%s' for deleting: %s", path, strerror(errno));
		return;
	}

	pathbuf = malloc(strlen(path) + 256);

	while ((entry = readdir(folder)) != NULL)
	{
		if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
			continue;

		strcpy(pathbuf, path);
		strcat(pathbuf, "/");
		strcat(pathbuf, entry->d_name);

		stat(pathbuf, &filestat);

		if (S_ISREG(filestat.st_mode))
		{
			if (unlink(pathbuf) != 0)
				Msg_Error("Unable to delete '%s': %s", pathbuf, strerror(errno));
		}
	}

	free(pathbuf);
	closedir(folder);
}

void OS_MkdirIfNotExists(const char *path)
{
	struct stat sb;
	int rc;
	rc = stat(path, &sb);

	if (rc < 0 || !S_ISDIR(sb.st_mode))
	{
		if (mkdir(path, 0755) != 0)
			perror("mkdir");
	}
}

#define BACKTRACE_SIZE 64
void HandleException(int signo, siginfo_t *info, void *context)
{
	void *tracebuf[BACKTRACE_SIZE];
	char **traces;
	int ntraces, i;

	FILE *crashlog;

	ntraces = backtrace(tracebuf, BACKTRACE_SIZE);
	traces = backtrace_symbols(tracebuf, ntraces);
	for (i = 0; i < ntraces; i++)
	{
		fprintf(stderr, "%s\n", traces[i]);
	}

	crashlog = fopen("crash.log", "a");
	if (crashlog)
	{
		static char timestamp[64];
		int ntimestamp;
		time_t t = time(NULL);
		struct tm tm = *localtime(&t);
		ntimestamp = snprintf(timestamp, 63, "\n\n==== %d-%02d-%02d %02d:%02d:%02d LINUX ====\n",
			tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
		fwrite(timestamp, 1, ntimestamp, crashlog);
		for (i = 0; i < ntraces; i++)
		{
			fwrite(traces[i], 1, strlen(traces[i]), crashlog);
			fwrite("\n", 1, 1, crashlog);
		}
		fclose(crashlog);
	}

	free(traces);
	exit(EXIT_FAILURE);
}

void OS_SetupCrashHandler(void)
{
	struct sigaction act = { 0 };
	act.sa_flags = SA_SIGINFO;
	act.sa_sigaction = HandleException;
	sigaction(SIGSEGV, &act, NULL);
	sigaction(SIGFPE, &act, NULL);
	sigaction(SIGILL, &act, NULL);
	sigaction(SIGSYS, &act, NULL);
}

#endif // !_WIN32
