#include "bbsys.hpp"
#include <map>

#include <stdutil.hpp>

//how many strings allocated
static int stringCnt;

//how many objects new'd but not deleted
static int objCnt;

//how many objects deleted but not released
static int unrelObjCnt;

//how many objects to alloc per block
static const int OBJ_NEW_INC = 512;

//how many strings to alloc per block
static const int STR_NEW_INC = 512;

//current data ptr
static BBData* dataPtr;

//chunks of mem - WHAT THE FUCK WAS I ON?!?!?!?
//static list<char*> memBlks;

//strings
static BBStr usedStrs, freeStrs;

//object handle number
static int next_handle;

//object<->handle maps
static std::map<int, BBObj*> handle_map;
static std::map<BBObj*, int> object_map;

static BBType _bbIntType(BBTYPE_INT);
static BBType _bbFltType(BBTYPE_FLT);
static BBType _bbStrType(BBTYPE_STR);
static BBType _bbCStrType(BBTYPE_CSTR);

static void* bbMalloc(int size)
{
	return malloc(size);
	/*
	char *c=new char[ size ];
	memBlks.push_back( c );
	return c;
	*/
}

static void bbFree(void* q)
{
	free(q);
	/*
	if( !q ) return;
	char *c=(char*)q;
	memBlks.remove( c );
	delete [] c;
	*/
}

static void removeStr(BBStr* str)
{
	str->next->prev = str->prev;
	str->prev->next = str->next;
}

static void insertStr(BBStr* str, BBStr* next)
{
	str->next       = next;
	str->prev       = next->prev;
	str->prev->next = str;
	next->prev      = str;
}

void* BBStr::operator new(size_t size)
{
	if (freeStrs.next == &freeStrs) {
		BBStr* t = (BBStr*)bbMalloc(sizeof(BBStr) * STR_NEW_INC);
		for (int k = 0; k < STR_NEW_INC; ++k)
			insertStr(t++, &freeStrs);
	}
	BBStr* t = freeStrs.next;
	removeStr(t);
	insertStr(t, &usedStrs);
	return t;
}

void BBStr::operator delete(void* q)
{
	if (!q)
		return;
	BBStr* t = (BBStr*)q;
	removeStr(t);
	insertStr(t, &freeStrs);
}

BBStr::BBStr()
{
	++stringCnt;
}

BBStr::BBStr(const char* s) : std::string(s)
{
	++stringCnt;
}

BBStr::BBStr(const char* s, int n) : std::string(s, n)
{
	++stringCnt;
}

BBStr::BBStr(const BBStr& s) : std::string(s)
{
	++stringCnt;
}

BBStr::BBStr(const std::string& s) : std::string(s)
{
	++stringCnt;
}

BBStr& BBStr::operator=(const char* s)
{
	std::string::operator=(s);
	return *this;
}

BBStr& BBStr::operator=(const BBStr& s)
{
	std::string::operator=(s);
	return *this;
}

BBStr& BBStr::operator=(const std::string& s)
{
	std::string::operator=(s);
	return *this;
}

BBStr::~BBStr()
{
	--stringCnt;
}

BBStr* _bbStrLoad(BBStr** var)
{
	return *var ? new BBStr(**var) : new BBStr();
}

void _bbStrRelease(BBStr* str)
{
	delete str;
}

void _bbStrStore(BBStr** var, BBStr* str)
{
	_bbStrRelease(*var);
	*var = str;
}

BBStr* _bbStrConcat(BBStr* s1, BBStr* s2)
{
	*s1 += *s2;
	delete s2;
	return s1;
}

int _bbStrCompare(BBStr* lhs, BBStr* rhs)
{
	int n = lhs->compare(*rhs);
	delete lhs;
	delete rhs;
	return n;
}

int _bbStrToInt(BBStr* s)
{
	int n = atoi(*s);
	delete s;
	return n;
}

BBStr* _bbStrFromInt(int n)
{
	return new BBStr(itoa(n));
}

float _bbStrToFloat(BBStr* s)
{
	float n = (float)atof(*s);
	delete s;
	return n;
}

