/*#io
UDB ioDoc(
		docCopyright("Steve Dekorte", 2004)
		docLicense("BSD revised")
		docObject("UDB")    
		docDescription("")
		*/

#include "SkipDB.h"
#include "UDB.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

UDB *UDB_new(void)
{
	UDB *self = (UDB *)calloc(1, sizeof(UDB));
	
	self->index       = UDBIndex_new();
	self->records     = UDBRecords_new();
	
	UDB_setPath_(self, "default");
	return self;
}

void UDB_free(UDB *self)
{
	UDB_close(self);
	free(self->path);    
	
	UDBIndex_free(self->index);   
	UDBRecords_free(self->records);   
	free(self);
}

void UDB_delete(UDB *self)
{
	UDB_close(self);
	UDBIndex_delete(self->index);
	UDBRecords_delete(self->records);
}

void UDB_setPath_(UDB *self, const char *path)
{
	self->path = strcpy((char *)realloc(self->path, strlen(path)+1), path);
	
	UDBIndex_setPath_(self->index, path);
	UDBRecords_setPath_(self->records, path);
}

void UDB_setSecondaryPath_(UDB *self, const char *secondaryPath)
{
	UDBIndex_setPath_(self->index, self->path);
	UDBIndex_setLogPath_(self->index, secondaryPath);
	
	UDBRecords_setPath_(self->records, secondaryPath);
	UDBRecords_setLogPath_(self->records, self->path);
}

char *UDB_path(UDB *self) 
{ 
	return self->path; 
}

void UDB_open(UDB *self)
{
	UDBIndex_open(self->index);
	UDBRecords_open(self->records);
	
	if (UDBRecords_isCommitted(self->records))
	{ 
		printf("UDB: found committed transaction that was not completed - completing now.\n");
		UDB_commitTransaction(self); 
	}
	
	self->isOpen = 1;
}

int UDB_isOpen(UDB *self)
{
	return self->isOpen;
}

void UDB_close(UDB *self)
{
	UDBIndex_close(self->index);
	UDBRecords_close(self->records);
	self->isOpen = 0;
}

// transactions --------------------------------------------------- 

int UDB_isInTransaction(UDB *self)
{
	if (!self->withinTransaction)
	{
		printf("UDB error - mutation operations must be within a transaction\n");
		exit(1);
		return 0;
	}
	return 1;
}

void UDB_beginTransaction(UDB *self)
{
	UDBIndex_begin(self->index);
	UDBRecords_begin(self->records);
	self->withinTransaction = 1;
}

void UDB_commitTransaction(UDB *self)
{
	UDBIndex_commit(self->index);
	UDBRecords_commit(self->records);
	self->withinTransaction = 0;
}

// ops --------------------------------------------------- 

PID_TYPE UDB_nextPid(UDB *self)
{
	return UDBIndex_nextPid(self->index);
}

PID_TYPE UDB_allocPid(UDB *self)
{
	return UDBIndex_allocPid(self->index);
}

void UDB_append_withPid_(UDB *self, Datum d, PID_TYPE pid) 
{
	UDBRecord *record = UDBRecords_newRecord(self->records);
	UDBRecord_pid_(record, pid);
	UDBRecord_saveWithDatum_(record, d);
	UDBIndex_setPos_forPid_(self->index, UDBRecord_pos(record), pid);
}

PID_TYPE UDB_put_(UDB *self, Datum d) // returns pid 
{
	if (UDB_isInTransaction(self))
	{
		PID_TYPE pid = UDBIndex_allocPid(self->index);
		UDB_append_withPid_(self, d, pid);
		return pid;
	}
	
	return 0;
}

void UDB_at_put_(UDB *self, PID_TYPE pid, Datum d)
{
	if (UDB_isInTransaction(self))
	{
		UDB_append_withPid_(self, d, pid);
	}
}

UDBRecord *UDB_recordAtPid_(UDB *self, PID_TYPE pid)
{
	PID_TYPE pos = UDBIndex_posForPid_(self->index, pid);
	if (!pos) return NULL;
	return UDBRecords_recordAtPos_(self->records, pos);
}

Datum UDB_at_(UDB *self, PID_TYPE pid)
{
	Datum d;
	UDBRecord *record = UDB_recordAtPid_(self, pid);
	
	if (!record)
	{
		d.size = 0;
		d.data = NULL;
		//printf("missing record with pid %i\n", pid);
		return d;
	}
	
	return UDBRecord_readDatum(record);
}

