/******************************************************************************/
/* SlunkCrypt, by LoRd_MuldeR <MuldeR2@GMX.de>                                */
/* This work has been released under the CC0 1.0 Universal license!           */
/******************************************************************************/

/* Platform configuration */
#ifdef _WIN32
#  define WIN32_LEAN_AND_MEAN 1
#else
#  define _GNU_SOURCE 1
#endif

/* Internal */
#include "utils.h"

/* Library */
#include <slunkcrypt.h>

/* CRT */
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <signal.h>
#include <stdarg.h>

/* Platform support */
#ifdef _WIN32
#  include <Windows.h>
#  include <io.h>
#  include <fcntl.h>
#  define STAT_T struct _stati64
#  define STAT(X,Y) _wstati64((X),(Y))
#  define FSTAT(X,Y) _fstati64((X),(Y))
#  define FILENO(X) _fileno((X))
#  define S_IFMT  _S_IFMT
#  define S_IFDIR _S_IFDIR
#  define S_IFIFO _S_IFIFO
#  ifndef _O_U8TEXT
#  define _O_U8TEXT 0x40000
#  endif
#else
#  include <unistd.h>
#  define STAT_T struct stat
#  define STAT(X,Y) stat((X),(Y))
#  define FSTAT(X,Y) fstat((X),(Y))
#  define FILENO(X) fileno((X))
#endif

// ==========================================================================
// Terminal initialization
// ==========================================================================

#ifdef _WIN32
#ifdef _DLL
#define _acmdln GetCommandLineA()
#define _wcmdln GetCommandLineW()
#else
extern char    *const _acmdln;
extern wchar_t *const _wcmdln;
#endif
static void clear_cmdline_args(char *const acmdln, wchar_t *const wcmdln)
{
	if (acmdln && acmdln[0U])
	{
		const size_t len = strlen(acmdln);
		slunkcrypt_bzero(acmdln, len * sizeof(char));
		if (len > 5U) strcpy(acmdln, "slunk");
	}
	if (wcmdln && wcmdln[0U])
	{
		const size_t len = wcslen(wcmdln);
		slunkcrypt_bzero(wcmdln, len * sizeof(wchar_t));
		if (len > 5U) wcscpy(wcmdln, L"slunk");
	}
}
static void set_translation_mode(FILE* const stream, const int utf8_mode)
{
	_setmode(_fileno(stream), utf8_mode ? _O_U8TEXT : _O_BINARY);
}
#endif

void init_terminal(void)
{
#ifdef _WIN32
	SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
	set_translation_mode(stderr, 1);
	set_translation_mode(stdin,  0);
	clear_cmdline_args(_acmdln, _wcmdln);
#endif
}

// ==========================================================================
// Signal handling
// ==========================================================================

void setup_signal_handler(const int signo, signal_handler_t* const handler)
{
#ifdef _WIN32
	signal(signo, handler);
#else
	struct sigaction act;
	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(signo, &act, NULL);
#endif
}

// ==========================================================================
// String functions
// ==========================================================================

char* CHR_to_utf8(const CHR *const input)
{
#ifdef _WIN32
	char *buffer = NULL;
	DWORD buffer_size = 0U, result = 0U;

	buffer_size = input ? WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, NULL, NULL) : 0U;
	if (buffer_size < 1U)
	{
		return NULL;
	}

	buffer = (char*)malloc(sizeof(char) * buffer_size);
	if (!buffer)
	{
		return NULL;
	}

	result = WideCharToMultiByte(CP_UTF8, 0, input, -1, (LPSTR)buffer, buffer_size, NULL, NULL);
	if ((result > 0U) && (result <= buffer_size))
	{
		return buffer;
	}

	free(buffer);
	return NULL;
#else
	return strdup(input); /*simple string copy*/
#endif
}

// ==========================================================================
// Time functions
// ==========================================================================

#ifndef _WIN32
#define TSPEC_TO_MSEC(X) ((((uint64_t)(X).tv_sec) * 1000U) + (((uint64_t)(X).tv_nsec) / 1000000U))
#endif

uint64_t clock_freq(void)
{
#ifdef _WIN32
	LARGE_INTEGER freq;
	if (QueryPerformanceFrequency(&freq))
	{
		return freq.QuadPart;
	}
#endif
	return 1000U;
}