BBStr* _bbStrFromFloat(float n)
{
	return new BBStr(ftoa(n));
}

BBStr* _bbStrConst(const char* s)
{
	return new BBStr(s);
}

void* _bbVecAlloc(BBVecType* type)
{
	void* vec = bbMalloc(type->size * 4);
	memset(vec, 0, type->size * 4);
	return vec;
}

void _bbVecFree(void* vec, BBVecType* type)
{
	if (type->elementType->type == BBTYPE_STR) {
		BBStr** p = (BBStr**)vec;
		for (int k = 0; k < type->size; ++p, ++k) {
			if (*p)
				_bbStrRelease(*p);
		}
	} else if (type->elementType->type == BBTYPE_OBJ) {
		BBObj** p = (BBObj**)vec;
		for (int k = 0; k < type->size; ++p, ++k) {
			if (*p)
				_bbObjRelease(*p);
		}
	}
	bbFree(vec);
}

void _bbVecBoundsEx()
{
	ThrowRuntimeException("Blitz array index out of bounds");
}

void _bbUndimArray(BBArray* array)
{
	if (void* t = array->data) {
		if (array->elementType == BBTYPE_STR) {
			BBStr** p    = (BBStr**)t;
			int     size = array->scales[array->dims - 1];
			for (int k = 0; k < size; ++p, ++k) {
				if (*p)
					_bbStrRelease(*p);
			}
		} else if (array->elementType == BBTYPE_OBJ) {
			BBObj** p    = (BBObj**)t;
			int     size = array->scales[array->dims - 1];
			for (int k = 0; k < size; ++p, ++k) {
				if (*p)
					_bbObjRelease(*p);
			}
		}
		bbFree(t);
		array->data = 0;
	}
}

void _bbDimArray(BBArray* array)
{
	int k;
	for (k = 0; k < array->dims; ++k)
		++array->scales[k];
	for (k = 1; k < array->dims; ++k) {
		array->scales[k] *= array->scales[k - 1];
	}
	int size    = array->scales[array->dims - 1];
	array->data = bbMalloc(size * 4);
	memset(array->data, 0, size * 4);
}

void _bbArrayBoundsEx()
{
	ThrowRuntimeException("Array index out of bounds");
}

static void unlinkObj(BBObj* obj)
{
	obj->next->prev = obj->prev;
	obj->prev->next = obj->next;
}

static void insertObj(BBObj* obj, BBObj* next)
{
	obj->next        = next;
	obj->prev        = next->prev;
	next->prev->next = obj;
	next->prev       = obj;
}

BBObj* _bbObjNew(BBObjType* type)
{
	if (type->free.next == &type->free) {
		int    obj_size = sizeof(BBObj) + type->fieldCnt * 4;
		BBObj* o        = (BBObj*)bbMalloc(obj_size * OBJ_NEW_INC);
		for (int k = 0; k < OBJ_NEW_INC; ++k) {
			insertObj(o, &type->free);
			o = (BBObj*)((char*)o + obj_size);
		}
	}
	BBObj* o = type->free.next;
	unlinkObj(o);
	o->type    = type;
	o->ref_cnt = 1;
	o->fields  = (BBField*)(o + 1);
	for (int k = 0; k < type->fieldCnt; ++k) {
		switch (type->fieldTypes[k]->type) {
		case BBTYPE_VEC:
			o->fields[k].VEC = _bbVecAlloc((BBVecType*)type->fieldTypes[k]);
			break;
		default:
			o->fields[k].INT = 0;
		}
	}
	insertObj(o, &type->used);
	++unrelObjCnt;
	++objCnt;
	return o;
}

