/*
 *
 *  Singe 2
 *  Copyright (C) 2006-2020 Scott Duensing <scott@kangaroopunch.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */


#ifdef _WIN32
#include <windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>

#define ourMkdir(p,m) mkdir(p)

static const int CONSOLE_LINES = 1000;
#else
#define ourMkdir mkdir
#endif


#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <ctype.h>

#include "util.h"


extern bool _confNoConsole;


FILE *utilTraceFile = NULL;


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

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

	return string;
}


__attribute__((__format__(__printf__, 1, 0)))
char *utilCreateStringVArgs(char *format, va_list args) {
	va_list  argsCopy;
	int32_t  size     = 0;
	char    *buffer   = NULL;

	va_copy(argsCopy, args);
	size = vsnprintf(NULL, 0, format, argsCopy) + 1;
	va_end(argsCopy);
	buffer = calloc(1, (size_t)size);
	if (buffer) {
		vsnprintf(buffer, (size_t)size, format, args);
	}

	return buffer;
}


__attribute__((__format__(__printf__, 1, 0)))
__attribute__((noreturn))
void utilDie(char *fmt, ...) {
	va_list args;
	if (!_confNoConsole) {
		va_start(args, fmt);
		vfprintf(stderr, fmt, args);
		va_end(args);
		printf("\n");
		fflush(stderr);
#ifdef _WIN32
		getchar();
#endif
	}
	exit(1);
}


bool utilFileExists(char *filename) {
	FILE *file;
	if ((file = fopen(filename, "r+"))) {
		fclose(file);
		return true;
	}
	return false;
}


void utilFixPathSeparators(char **path, bool slash) {

	int32_t  i    =  0;
	char    *work = *path;
	char    *temp =  NULL;

	// Flip path separators to whatever our OS wants
	while (work[i] != 0) {
		if (work[i] == '\\' || work[i] == '/') {
			work[i] = utilGetPathSeparator();
		}
		i++;
	}

	if (slash) {
		// Does this string end with a path separator?
		if (work[strlen(work) - 1] != utilGetPathSeparator()) {
			// No - append one.
			temp = strdup(work);
			free(work);
			work = malloc(sizeof(char) * (strlen(temp) + 1));
			strcpy(work, temp);
			work[strlen(temp)] = utilGetPathSeparator();
			work[strlen(temp) + 1] = 0;
			free(temp);
		}
	}

	*path = work;
}


char *utilGetFileExtension(char *filename) {
	char    *start = filename + strlen(filename);
	int32_t  x;

	// Scan through name and find the last '.'
	for (x=0; x<(int32_t)strlen(filename); x++) {
		if (filename[x] == '.') {
			start = &filename[x + 1];
		}
		// Reset if we find a path separator
		if (filename[x] == utilGetPathSeparator()) {
			start = filename + strlen(filename);
		}
	}

	return start;
}


char *utilGetLastPathComponent(char *pathname) {
	char    *start = pathname;
	int32_t  x;

	// Scan through name and find the last path separator
	for (x=0; x<(int32_t)strlen(pathname); x++) {
		if (pathname[x] == utilGetPathSeparator()) {
			start = &pathname[x + 1];
		}
	}

	return start;
}


char utilGetPathSeparator(void) {
#ifdef _WIN32
	return '\\';
#else
	return '/';
#endif
}


bool utilMkDirP(const char *dir, const mode_t mode) {
	char         tmp[UTIL_PATH_MAX];
	char        *p   = NULL;
	struct stat  sb;
	size_t       len;

	// Make copy of dir.
	len = strnlen(dir, UTIL_PATH_MAX);
	if (len == 0 || len == UTIL_PATH_MAX) {
		return -1;
	}
	memcpy(tmp, dir, len);
	tmp[len] = '\0';

	// Remove trailing slash.
	if (tmp[len - 1] == utilGetPathSeparator()) {
		tmp[len - 1] = '\0';
	}

	// Does it already exist?
	if (stat(tmp, &sb) == 0) {
		if (S_ISDIR (sb.st_mode)) {
			return true;
		}
	}

	// Recursive mkdir.
	for (p = tmp + 1; *p; p++) {
		if (*p == utilGetPathSeparator()) {
			*p = 0;
			if (stat(tmp, &sb) != 0) {
				// Does not exist - create it.
				if (ourMkdir(tmp, mode) < 0) {
					return false;
				}
			} else {
				if (!S_ISDIR(sb.st_mode)) {
					// Not a directory
					return false;
				}
			}
			*p = utilGetPathSeparator();
		}
	}
	// Check path
	if (stat(tmp, &sb) != 0) {
		// Does not exist - create it.
		if (ourMkdir(tmp, mode) < 0) {
			return false;
		}
	} else {
		if (!S_ISDIR(sb.st_mode)) {
			// Not a directory
			return false;
		}
	}

	return true;
}