uint64_t clock_read(void)
{
#ifdef _WIN32
	LARGE_INTEGER count;
	if (QueryPerformanceCounter(&count))
	{
		return count.QuadPart;
	}
#else
	struct timespec clock_value;
#ifdef CLOCK_MONOTONIC
	if (clock_gettime(CLOCK_MONOTONIC, &clock_value) == 0)
	{
		return TSPEC_TO_MSEC(clock_value);
	}
#endif
	if (clock_gettime(CLOCK_REALTIME, &clock_value) == 0)
	{
		return TSPEC_TO_MSEC(clock_value);
	}
#endif
	return 0U;
}

// ==========================================================================
// Byte-order support
// ==========================================================================

void store_ui64(uint8_t *const dst, const uint64_t value)
{
	dst[0U] = (uint8_t)(value >>  0);
	dst[1U] = (uint8_t)(value >>  8);
	dst[2U] = (uint8_t)(value >> 16);
	dst[3U] = (uint8_t)(value >> 24);
	dst[4U] = (uint8_t)(value >> 32);
	dst[5U] = (uint8_t)(value >> 40);
	dst[6U] = (uint8_t)(value >> 48);
	dst[7U] = (uint8_t)(value >> 56);
}

uint64_t load_ui64(const uint8_t *const src)
{
	return
		((uint64_t)(src[0U]) <<  0) |
		((uint64_t)(src[1U]) <<  8) |
		((uint64_t)(src[2U]) << 16) |
		((uint64_t)(src[3U]) << 24) |
		((uint64_t)(src[4U]) << 32) |
		((uint64_t)(src[5U]) << 40) |
		((uint64_t)(src[6U]) << 48) |
		((uint64_t)(src[7U]) << 56);
}

size_t fwrite_ui64(const uint64_t value, FILE *const stream)
{
	uint8_t buffer[sizeof(uint64_t)];
	store_ui64(buffer, value);
	const size_t result = fwrite(buffer, sizeof(uint8_t), sizeof(uint64_t), stream);
	return result / sizeof(uint64_t);
}

size_t fread_ui64(uint64_t *const value, FILE *const stream)
{
	uint8_t buffer[sizeof(uint64_t)];
	const size_t result = fread(buffer, sizeof(uint8_t), sizeof(uint64_t), stream);
	*value = (result >= sizeof(uint64_t)) ? load_ui64(buffer) : ((uint64_t)(-1LL));
	return result / sizeof(uint64_t);
}

// ==========================================================================
// File functions
// ==========================================================================

typedef struct
{
	uint64_t dev;
	uint64_t ino;
}
file_uid_t;

static int get_file_unique_id(const CHR *const path, file_uid_t *const file_id)
{
	int retval = -1;
#ifdef _WIN32
	const HANDLE handle = CreateFileW(path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0U, NULL);
	if (handle != INVALID_HANDLE_VALUE)
	{
		BY_HANDLE_FILE_INFORMATION file_info;
		if ((retval = (!GetFileInformationByHandle(handle, &file_info))) == 0)
		{
			file_id->dev = file_info.dwVolumeSerialNumber;
			file_id->ino = (((uint64_t)file_info.nFileIndexHigh) << 32) | file_info.nFileIndexLow;
		}
		CloseHandle(handle);
	}
#else
	STAT_T file_info;
	if ((retval = STAT(path, &file_info)) == 0)
	{
		file_id->dev = file_info.st_dev;
		file_id->ino = file_info.st_ino;
	}
#endif
	return retval;
}

int same_file(const CHR *const path0, const CHR *const path1)
{
	file_uid_t file_id0, file_id1;
	if ((get_file_unique_id(path0, &file_id0) == 0) && (get_file_unique_id(path1, &file_id1) == 0))
	{
		return (file_id0.dev == file_id1.dev) && (file_id0.ino == file_id1.ino);
	}
	return -1;
}

uint64_t get_size(FILE *const file)
{
	STAT_T file_info;
	if (FSTAT(FILENO(file), &file_info) == 0)
	{
		const unsigned long ftype = file_info.st_mode & S_IFMT;
		if ((ftype != S_IFDIR) && (ftype != S_IFIFO))
		{
			const int64_t size = file_info.st_size;
			return (size >= 0) ? ((uint64_t)size) : 0U;
		}
		return 0U;
	}
	return UINT64_MAX;
}

const CHR* get_file_name(const CHR* path)
{
	const CHR* ptr;
	while ((ptr = STRRCHR(path, T('/'))))
	{
		path = ptr + 1U;
	}
	while ((ptr = STRRCHR(path, T('\\'))))
	{
		path = ptr + 1U;
	}
	return path;
}

// ==========================================================================
// Math functions
// ==========================================================================

uint64_t round_down(const uint64_t value, const uint64_t base)
{
	const uint64_t modulus = value % base;
	return modulus ? (value - modulus) : value;
}