void _bbObjDelete(BBObj* obj)
{
	if (!obj)
		return;
	BBField* fields = obj->fields;
	if (!fields)
		return;
	BBObjType* type = obj->type;
	for (int k = 0; k < type->fieldCnt; ++k) {
		switch (type->fieldTypes[k]->type) {
		case BBTYPE_STR:
			_bbStrRelease(fields[k].STR);
			break;
		case BBTYPE_OBJ:
			_bbObjRelease(fields[k].OBJ);
			break;
		case BBTYPE_VEC:
			_bbVecFree(fields[k].VEC, (BBVecType*)type->fieldTypes[k]);
			break;
		}
	}
	std::map<BBObj*, int>::iterator it = object_map.find(obj);
	if (it != object_map.end()) {
		handle_map.erase(it->second);
		object_map.erase(it);
	}
	obj->fields = 0;
	_bbObjRelease(obj);
	--objCnt;
}

void _bbObjDeleteEach(BBObjType* type)
{
	BBObj* obj = type->used.next;
	while (obj->type) {
		BBObj* next = obj->next;
		if (obj->fields)
			_bbObjDelete(obj);
		obj = next;
	}
}

extern void bbDebugLog(BBStr* t);
extern void bbStop();

void _bbObjRelease(BBObj* obj)
{
	if (!obj || --obj->ref_cnt)
		return;
	unlinkObj(obj);
	insertObj(obj, &obj->type->free);
	--unrelObjCnt;
}

void _bbObjStore(BBObj** var, BBObj* obj)
{
	if (obj)
		++obj->ref_cnt; //do this first incase of self-assignment
	_bbObjRelease(*var);
	*var = obj;
}

int _bbObjCompare(BBObj* o1, BBObj* o2)
{
	return (o1 ? o1->fields : 0) != (o2 ? o2->fields : 0);
}

BBObj* _bbObjNext(BBObj* obj)
{
	do {
		obj = obj->next;
		if (!obj->type)
			return 0;
	} while (!obj->fields);
	return obj;
}

BBObj* _bbObjPrev(BBObj* obj)
{
	do {
		obj = obj->prev;
		if (!obj->type)
			return 0;
	} while (!obj->fields);
	return obj;
}

BBObj* _bbObjFirst(BBObjType* type)
{
	return _bbObjNext(&type->used);
}

BBObj* _bbObjLast(BBObjType* type)
{
	return _bbObjPrev(&type->used);
}

void _bbObjInsBefore(BBObj* o1, BBObj* o2)
{
	if (o1 == o2)
		return;
	unlinkObj(o1);
	insertObj(o1, o2);
}

void _bbObjInsAfter(BBObj* o1, BBObj* o2)
{
	if (o1 == o2)
		return;
	unlinkObj(o1);
	insertObj(o1, o2->next);
}

int _bbObjEachFirst(BBObj** var, BBObjType* type)
{
	_bbObjStore(var, _bbObjFirst(type));
	return *var != 0;
}

int _bbObjEachNext(BBObj** var)
{
	_bbObjStore(var, _bbObjNext(*var));
	return *var != 0;
}

int _bbObjEachFirst2(BBObj** var, BBObjType* type)
{
	*var = _bbObjFirst(type);
	return *var != 0;
}

int _bbObjEachNext2(BBObj** var)
{
	*var = _bbObjNext(*var);
	return *var != 0;
}

BBStr* _bbObjToStr(BBObj* obj)
{
	if (!obj || !obj->fields)
		return new BBStr("[NULL]");

	static BBObj* root;
	static int    recurs_cnt;

	if (obj == root)
		return new BBStr("[ROOT]");
	if (recurs_cnt == 8)
		return new BBStr("....");

	++recurs_cnt;
	BBObj* oldRoot = root;
	if (!root)
		root = obj;

	BBObjType* type   = obj->type;
	BBField*   fields = obj->fields;
	BBStr *    s      = new BBStr("["), *t;
	for (int k = 0; k < type->fieldCnt; ++k) {
		if (k)
			*s += ',';
		switch (type->fieldTypes[k]->type) {
		case BBTYPE_INT:
			t = _bbStrFromInt(fields[k].INT);
			*s += *t;
			delete t;
			break;
		case BBTYPE_FLT:
			t = _bbStrFromFloat(fields[k].FLT);
			*s += *t;
			delete t;
			break;
		case BBTYPE_STR:
			if (fields[k].STR)
				*s += '\"' + *fields[k].STR + '\"';
			else
				*s += "\"\"";
			break;
		case BBTYPE_OBJ:
			t = _bbObjToStr(fields[k].OBJ);
			*s += *t;
			delete t;
			break;
		default:
			*s += "???";
		}
	}
	*s += ']';
	root = oldRoot;
	--recurs_cnt;
	return s;
}

