/**
 * \file Debug.h
 * \date 23.11.2002
 * \author Matus Horvath
 *
 * Useful debugging functions implementation
 */

#include "Common/Support/Debug.h"

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#ifdef DEBUG

// When compiling with Visual C++, we need both DEBUG and _DEBUG
#if defined(_MSC_VER) && !defined(_DEBUG)
#error To build debug version with Visual C++, define both DEBUG and _DEBUG
#endif // defined(_MSC_VER) && !defined(_DEBUG)


#include <time.h>

#ifdef _MSC_VER

#include <crtdbg.h>
#include <string.h>
#include <share.h>
#include <direct.h>

#else // _MSC_VER

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#endif // _MSC_VER


#define DEBUG_DIR "Debug"

static char fileName[128] = "";
static char nameBase[128] = "";
static char playerId[64] = "";
static int tag = 1;
static FILE* file = NULL;

/// Helper function which outputs data to debug file
static void debug(const char* pre, const char* format, const char* post, va_list args)
{
	if(file == NULL)
	{
		struct tm* t;
		int prevSec = -1;

		// Create directory "Debug" if it does not exist
#ifdef _MSC_VER
		(void) _mkdir(DEBUG_DIR);
#else // _MSC_VER
		(void) mkdir(DEBUG_DIR, 0755);
#endif // _MSC_VER

		// Try to open my own file 22 times 
		// (= two teams of 11 players may try to open at the same time)
		// update: plus coach, plus... let's make it 50 :)
		for(int i = 1; i <= 50; i++)
		{
			// Get current time
			time_t tt;
			time(&tt);
			t = localtime(&tt);

			// Is it different then time of the last attempt?
			if(t->tm_sec == prevSec)
			{
				tag++;
			}
			else
			{
				tag = 1;
				prevSec = t->tm_sec;
			}

			// Generate file name
			sprintf(fileName, DEBUG_DIR "/%02d%02d%02d_%02d%02d%02d_%s%02d.txt", 
				t->tm_year % 100, t->tm_mon + 1, t->tm_mday, 
				t->tm_hour, t->tm_min, t->tm_sec, playerId, tag);

#ifdef _MSC_VER
			// Open and lock file
			file = _fsopen(fileName, "a+", _SH_DENYWR);
			if(file != NULL) break;			// opened and locked
#else // _MSC_VER
			// Open file
			file = fopen(fileName, "a+");
			if(file == NULL) continue;		// could not even open it, try next file name

			// Try to lock it
			struct flock l;
			l.l_type = F_WRLCK; l.l_whence = 0; l.l_start = 0; l.l_len = 0;
			if(fcntl(fileno(file), F_SETLK, &l) == 0) break;	// opened and locked

			// Not locked
			fclose(file); file = NULL;
#endif // _MSC_VER
		}

		// Success?
		if(file == NULL)
		{
			fprintf(stderr, "Error opening debug file\n");
			abort();
		}

		// Save file name base
		sprintf(nameBase, DEBUG_DIR "/%02d%02d%02d_%02d%02d%02d_", 
			t->tm_year % 100, t->tm_mon + 1, t->tm_mday, 
			t->tm_hour, t->tm_min, t->tm_sec);

		fprintf(stderr, "Debug file is %s\n", fileName);
	}

	fprintf(file, "%s", pre);
	vfprintf(file, format, args);
	fprintf(file, "%s", post);
	
	fflush(file);
}

