#include "bbbank.hpp"
#include <set>
#include "bbstream.hpp"

struct bbBank {
	char* data;
	int   size, capacity;

	bbBank(int sz) : size(sz)
	{
		capacity = (size + 15) & ~15;
		data     = new char[capacity];
		memset(data, 0, size);
	}
	virtual ~bbBank()
	{
		delete[] data;
	}
	void resize(int n)
	{
		if (n > size) {
			if (n > capacity) {
				capacity = capacity * 3 / 2;
				if (n > capacity)
					capacity = n;
				capacity = (capacity + 15) & ~15;
				char* p  = new char[capacity];
				memcpy(p, data, size);
				delete[] data;
				data = p;
			} else
				memset(data + size, 0, n - size);
		}
		size = n;
	}
};

static std::set<bbBank*> bank_set;

#ifdef _DEBUG
static inline void debugBank(bbBank* b)
{
	if (debug) {
		if (!bank_set.count(b))
			ThrowRuntimeException("bbBank does not exist");
	}
}

static inline void debugBank(bbBank* b, int offset)
{
	if (debug) {
		debugBank(b);
		if (offset >= b->size)
			ThrowRuntimeException("Offset out of range");
	}
}
#else
#define debugBank
#endif

bbBank* bbCreateBank(int size)
{
	bbBank* b = new bbBank(size);
	bank_set.insert(b);
	return b;
}

void bbFreeBank(bbBank* b)
{
	if (bank_set.erase(b))
		delete b;
}

int bbBankSize(bbBank* b)
{
	debugBank(b);
	return b->size;
}

void bbResizeBank(bbBank* b, int size)
{
	debugBank(b);
	b->resize(size);
}

void bbCopyBank(bbBank* src, int src_p, bbBank* dest, int dest_p, int count)
{
	if (debug) {
		debugBank(src, src_p + count - 1);
		debugBank(dest, dest_p + count - 1);
	}
	memmove(dest->data + dest_p, src->data + src_p, count);
}

int bbPeekByte(bbBank* b, int offset)
{
	debugBank(b, offset);
	return *(unsigned char*)(b->data + offset);
}

int bbPeekShort(bbBank* b, int offset)
{
	debugBank(b, offset + 1);
	return *(unsigned short*)(b->data + offset);
}

int bbPeekInt(bbBank* b, int offset)
{
	debugBank(b, offset + 3);
	return *(int*)(b->data + offset);
}

float bbPeekFloat(bbBank* b, int offset)
{
	debugBank(b, offset + 3);
	return *(float*)(b->data + offset);
}

void bbPokeByte(bbBank* b, int offset, int value)
{
	debugBank(b, offset);
	*(char*)(b->data + offset) = value;
}

void bbPokeShort(bbBank* b, int offset, int value)
{
	debugBank(b, offset);
	*(unsigned short*)(b->data + offset) = value;
}

void bbPokeInt(bbBank* b, int offset, int value)
{
	debugBank(b, offset);
	*(int*)(b->data + offset) = value;
}

void bbPokeFloat(bbBank* b, int offset, float value)
{
	debugBank(b, offset);
	*(float*)(b->data + offset) = value;
}

int bbReadBytes(bbBank* b, bbStream* s, int offset, int count)
{
	if (debug) {
		debugBank(b, offset + count - 1);
		debugStream(s);
	}
	return s->read(b->data + offset, count);
}

int bbWriteBytes(bbBank* b, bbStream* s, int offset, int count)
{
	if (debug) {
		debugBank(b, offset + count - 1);
		debugStream(s);
	}
	return s->write(b->data + offset, count);
}

int bbCallDLL(BBStr* dll, BBStr* fun, bbBank* in, bbBank* out)
{
	if (debug) {
		if (in)
			debugBank(in);
		if (out)
			debugBank(out);
	}
	int t =
		gx_runtime->callDll(*dll, *fun, in ? in->data : 0, in ? in->size : 0, out ? out->data : 0, out ? out->size : 0);
	delete dll;
	delete fun;
	return t;
}

bool bank_create()
{
	return true;
}

bool bank_destroy()
{
	while (bank_set.size())
		bbFreeBank(*bank_set.begin());
	return true;
}

void bank_link(void (*rtSym)(const char*, void*))
{
	rtSym("%CreateBank%size=0", bbCreateBank);
	rtSym("FreeBank%bank", bbFreeBank);
	rtSym("%BankSize%bank", bbBankSize);
	rtSym("ResizeBank%bank%size", bbResizeBank);
	rtSym("CopyBank%src_bank%src_offset%dest_bank%dest_offset%count", bbCopyBank);
	rtSym("%PeekByte%bank%offset", bbPeekByte);
	rtSym("%PeekShort%bank%offset", bbPeekShort);
	rtSym("%PeekInt%bank%offset", bbPeekInt);
	rtSym("#PeekFloat%bank%offset", bbPeekFloat);
	rtSym("PokeByte%bank%offset%value", bbPokeByte);
	rtSym("PokeShort%bank%offset%value", bbPokeShort);
	rtSym("PokeInt%bank%offset%value", bbPokeInt);
	rtSym("PokeFloat%bank%offset#value", bbPokeFloat);
	rtSym("%ReadBytes%bank%file%offset%count", bbReadBytes);
	rtSym("%WriteBytes%bank%file%offset%count", bbWriteBytes);
	rtSym("%CallDLL$dll_name$func_name%in_bank=0%out_bank=0", bbCallDLL);
}