int _bbObjToHandle(BBObj* obj)
{
	if (!obj || !obj->fields)
		return 0;
	std::map<BBObj*, int>::const_iterator it = object_map.find(obj);
	if (it != object_map.end())
		return it->second;
	++next_handle;
	object_map[obj]         = next_handle;
	handle_map[next_handle] = obj;
	return next_handle;
}

BBObj* _bbObjFromHandle(int handle, BBObjType* type)
{
	std::map<int, BBObj*>::const_iterator it = handle_map.find(handle);
	if (it == handle_map.end())
		return 0;
	BBObj* obj = it->second;
	return obj->type == type ? obj : 0;
}

void _bbNullObjEx()
{
	ThrowRuntimeException("Object does not exist");
}

void _bbRestore(BBData* data)
{
	dataPtr = data;
}

int _bbReadInt()
{
	switch (dataPtr->fieldType) {
	case BBTYPE_END:
		ThrowRuntimeException("Out of data");
		return 0;
	case BBTYPE_INT:
		return dataPtr++->field.INT;
	case BBTYPE_FLT:
		return dataPtr++->field.FLT;
	case BBTYPE_CSTR:
		return atoi(dataPtr++->field.CSTR);
	default:
		ThrowRuntimeException("Bad data type");
		return 0;
	}
}

float _bbReadFloat()
{
	switch (dataPtr->fieldType) {
	case BBTYPE_END:
		ThrowRuntimeException("Out of data");
		return 0;
	case BBTYPE_INT:
		return dataPtr++->field.INT;
	case BBTYPE_FLT:
		return dataPtr++->field.FLT;
	case BBTYPE_CSTR:
		return atof(dataPtr++->field.CSTR);
	default:
		ThrowRuntimeException("Bad data type");
		return 0;
	}
}

BBStr* _bbReadStr()
{
	switch (dataPtr->fieldType) {
	case BBTYPE_END:
		ThrowRuntimeException("Out of data");
		return 0;
	case BBTYPE_INT:
		return new BBStr(itoa(dataPtr++->field.INT));
	case BBTYPE_FLT:
		return new BBStr(ftoa(dataPtr++->field.FLT));
	case BBTYPE_CSTR:
		return new BBStr(dataPtr++->field.CSTR);
	default:
		ThrowRuntimeException("Bad data type");
		return 0;
	}
}

int _bbAbs(int n)
{
	return n >= 0 ? n : -n;
}

int _bbSgn(int n)
{
	return n > 0 ? 1 : (n < 0 ? -1 : 0);
}

int _bbMod(int x, int y)
{
	return x % y;
}

float _bbFAbs(float n)
{
	return n >= 0 ? n : -n;
}

float _bbFSgn(float n)
{
	return n > 0 ? 1 : (n < 0 ? -1 : 0);
}

float _bbFMod(float x, float y)
{
	return (float)fmod(x, y);
}

float _bbFPow(float x, float y)
{
	return (float)pow(x, y);
}

void bbRuntimeStats()
{
	gx_runtime->debugLog(("Active strings :" + itoa(stringCnt)).c_str());
	gx_runtime->debugLog(("Active objects :" + itoa(objCnt)).c_str());
	gx_runtime->debugLog(("Unreleased objs:" + itoa(unrelObjCnt)).c_str());
	/*
	clog<<"Active strings:"<<stringCnt<<endl;
	clog<<"Active objects:"<<objCnt<<endl;
	clog<<"Unreleased Objects:"<<unrelObjCnt<<endl;
	for( BBStr *t=usedStrs.next;t!=&usedStrs;t=t->next ){
	clog<<"string@"<<(void*)t<<endl;
	}
	*/
}