void DBG_SET_ID(const char* id)
{
	sprintf(playerId, "%s_", id);

	if(file != NULL)
	{
		// We already have a debug file, rename it
		char newName[128];
		sprintf(newName, "%s%s_%02d.txt", nameBase, id, tag);
		FILE* tmp = fopen(newName, "w+");
		if(tmp == NULL)
		{
			fprintf(stderr, "File open error while renaming debug file to %s\n", newName);
			return;
		}

		if(fseek(file, 0, SEEK_SET) != 0)
		{
			fprintf(stderr, "Seek error while renaming debug file to %s\n", newName);
			fclose(tmp); return;
		}
		
		size_t bytes;
		char buf[4096];
		while(!feof(file))
		{
			bytes = fread((void*) buf, 1, 4096, file);
			if(bytes != 4096 && ferror(file))
			{
				fprintf(stderr, "Read error while renaming debug file to %s\n", newName);
				fclose(tmp); return;
			}

			bytes = fwrite((void*) buf, 1, bytes, tmp);
			if(bytes != 4096 && ferror(tmp))
			{
				fprintf(stderr, "Write error while renaming debug file to %s\n", newName);
				fclose(tmp); return;
			}
		}

		fclose(file);
		file = tmp;

#ifdef _MSC_VER
		_unlink(fileName);
#else // _MSC_VER
		unlink(fileName);
#endif // _MSC_VER

		strcpy(fileName, newName);
		fprintf(stderr, "Debug file changed to %s\n", fileName);
	}
}

void DBG(const char* format, ...)
{
	va_list args;
	va_start(args, format);
	debug("", format, "", args);
	printf("[DBG] ");
	vprintf(format, args);
	va_end(args);
}

void DBGN(const char* format, ...)
{
	va_list args;
	va_start(args, format);
	debug("", format, "\n", args);
	printf("[DBG] ");
	vprintf(format, args);
	printf("\n");
	va_end(args);
}

#ifdef _MSC_VER
static int msvcReportHook(int type, char* message, int* returnValue)
{
	int len = strlen(message);
	bool endLine = (len > 0 && message[len - 1] == '\n');

	switch(type)
	{
	case _CRT_ASSERT: DBG("[CRT A] %s%s", message, endLine ? "" : "\n"); break;
	case _CRT_WARN: DBG("[CRT W] %s%s", message, endLine ? "" : "\n"); break;
	case _CRT_ERROR: DBG("[CRT E] %s%s", message, endLine ? "" : "\n"); break;
	}

	return 1;
}
#endif // _MSC_VER

void DBG_INIT()
{
#ifdef _MSC_VER
	// Everything should go to debug file
	_CrtSetReportHook(msvcReportHook);

	// Heap debugging
	int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
	_CrtSetDbgFlag(flag | _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif // _MSC_VER
}

void DBG_DONE()
{
//#ifdef _MSC_VER
	// Heap debugging
	//_CrtCheckMemory();
	//_CrtDumpMemoryLeaks();
	//_CrtMemDumpAllObjectsSince(NULL);
//#endif // _MSC_VER
}

#endif // DEBUG

void ERR(const char* format, ...)
{
	va_list args;
	va_start(args, format);
#ifdef DEBUG
	debug("[ERROR] ", format, "", args);
#endif // DEBUG
	fprintf(stderr, "[ERROR] ");
	vfprintf(stderr, format, args);
	va_end(args);
}

void ERRN(const char* format, ...)
{
	va_list args;
	va_start(args, format);
#ifdef DEBUG
	debug("[ERROR] ", format, "\n", args);
#endif // DEBUG
	fprintf(stderr, "[ERROR] ");
	vfprintf(stderr, format, args);
	fprintf(stderr, "\n");
	va_end(args);
}

void WARN(const char* format, ...)
{
	va_list args;
	va_start(args, format);
#ifdef DEBUG
	debug("[WARNING] ", format, "", args);
#endif // DEBUG
	fprintf(stderr, "[WARNING] ");
	vfprintf(stderr, format, args);
	va_end(args);
}

void WARNN(const char* format, ...)
{
	va_list args;
	va_start(args, format);
#ifdef DEBUG
	debug("[WARNING] ", format, "\n", args);
#endif // DEBUG
	fprintf(stderr, "[WARNING] ");
	vfprintf(stderr, format, args);
	fprintf(stderr, "\n");
	va_end(args);
}

void TXT(const char* format, ...)
{
	va_list args;
	va_start(args, format);
#ifdef DEBUG
	debug("", format, "", args);
#endif // DEBUG
	vfprintf(stderr, format, args);
	va_end(args);
}

void TXTN(const char* format, ...)
{
	va_list args;
	va_start(args, format);
#ifdef DEBUG
	debug("", format, "\n", args);
#endif // DEBUG
	vfprintf(stderr, format, args);
	fprintf(stderr, "\n");
	va_end(args);
}