bool utilPathExists(char *pathname) {
	DIR *dir = opendir(pathname);
	if (dir) {
		closedir(dir);
		return true;
	}
	return false;
}


char *utilReadFile(char *filename, size_t *bytes) {
	char   *data   =  NULL;
	FILE   *in     =  fopen(filename, "rb");
	size_t  read   =  0;

	(void)read;

	*bytes = 0;

	if (in) {
		fseek(in, 0, SEEK_END);
		*bytes = ftell(in);
		fseek(in, 0, SEEK_SET);
		data = malloc(sizeof(char) * (*bytes));
		read = fread(data, sizeof(char), *bytes, in);
		fclose(in);
	}

	return data;
}


char *utilReadLine(char *haystack, size_t length, char **offset) {
	size_t  bytes  =  0;
	char   *temp   =  *offset;
	char   *tail   =  temp;
	char   *result =  NULL;

	// They didn't know where to start
	if (temp == NULL) {
		temp = haystack;
		tail = temp;
	}

	// Is there still data to read?
	while ((size_t)(tail - haystack) < length) {
		// Is this the end of a line?
		if ((tail[0] == 10) || (tail[0] == 13)) {
			// Yep!
			bytes = tail - temp + 1;
			result = malloc(sizeof(char) * bytes);
			memcpy(result, temp, bytes - 1);
			result[bytes - 1] = 0;
			// Read past any additional CR/LFs
			while ((tail[0] == 10) || (tail[0] == 13)) {
				tail++;
			}
			// Return where we left off
			temp = tail;
			*offset = temp;
			return result;
		}
		// Next character
		tail++;
	}

	// Was there data at the end of the block with no CR/LF?
	if (tail > temp) {
		// Yep.  Treat it as a line.
		bytes = tail - temp + 1;
		result = malloc(sizeof(char) * bytes);
		memcpy(result, temp, bytes - 1);
		result[bytes - 1] = 0;
		temp = tail;
		*offset = temp;
	}

	// Didn't find anything
	return result;
}


void utilRedirectConsole(void) {
#ifdef _WIN32
	// http://dslweb.nwnexus.com/~ast/dload/guicon.htm
	int                         hConHandle;  // Not changing this int.
	intptr_t                    lStdHandle;
	CONSOLE_SCREEN_BUFFER_INFO  coninfo;
	FILE                       *fp;
	static bool consoleOpen = false;

	if (!consoleOpen) {
		consoleOpen = true;

		// allocate a console for this app
		AllocConsole();

		// set the screen buffer to be big enough to let us scroll text
		GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
		coninfo.dwSize.Y = CONSOLE_LINES;
		SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);

		// redirect unbuffered STDOUT to the console
		lStdHandle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
		hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
		fp = _fdopen(hConHandle, "w");
		*stdout = *fp;
		setvbuf(stdout, NULL, _IONBF, 0);

		// redirect unbuffered STDIN to the console
		lStdHandle = (intptr_t)GetStdHandle(STD_INPUT_HANDLE);
		hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
		fp = _fdopen(hConHandle, "r");
		*stdin = *fp;
		setvbuf(stdin, NULL, _IONBF, 0);

		// redirect unbuffered STDERR to the console
		lStdHandle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
		hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
		fp = _fdopen(hConHandle, "w");
		*stderr = *fp;
		setvbuf(stderr, NULL, _IONBF, 0);
	}
#endif
}


__attribute__((__format__(__printf__, 1, 0)))
void utilSay(char *fmt, ...) {
	va_list args;
	if (!_confNoConsole) {
		va_start(args, fmt);
		vfprintf(stdout, fmt, args);
		va_end(args);
		printf("\n");
		fflush(stdout);
	}
}



int utilStricmp(char *a, char *b) {
	for (;; a++, b++) {
		int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
		if (d != 0 || !*a) {
			return d;
		}
	}
}


// Windows does not have strndup() so we implement our own.
char *utilStrndup( const char *s1, size_t n) {
	char *copy = (char *)malloc(n + 1);
	memcpy(copy, s1, n);
	copy[n] = 0;
	return copy;
}


void utilTrace(char *fmt, ...) {
	va_list args;
	if (utilTraceFile) {
		va_start(args, fmt);
		utilTraceVArgs(fmt, args);
		va_end(args);
	}
}


void utilTraceEnd(void) {
	if (utilTraceFile) fclose(utilTraceFile);
}


void utilTraceStart(char *filename) {
	utilTraceFile = fopen(filename, "wt");
	if (!utilTraceFile) utilDie("Unable to create trace file:  %s", filename);
}


__attribute__((__format__(__printf__, 1, 0)))
void utilTraceVArgs(char *fmt, va_list args) {
	if (utilTraceFile) {
		vfprintf(utilTraceFile, fmt, args);
		fprintf(utilTraceFile, "\n");
		fflush(utilTraceFile);
	}
}