bool basic_create()
{
	next_handle = 0;
	//	memBlks.clear();
	handle_map.clear();
	object_map.clear();
	stringCnt = objCnt = unrelObjCnt = 0;
	usedStrs.next = usedStrs.prev = &usedStrs;
	freeStrs.next = freeStrs.prev = &freeStrs;
	return true;
}

bool basic_destroy()
{
	while (usedStrs.next != &usedStrs)
		delete usedStrs.next;
	//	while( memBlks.size() ) bbFree( memBlks.back() );
	handle_map.clear();
	object_map.clear();
	return true;
}

void basic_link(void (*rtSym)(const char* sym, void* pc))
{
	rtSym("_bbIntType", &_bbIntType);
	rtSym("_bbFltType", &_bbFltType);
	rtSym("_bbStrType", &_bbStrType);
	rtSym("_bbCStrType", &_bbCStrType);

	rtSym("_bbStrLoad", _bbStrLoad);
	rtSym("_bbStrRelease", _bbStrRelease);
	rtSym("_bbStrStore", _bbStrStore);
	rtSym("_bbStrCompare", _bbStrCompare);
	rtSym("_bbStrConcat", _bbStrConcat);
	rtSym("_bbStrToInt", _bbStrToInt);
	rtSym("_bbStrFromInt", _bbStrFromInt);
	rtSym("_bbStrToFloat", _bbStrToFloat);
	rtSym("_bbStrFromFloat", _bbStrFromFloat);
	rtSym("_bbStrConst", _bbStrConst);
	rtSym("_bbDimArray", _bbDimArray);
	rtSym("_bbUndimArray", _bbUndimArray);
	rtSym("_bbArrayBoundsEx", _bbArrayBoundsEx);
	rtSym("_bbVecAlloc", _bbVecAlloc);
	rtSym("_bbVecFree", _bbVecFree);
	rtSym("_bbVecBoundsEx", _bbVecBoundsEx);
	rtSym("_bbObjNew", _bbObjNew);
	rtSym("_bbObjDelete", _bbObjDelete);
	rtSym("_bbObjDeleteEach", _bbObjDeleteEach);
	rtSym("_bbObjRelease", _bbObjRelease);
	rtSym("_bbObjStore", _bbObjStore);
	rtSym("_bbObjCompare", _bbObjCompare);
	rtSym("_bbObjNext", _bbObjNext);
	rtSym("_bbObjPrev", _bbObjPrev);
	rtSym("_bbObjFirst", _bbObjFirst);
	rtSym("_bbObjLast", _bbObjLast);
	rtSym("_bbObjInsBefore", _bbObjInsBefore);
	rtSym("_bbObjInsAfter", _bbObjInsAfter);
	rtSym("_bbObjEachFirst", _bbObjEachFirst);
	rtSym("_bbObjEachNext", _bbObjEachNext);
	rtSym("_bbObjEachFirst2", _bbObjEachFirst2);
	rtSym("_bbObjEachNext2", _bbObjEachNext2);
	rtSym("_bbObjToStr", _bbObjToStr);
	rtSym("_bbObjToHandle", _bbObjToHandle);
	rtSym("_bbObjFromHandle", _bbObjFromHandle);
	rtSym("_bbNullObjEx", _bbNullObjEx);
	rtSym("_bbRestore", _bbRestore);
	rtSym("_bbReadInt", _bbReadInt);
	rtSym("_bbReadFloat", _bbReadFloat);
	rtSym("_bbReadStr", _bbReadStr);
	rtSym("_bbAbs", _bbAbs);
	rtSym("_bbSgn", _bbSgn);
	rtSym("_bbMod", _bbMod);
	rtSym("_bbFAbs", _bbFAbs);
	rtSym("_bbFSgn", _bbFSgn);
	rtSym("_bbFMod", _bbFMod);
	rtSym("_bbFPow", _bbFPow);
	rtSym("RuntimeStats", bbRuntimeStats);
}