void UDB_removeAt_(UDB *self, PID_TYPE pid)
{
	if (UDB_isInTransaction(self))
	{
		UDBRecord *record = UDB_recordAtPid_(self, pid);
		
		if (!record)
		{
			printf("UDB error: missing record with pid %" PID_FORMAT " for remove\n", pid);
			record = UDB_recordAtPid_(self, pid);
			return;
		}
		
		UDBRecords_removeRecord_(self->records, record);
		UDBIndex_setPos_forPid_(self->index, 0, pid);
	}
}

// compact --------------------------------------------------- 

UDBRecord *UDB_firstEmptyRecord(UDB *self)
{
	UDBRecord *record = UDBRecords_firstRecord(self->records);
	
	while (record)
	{	
		if (UDBRecord_isEmpty(record)) return record;
		record = UDBRecords_nextRecord(self->records);
	}
	return NULL;
}

int UDB_compact(UDB *self)
{
	int count = 0;
	int compactions;
	
	do 
	{
		compactions = UDB_compactStepFor_(self, 0.1);
		count += compactions;
	} while (compactions);
	
	return count;
}

//#define DEBUG 1

int UDB_compactStep(UDB *self)
{
	return UDB_compactStepFor_(self, 0.0);
}

int UDB_compactStepFor_(UDB *self, double maxSeconds)
{   
	UDBRecord *firstEmptyRecord = UDBRecords_firstEmptyRecord(self->records);
	
	if (!firstEmptyRecord)
	{
		return 0;
	}
	
	if (!UDBRecord_isEmpty(firstEmptyRecord))
	{
		printf("firstEmptyRecord not empty!\n");
		firstEmptyRecord = UDBRecords_firstEmptyRecord(self->records);
		UDBRecord_isEmpty(firstEmptyRecord);
		exit(1);
	}
	
#ifdef DEBUG
	printf("empty record pid: %i pos: %i size: %i isEmpty: %i\n", 
		  UDBRecord_pid(firstEmptyRecord), 
		  UDBRecord_pos(firstEmptyRecord), 
		  UDBRecord_totalSize(firstEmptyRecord), 
		  UDBRecord_isEmpty(firstEmptyRecord));
#endif
	    
	if (firstEmptyRecord)
	{
		UDBRecord *record = UDBRecords_recordAfter_(self->records, firstEmptyRecord);
		
		if (!record)
		{
			// reached end of file 
			PID_TYPE size = UDBRecord_pos(firstEmptyRecord);
			UDBRecords_truncate_(self->records, size);
			return 0;
		}
		
#ifdef DEBUG
		printf("next  record pid: %i pos: %i size: %i isEmpty: %i\n\n", 
			  UDBRecord_pid(record), 
			  UDBRecord_pos(record), 
			  UDBRecord_totalSize(record), 
			  UDBRecord_isEmpty(record));
#endif
		
		UDB_beginTransaction(self);
		
		if (UDBRecord_isEmpty(record))
		{
			// coalese this empty record into the first empty record 
			PID_TYPE newSize = UDBRecord_totalSize(firstEmptyRecord) 
			- sizeof(UDBRecordHeader) + UDBRecord_totalSize(record);
			UDBRecord_size_(firstEmptyRecord, newSize);
			UDBRecord_saveHeader(firstEmptyRecord);
#ifdef DEBUG
			printf("merge\n");
#endif
			UDBIndex_setPos_forPid_(self->index, 0, UDBRecord_pid(record));
		}
		else
		{
			// swap places with the first empty record 
			PID_TYPE oldEmptyPos = UDBRecord_pos(firstEmptyRecord);
			PID_TYPE newEmptyPos = oldEmptyPos + UDBRecord_totalSize(record);
			UDBRecords_firstEmptyRecordPos_(self->records, newEmptyPos);
			UDBRecord_moveToPos_(record, UDBRecord_pos(firstEmptyRecord));
			UDBRecord_setPos_(firstEmptyRecord, newEmptyPos);
			UDBRecord_saveHeader(firstEmptyRecord);
#ifdef DEBUG
			printf("move\n");
#endif
			UDBIndex_setPos_forPid_(self->index, newEmptyPos, UDBRecord_pid(firstEmptyRecord));
			UDBIndex_setPos_forPid_(self->index, oldEmptyPos, UDBRecord_pid(record));
		}
		
		UDB_commitTransaction(self);
		
		return 1;
	}
	return 0;
}

void UDB_show(UDB *self)
{
	UDBRecord *record = UDBRecords_firstRecord(self->records);
	printf("UDB Records:\n");
	
	while (record)
	{	
		UDBRecord_show(record);
		record = UDBRecords_nextRecord(self->records);
	}
}

void UDB_showIndex(UDB *self)
{
	UDBIndex_show(self->index);
}

