////////////////////////////////////////////////////////////////////////////////
// BGBase.cpp
// Virtual class, memory tracer declaration and misc functions
// Copyright Bjoern Ganster
// Developed since in 2005
////////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <time.h>

#include <algorithm>
#include <iostream>
#include <map>
#include <vector>

#include "BGBase.h"
#include "BGCPString.h"
#include "FileCache.h"
#include "MemoryBlock.h"

#ifndef _WIN32
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <sys/stat.h>
#endif

#define FILE_NUMBER FILE_NO_BGBASE_CPP

////////////////////////////////////////////////////////////////////////////////

#ifdef _DEBUG
#define SimpleMemoryCounters
#endif

////////////////////////////////////////////////////////////////////////////////
// Constructor

VirtualClass::VirtualClass(const char* className, int classID)
: mClassName(className),
  mClassID(classID)
{
	#ifdef SimpleMemoryCounters
	SET_SCOPE;
	MemoryTracker* mt = getMemoryTracker();
	if (mt != nullptr) {
		mt->registerObject(className, classID, sizeof(*this));
	}
	RETURN;
	#endif
}

// Copy constructor
VirtualClass::VirtualClass (const VirtualClass& other)
: mClassName(other.mClassName),
  mClassID(other.mClassID)
{
	#ifdef SimpleMemoryCounters
	SET_SCOPE;
	MemoryTracker* mt = getMemoryTracker();
	if (mt != nullptr) {
		mt->registerObject(mClassName, mClassID, sizeof(*this));
	}
	RETURN;
	#endif
}

// Destructor
VirtualClass::~VirtualClass() 
{
	#ifdef SimpleMemoryCounters
	SET_SCOPE;
	MemoryTracker* mt = getMemoryTracker();
	if (mt != nullptr) {
		mt->deregisterObject(mClassID, sizeof(*this));
	}
	RETURN;
	#endif
}

