////////////////////////////////////////////////////////////////////////////////
// MemoryBlock.cpp
// Represent inhomogeneous data in memory, e.g. a file
// Copyright Bjoern Ganster
////////////////////////////////////////////////////////////////////////////////

#include "MemoryBlock.h"

#include <algorithm>
#include <iostream>

#define FILE_NUMBER FILE_NO_MEMORYBLOCK_CPP

////////////////////////////////////////////////////////////////////////////////
// Default constructor

MemoryBlock::MemoryBlock ()
: VirtualClass ("MemoryBlock", MemoryBlockID),
  mBuf(nullptr),
  mSize(0),
  mNeedToFreeMem(false)
{
}

// Construct and reserve memory
MemoryBlock::MemoryBlock(size_t newSize)
: VirtualClass ("MemoryBlock", MemoryBlockID)
{
	SET_SCOPE;
	mSize = newSize;
	mBuf = (unsigned char*) traceAlloc(mSize, "MemoryBlockBuf", MemoryBlockBufID);
	mNeedToFreeMem = true;
	RETURN;
}

// Clone constructor
MemoryBlock::MemoryBlock (void* newBuf, size_t newSize, bool freeLater)
: VirtualClass ("MemoryBlock", MemoryBlockID)
{
	SET_SCOPE;
	mSize = newSize;
	mBuf = (unsigned char*) newBuf;
	mNeedToFreeMem = freeLater;
	RETURN;
}

// Destructor
MemoryBlock::~MemoryBlock()
{
	SET_SCOPE;
	clear();
	RETURN;
}

// Clear contents
void MemoryBlock::clear()
{
	SET_SCOPE;
	if (mNeedToFreeMem) {
		SET_OP;
		traceFree(mBuf, MemoryBlockBufID, mSize);
	}
	mBuf = nullptr;
	mSize = 0;
	RETURN;
}

// Get size of MemoryBlock
size_t MemoryBlock::getSize() const
{
	return mSize;
}

// Resize MemoryBlock
bool MemoryBlock::resize (size_t newSize)
{
	SET_SCOPE;
	unsigned char* tempBuf = nullptr;
	if (newSize != 0) {
		SET_OP;
		tempBuf = (unsigned char*) traceAlloc(newSize, "MemoryBlockBuf", MemoryBlockBufID);
		if (tempBuf == NULL) {
			COUT <<"Out of memory error" << std::endl;
			RETURN false;
		}
	}

	SET_OP;
	if (tempBuf != NULL) {
		SET_OP;
		size_t count = std::min(mSize, newSize);
		if (count > 0 && mBuf != nullptr) {
			memcpy (tempBuf, mBuf, count);
		}
    }

	if (mSize > 0 && mBuf != NULL && mNeedToFreeMem) {
		SET_OP;
		traceFree(mBuf, MemoryBlockBufID, mSize);
	}

	mSize = newSize;
	mBuf = tempBuf;
	mNeedToFreeMem = true;
	RETURN true;
}

////////////////////////////////////////////////////////////////////////////////
// Get memory block for a file

// Load from file
bool MemoryBlock::loadFromFile (const PathChar* FN)
{
	SET_SCOPE;
	if (mBuf != nullptr && mNeedToFreeMem) {
		traceFree(mBuf, MemoryBlockBufID, mSize);
	}

	mBuf = (unsigned char*) ::loadFromFile(FN, mSize);
	if (mBuf != nullptr) {
		mNeedToFreeMem = true;
		RETURN true;
	} else {
		mNeedToFreeMem = false;
		RETURN false;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Read from data block - returns the number of bytes read


size_t MemoryBlock::read(size_t offset, size_t byteCount, void* data) const
{
	if (offset >= mSize) {
		return 0;
	}

	SET_SCOPE;
	size_t toRead = byteCount;
	if (offset + byteCount > mSize) {
		toRead = mSize-offset;
	}

	SET_OP;
	memcpy(data, mBuf+offset, toRead);
	RETURN byteCount;
}

// Copy data into the buffer, enlarging it when necessary
// todo: add flag that controls whether the buffer may be enlarged ...
bool MemoryBlock::write(size_t offset, const void* aBuf, size_t count)
{
	if (aBuf == nullptr || count == 0) {
		return false;
	}

	SET_SCOPE;
	if (offset+count > mSize) {
		SET_OP;
		resize(offset+count);
	}

	if (offset+count <= mSize) {
		memcpy(mBuf + offset, aBuf, count);
		RETURN true;
	} else {
		RETURN false;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Replace all occurences of old with new string

size_t MemoryBlock::replaceAll (const char* oldStr, const char* newStr)
{
	SET_SCOPE;
	size_t len = strlen (oldStr);
	size_t replaced = 0;
	if (len != strlen (newStr) || mSize < len) {
		RETURN 0;
	}

	for (size_t i = 0; i < mSize-len; ++i) {
		bool matches = true;
		size_t j = 0; 
		while (j < len && matches) {
			if (mBuf[i+j] == oldStr[j])
				++j;
			else
				matches = false;
		}

		if (matches) {
			++replaced;
			for (size_t k = 0; k < len; ++k)
				mBuf[i+k] = newStr[k];
		}
	}

	RETURN replaced;
}

////////////////////////////////////////////////////////////////////////////////
// Fill block with byte. Resizes if necessary.

bool MemoryBlock::fill(size_t offset, size_t count, char byte)
{
	SET_SCOPE;
	bool result = true;
	if (offset + count > mSize) {
		if (!resize(offset + count)) {
			result = false;
			if (offset < mSize) {
				count = mSize-offset;
			} else {
				RETURN false;
			}
		}
	}
	memset(mBuf+offset, byte, count);
	RETURN result;
}

void MemoryBlock::dump(size_t offset, size_t count)
{
	char* hexchars = "0123456789abcdef";
	while (count > 0) {
		unsigned char c = get<unsigned char>(offset);
		char str[4];
		str[0] = hexchars[c / 16];
		str[1] = hexchars[c % 16];
		str[2] = ' ';
		str[3] = 0;
		printf(str);
		++offset;
		--count;
	}
	printf("\n");
}

// xor this and other bytewise
bool MemoryBlock::xorData(const MemoryBlock& other)
{
	if (other.getSize() == 0) {
		return false;
	}

	for (size_t i = 0; i < getSize(); ++i) {
		char c = get<BYTE>(i);
		size_t di = i % other.getSize();
		char d = other.get<BYTE>(di);
		set<BYTE>(i, c ^ d);
	}

	return true;
}

// Encrypt the file given by fn1 using the file given by fn2
bool xorEncrypt(const PathChar* fn1, const PathChar* fn2)
{
	MemoryBlock mb1, mb2;
	bool mb1Loaded = mb1.loadFromFile(fn1);
	bool mb2Loaded = mb2.loadFromFile(fn2);
	if (mb1Loaded && mb2Loaded) {
		mb1.xorData(mb2);
		return mb1.saveToFile(fn1);
	} else {
		return false;
	}
}

// Copy/assignment operator
void MemoryBlock::operator=(const MemoryBlock& other)
{
	if (resize(other.getSize())) {
		memcpy(getPtr<char>(0), other.getPtr<char>(0), other.getSize());
	}
}