// Send messages to other objects
bool VirtualClass::sendMessage (MessageListener* receiver, int messageType, VirtualClass* data)
{
	if (receiver != NULL) {
		SET_SCOPE;
		receiver->receiveMessage (this, messageType, data);
		RETURN true;
	} else {
		return false;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Memory profiling support

// Allocate memory with trace support

void* traceAlloc(size_t bytes, const char* className, int classID)
{
	SET_SCOPE;
	void* result = malloc (bytes);
	#ifdef SimpleMemoryCounters
	MemoryTracker* mt = getMemoryTracker();
	if (mt != nullptr) {
		mt->registerObject(className, classID, bytes);
	}
	#endif
	RETURN result;
}

// Test heap integrity by allocating and immediately freeing that memory
void testHeap()
{
	void* buf = traceAlloc(10, "Test Heap buffer", 0);
	traceFree(buf, 0, 10);
}

////////////////////////////////////////////////////////////////////////////////
// Free memory with trace support

#ifdef SimpleMemoryCounters
void traceFree(void* p, int classID, size_t usedMem)
{
	SET_SCOPE;
	MemoryTracker* mt = getMemoryTracker();
	if (mt != nullptr) {
		mt->deregisterObject(classID, usedMem);
	}
	free (p);
	RETURN;
}
#else
void traceFree(void* p, int, size_t)
{
	SET_SCOPE;
	free(p);
	RETURN;
}
#endif

// Dump memory counters for all classes
#ifdef SimpleMemoryCounters
void dumpMemoryUsage ()
{
	SET_SCOPE;
	MemoryTracker* mt = getMemoryTracker();
	if (mt != nullptr) {
		mt->dump();
	}
	RETURN;
}
#else
void dumpMemoryUsage()
{
	COUT << "Memory counters are inactive" << std::endl;
}
#endif

////////////////////////////////////////////////////////////////////////////////
// Translation support

MemoryBlock* langFile;
std::vector <BGCPString*> trStrings;

bool loadLangFile (const PathChar* FileName, WORD cpHint)
{
	SET_SCOPE;
	
	// Load strings
	langFile = new MemoryBlock();
	if (!langFile->loadFromFile(FileName)) {
		RETURN false;
	}

	// Obtain string pointers
	size_t i = 0;
	int strStart = 0;
	int strEnd = 0;

	while (i < langFile->getSize()) {
		char c = langFile->get<uint8_t>(i);
		if (c == 0x0a || c == 0x0d) {
			if (i == strEnd+1) {
				int len = strEnd-strStart+1;
				BGCPString* line = new BGCPString((const char*) langFile->getPtr<char>(strStart), len, cpHint);
				trStrings.emplace_back(line);
				/*TCHAR* line = new TCHAR [len+1];
				memcpy((void*) line, (void*) langFile->getPtr(strStart), 2*len);
				line[len] = (char) 0;
				trStrings.add(line);*/
				strStart = -1;
				strEnd = -1;
			} else
				++i;
		} else {
			if (strStart == -1)
				strStart = i;
			strEnd = i;
			++i;
		}
	}

	RETURN true;
}

// Get translated string
BGCPString tr (size_t strNum)
{
	if (strNum < trStrings.size()) {
		return *trStrings[strNum];
	} else {
		return BGCPString();
	}
}

////////////////////////////////////////////////////////////////////////////////
// Translate SDL keyboard event

MemoryBlock* translateKeyTable;

bool loadKeyTable (const PathChar* path)
{
	SET_SCOPE;
	translateKeyTable = new MemoryBlock;
	if (translateKeyTable->loadFromFile(path)) {
		printf ("Keyboard table " PathCharMod " loaded\n", path);
		RETURN true;
	} else {
		delete translateKeyTable;
		translateKeyTable = NULL;
		printf ("Failed to load keyboard table " PathCharMod "\n", path);
		RETURN false;
	}
}

BGKey translateKey (BGKey key, bool shiftHeld, bool AltGRHeld)
{
   if (translateKeyTable == NULL || key > 255)
      return key;

   int index = key;
   if (AltGRHeld)
      index += 512;
   else if (shiftHeld)
      index += 256;

   SET_SCOPE;
   BGKey result = (BGKey) translateKeyTable->get<uint8_t> (index);
   RETURN result;
}

////////////////////////////////////////////////////////////////////////////////
// Load data from file

#ifdef _WIN32

// Load data from file (Windows, ANSI file name)
// Free the returned buffer using traceFree(buf, UndefinedClassID, size);
void* loadFromFile (const char* FN, size_t& size)
{
	// Open file
	SET_SCOPE;
	Win32HandleCloser hc = CreateFileA(FN, GENERIC_READ, FILE_SHARE_READ, NULL, 
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (!hc.valid()) {
		DWORD winError = GetLastError();
		COUT << "Failed to open file " << FN << ", Windows error code is " 
		           << winError << std::endl;
		RETURN nullptr;
	}

	// Determine file size
	SET_OP;
	LARGE_INTEGER fileSize;
	GetFileSizeEx(hc.get(), &fileSize);

	// Allocate buffer, if possible
	size = (size_t) fileSize.QuadPart;
	if ((LONGLONG) size < fileSize.QuadPart || size == 0) {
		RETURN nullptr;
	}

	unsigned char* buf = (unsigned char*) traceAlloc(size, "LoadFromFileBuf", LoadFromFileBufID);

	if (buf == nullptr) {
		RETURN nullptr;
	}

	// Read file - only ReadWriteBufMaxSize bytes per call due to documented 
	// limitations in ReadFile
	INT64 readTotal = 0;
	DWORD read = 0;
	do {
		SET_OP;
		DWORD toRead = (DWORD)std::min<INT64> (size-readTotal, ReadWriteBufMaxSize);
		if (!ReadFile(hc.get(), buf+readTotal, toRead, &read, NULL)) {
			BGCPString error = BGCPString("Read failure on") + BGCPString(FN) +
				BGCPString(": ") + BGCPString::getOSError();
			log(error);
		}
		readTotal += read;
	} while (readTotal < fileSize.QuadPart && read > 0);

	if (readTotal == size) {
		RETURN buf;
	} else {
		size = 0;
		delete buf;
		RETURN nullptr;
	}
}

// Load data from file (Windows, Unicode file name)
// Free the returned buffer using traceFree(buf, UndefinedClassID, size);
void* loadFromFile (const wchar_t* FN, size_t& size)
{
	// Open file
	SET_SCOPE;
	Win32HandleCloser hc = CreateFileW(FN, GENERIC_READ, FILE_SHARE_READ, NULL, 
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (!hc.valid()) {
		BGCPString error = BGCPString("Failed to open ") + BGCPString(FN, CP_SYSTEM) +
			BGCPString(" for reading: ") + BGCPString::getOSError();
		log(error);
		RETURN nullptr;
	}

	// Determine file size
	SET_OP;
	LARGE_INTEGER fileSize;
	GetFileSizeEx(hc.get(), &fileSize);

	// Allocate buffer, if possible
	SET_OP;
	size = (size_t) fileSize.QuadPart;
	if ((LONGLONG) size < fileSize.QuadPart || size == 0) {
		RETURN nullptr;
	}

	unsigned char* buf = (unsigned char*) traceAlloc(size, "LoadFromFileBuf", LoadFromFileBufID);

	if (buf == nullptr) {
		RETURN nullptr;
	}

	// Read file - only ReadWriteBufMaxSize bytes per call due to documented 
	// limitations in ReadFile
	INT64 readTotal = 0;
	DWORD read = 0;
	do {
		SET_OP;
		DWORD toRead = (DWORD)std::min<INT64> (size-readTotal, ReadWriteBufMaxSize);
		if (!ReadFile(hc.get(), buf+readTotal, toRead, &read, NULL)) {
			//log("Read failure on ", FN, ": ", BGCPString::getOSError());
			BGCPString error = BGCPString("Read failure on ") + BGCPString(FN, CP_SYSTEM) +
				BGCPString(": ") + BGCPString::getOSError();
			error.convertToCP(CP_SYSTEM);
			log(error);
		}
		readTotal += read;
	} while (readTotal < fileSize.QuadPart && read > 0);

	SET_OP;
	if (readTotal == size) {
		RETURN buf;
	} else {
		size = 0;
		delete buf;
		RETURN nullptr;
	}
}

#else

// Load data from file (Linux/Apple)
// size returns the actual amount that was read
void* loadFromFile (const char* fileName, size_t& size)
{
	SET_SCOPE;
	FILE* file = fopen(fileName, "r");

	if (file == NULL) {
		size = 0;
		COUT << "Failed to open " << fileName << ", errno is " << errno << std::endl;
		RETURN nullptr;
	}

	// Determine file size
	SET_OP;
	fseek (file, 0, SEEK_END);
	size = ftell (file);
	fseek (file, 0, SEEK_SET);

	if (size == 0) {
		fclose(file);
		return nullptr;
	}

	// Read file
	unsigned char* buf = (unsigned char*) traceAlloc(size, "LoadFromFileBuf", LoadFromFileBufID);

	if (buf == nullptr) {
		fclose(file);
		RETURN nullptr;
	}

	if (buf != NULL) {
		// If we can't read everything, size should give the amount read
		SET_OP;
		size = fread(buf, 1, size, file);
	} else {
		COUT << "Failed to allocate buffer of " << size 
		     << " bytes to read " << fileName << std::endl;
		size = 0;
	}

	fclose(file);
	RETURN buf;
}

#endif

////////////////////////////////////////////////////////////////////////////////
// Save data to file

#ifdef _WIN32

// Save data to file (Windows, Unicode file name)
bool saveToFile (const char* data, size_t size, const wchar_t* FN)
{
	if (data == nullptr || size == 0 || FN == nullptr) {
		return false;
	}

	SET_SCOPE;
	Win32HandleCloser hc = CreateFileW(FN, GENERIC_WRITE, FILE_SHARE_READ, NULL,
		CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (!hc.valid()) {
		COUT << "Failed to open " << FN << " for writing, ";
		BGCPString::getOSError().printLn();
		RETURN false;
	}
	DWORD written = 0;
	size_t totalWritten = 0;
	size_t remaining = size;

	// WiteFile is documented to limit the max. amount of bytes written per call
	// so a loop is needed to read the data
	do {
		SET_OP;
		DWORD toWrite = (DWORD) std::min((size_t) ReadWriteBufMaxSize, remaining);
		WriteFile (hc.get(), data, toWrite, &written, NULL);
		data += written;
		remaining -= written;
		totalWritten += written;
   } while (written > 0 && remaining > 0);

   RETURN (totalWritten == size);
}

// Save data to file (Windows, ANSI file name)
bool saveToFile (const char* data, size_t size, const char* FN)
{
	if (data == nullptr || size == 0 || FN == nullptr) {
		return false;
	}

	SET_SCOPE;
	Win32HandleCloser hc = CreateFileA(FN, GENERIC_WRITE, FILE_SHARE_READ, NULL,
		CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (!hc.valid()) {
		COUT << "Failed to open file " << FN << " for writing, ";
		BGCPString::getOSError().printLn();
		RETURN false;
	}

	DWORD written;
	size_t totalWritten = 0;
	size_t remaining = size;

	// WiteFile is documented to limit the max. amount of bytes written per call
	// so a loop is needed to read the data
	do {
		SET_OP;
		DWORD toWrite = (DWORD) std::min((size_t) ReadWriteBufMaxSize, remaining);
		WriteFile (hc.get(), data, toWrite, &written, NULL);
		data += written;
		remaining -= written;
		totalWritten += written;
	} while (written > 0 && remaining > 0);

	RETURN (totalWritten == size);
}

#else

// Save data to file (Linux)
bool saveToFile (const char* data, size_t size, const char* FN)
{
	SET_SCOPE;
	FILE* f = fopen(FN, "wb");
	if (f == NULL) {
		RETURN false;
	}

	SET_OP;
	size_t written = fwrite (data, 1, size, f);
	fclose (f);
	RETURN written == size;
}

#endif

////////////////////////////////////////////////////////////////////////////////
// Initialize randomizer - without this, the program produces the same 
// sequence of pseudorandom numbers every time the program is run

void randomize()
{
   time_t now;
   time(&now);
   srand ((unsigned int) now);
}

////////////////////////////////////////////////////////////////////////////////

#ifdef WIN32
void checkShiftAndControl (bool* shiftHeld, bool* controlHeld, bool* altGRDown)
{
   // GetAsyncKeyState?
   if (shiftHeld != NULL)
      (*shiftHeld) =  (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT));
   if (controlHeld != NULL)
      (*controlHeld) = (GetAsyncKeyState(VK_LCONTROL) || GetAsyncKeyState(VK_RCONTROL));
   if (altGRDown != NULL)
      (*altGRDown) = (GetAsyncKeyState(VK_RMENU) > 0);
}
#else
#ifndef __APPLE__
#include <X11/Xlib.h>

void checkShiftAndControl (bool* shiftHeld, bool* controlHeld, bool* altGRDown)
{
   char mask[32] ;
   memset(mask, 0, sizeof (mask));
   Display * display = XOpenDisplay(NULL);
   XQueryKeymap(display, mask);

   
   if (shiftHeld != NULL)
      (*shiftHeld) = (mask[6] & 4) || // Left shift
         (mask[7] & 64); // Right shift;
   if (controlHeld != NULL)
      (*controlHeld) = (mask[4] & 32) || // Left control
         (mask[13] & 2); // Right control
}
#else
void checkShiftAndControl (bool* shiftHeld, bool* controlHeld, bool* altGRDown)
{
}
#endif
#endif

////////////////////////////////////////////////////////////////////////////////
// Test if file exists

bool fileExists (const PathChar* FN)
{
#ifdef WIN32
	Win32HandleCloser h = CreateFileW(FN, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	return h.valid();
#else
	struct stat fileStat;
	return (stat (FN, &fileStat) == 0);
#endif
}

////////////////////////////////////////////////////////////////////////////////

void copyFile (const PathChar* sourceFN, const PathChar* targetFN)
{
	FileCache source, target;
	if (!source.openReadOnly (sourceFN))  {
		printf("Failed to open " PathCharMod " for reading\n", sourceFN);
		return;
	}
	if (!target.create (targetFN)) {
		source.close();
		printf("Failed to create " PathCharMod "\n", targetFN);
		return;
	}
	
	auto sourceSize = source.getFileSize();
	//printf("Copying %i bytes from " PathCharMod " to " PathCharMod "\n", sourceSize, sourceFN, targetFN);

	const FileOffset bufSize = 16384;
	char buf[bufSize];
	FileOffset offset = 0;

	while (offset < sourceSize) {
		FileOffset toRead = std::min<FileOffset>(bufSize, source.getFileSize()-offset);
		source.read (offset, buf, toRead);
		target.write (offset, buf, toRead);
		offset += toRead;
	}

	source.close();
	target.close();
}

////////////////////////////////////////////////////////////////////////////////
// Simple class to make sure that handles are closed 

#ifdef _WIN32

Win32HandleCloser::Win32HandleCloser(HANDLE handle)
: mHandle(handle)
{
}

Win32HandleCloser::~Win32HandleCloser()
{
	close();
}

void Win32HandleCloser::close()
{
	SET_SCOPE;
	if (mHandle != INVALID_HANDLE_VALUE) {
		CloseHandle(mHandle);
		mHandle = INVALID_HANDLE_VALUE;
	}
	RETURN;
}

HANDLE Win32HandleCloser::get() const
{
	return mHandle;
}

bool Win32HandleCloser::valid()
{
	return (mHandle != INVALID_HANDLE_VALUE);
}

void Win32HandleCloser::operator=(HANDLE newVal)
{
	if (valid())
		CloseHandle(mHandle);
	mHandle = newVal;
}

#endif

////////////////////////////////////////////////////////////////////////////////
// MemoryTracker 

MemoryTracker* gMT = nullptr;

MemoryTracker* getMemoryTracker()
{
	return gMT;
}

void setMemoryTracker(MemoryTracker* mt)
{
	gMT = mt;
}

////////////////////////////////////////////////////////////////////////////////
// This subclass implements memory tracking by tracking every single allocation

struct MemoryUse {
	const char* className;
	int classID;
	size_t instanceSize;
	size_t count;

	MemoryUse(const char* aClassName, int aClassID, size_t aInstanceSize)
	: className (aClassName), 
		classID (aClassID), 
		instanceSize (aInstanceSize),
		count(1)
	{
	}
};

class SimpleMemoryTracker: public MemoryTracker {
public:
	// Register a new object
	void registerObject(const char* className, int classID, size_t instanceSize) OVERRIDE
	{
		for (size_t i = 0; i < mMemoryUses.size(); ++i) {
			MemoryUse& memUse = mMemoryUses[i];
			if (memUse.classID == classID) {
				++memUse.count;
				memUse.instanceSize += instanceSize;
				return;
			}
		}

		// No old entry found, create a new one
		MemoryUse memUse(className, classID, instanceSize);
		mMemoryUses.push_back(memUse);
	}

	// Deregister an object that is about to be deleted
	void deregisterObject(int classID, size_t instanceSize) OVERRIDE
	{
		for (size_t i = 0; i < mMemoryUses.size(); ++i) {
			MemoryUse& memUse = mMemoryUses[i];
			if (memUse.classID == classID) {
				if (memUse.count > 0) {
					--memUse.count;
					if (memUse.count > 0 && memUse.instanceSize > instanceSize) {
						memUse.instanceSize -= instanceSize;
					} else { 
						memUse.instanceSize = 0;
					}
					return;
				} else {
					COUT << "Trying to destroy an instance of " << memUse.className << ", but no more instances are known ..." << std::endl;
				}
			}
		}
	}

	// Print usage information
	void dump() OVERRIDE
	{
		size_t knownMemoryUses = 0;
		for (size_t i = 0; i < mMemoryUses.size(); ++i) {
			if (mMemoryUses[i].count > 0) {
				const MemoryUse& memUse = mMemoryUses[i];
				COUT << memUse.className << "(id=" << memUse.classID << "): " << memUse.count << " instances use ";
				BGCPString::sizeStr(memUse.instanceSize).printLn();
				knownMemoryUses += memUse.instanceSize;
			}
		}

		COUT << "Known memory use: ";
		BGCPString::sizeStr(knownMemoryUses).printLn();
	}

private:
	std::vector<MemoryUse> mMemoryUses;
};

void activateSimpleMemoryTracker()
{
	setMemoryTracker(new SimpleMemoryTracker);
}

void logStackTrace(const char* msg)
{
	CONSTEXPR size_t bufLen = 2048;
	char buf[bufLen];
	stGetCurrentThreadStackTrace(buf, bufLen);
	COUT << "Stacktrace: " << buf << std::endl;
}

// Test various types of crashes
#ifdef _DEBUG
int crash(int method)
{
	switch(method) {
		case 0: // division by zero
		{
			int i = 1;
			return i / (i-1);
		}
		case 1: // nullptr unreference
		{
			char* p = nullptr;
			return p[0];
		}
		case 2: // infinite recusion
			return crash(method);
		case 3: // C++ exception
			throw std::exception("Hello crashes");
			return 0;
		default: // double free
		{
			char* p = new char[10];
			char* q = p;
			delete p;
			delete q;
			return 0;
		}
	}
}
#endif
