#include <stdlib.h>
#include "journal.h"
#include "sdk/CVector.h"

struct j_info
{
	int id;
	Vault *vault;
};

Journal::Journal(const char *file)
{
	m_File.assign(file);
	m_Fp = NULL;
	m_LastId = 0;
}

#define rd(v,s) if (fread(&v, sizeof(s), 1, m_Fp) != 1) { \
	fclose(m_Fp); \
	m_Fp = NULL; \
	goto _error; }
#define rds(v,l) if (fread(v, sizeof(char), l, m_Fp) != l) { \
	fclose(m_Fp); \
	m_Fp = NULL; \
	goto _error; } else { \
	v[l] = '\0'; }

bool Journal::Replay(size_t &files, size_t &ops)
{
	m_Fp = fopen(m_File.c_str(), "rb");

	files = 0;
	ops = 0;

	if (!m_Fp)
		return false;

	//this must come before the first jump...
	CVector<j_info *> table;
	uint32_t magic;

	rd(magic, uint32_t);
	if (magic != JOURNAL_MAGIC)
		return false;

	j_info *j;
	uint8_t op, klen;
	uint16_t vlen;
	uint32_t id;
	size_t i;
	char *key=NULL, *val=NULL;
	while (!feof(m_Fp))
	{
		if (fread(&op, sizeof(uint8_t), 1, m_Fp) != 1)
		{
			if (feof(m_Fp))
				break;
			else
				goto _error;
		}
		switch (op)
		{
		case Journal_Nop:
			{
				break;
			}
		case Journal_Name:
			{
				rd(id, uint32_t);
				rd(klen, uint8_t);
				key = new char[klen+1];
				rds(key, klen);
				j = new j_info;
				j->id = id;
				j->vault = new Vault(key);
				j->vault->ReadFromFile();
				table.push_back(j);
				files++;
				delete [] key;
				key = NULL;
				break;
			}
		case Journal_Store:
			{
				//Stores key/val (id,time,klen,vlen,[],[])
				uint32_t stamp;
				rd(id, uint32_t);
				rd(stamp, uint32_t);
				rd(klen, uint8_t);
				rd(vlen, uint16_t);
				key = new char[klen+1];
				val = new char[vlen+1];
				rds(key, klen);
				rds(val, vlen);
				for (i=0; i<table.size(); i++)
				{
					if (table.at(i)->id == id)
					{
						table.at(i)->vault->Store(key, val, (time_t)stamp);
						break;
					}
				}
				delete [] key;
				delete [] val;
				key = NULL;
				val = NULL;
				break;
			}
		case Journal_Erase:
			{
				//Erases key (id,klen,[])
				rd(id, uint32_t);
				rd(klen, uint8_t);
				key = new char[klen+1];
				rds(key, klen);
				for (i=0; i<table.size(); i++)
				{
					if (table.at(i)->id == id)
					{
						table.at(i)->vault->EraseKey(key);
						break;
					}
				}
				delete [] key;
				key = NULL;
				break;
			}
		case Journal_Clear:
			{
				//Clears (id)
				rd(id, uint32_t);
				for (i=0; i<table.size(); i++)
				{
					if (table.at(i)->id == id)
					{
						table.at(i)->vault->Clear();
						break;
					}
				}
				break;
			}
		case Journal_Prune:
			{
				//Prunes (id,t1,t2,all)
				rd(id, uint32_t);
				uint32_t begin, end;
				uint8_t all;
				rd(begin, uint32_t);
				rd(end, uint32_t);
				rd(all, uint8_t);
				for (i=0; i<table.size(); i++)
				{
					if (table.at(i)->id == id)
					{
						table.at(i)->vault->Prune((time_t)begin, (time_t)end, all?true:false);
						break;
					}
				}
				break;
			}
		default:
			{
				goto _error;
			}
		} //end while
		ops++;
	}

	for (uint32_t i=0; i<table.size(); i++)
	{
		j = table.at(i);
		j->vault->WriteToFile();
		delete j->vault;
		delete j;
	}
    
	fclose(m_Fp);

	return true;
_error:
	for (uint32_t i=0; i<table.size(); i++)
	{
		j = table.at(i);
		j->vault->WriteToFile();
		delete j->vault;
		delete j;
	}
	if (key)
	{
		delete [] key;
		key = NULL;
	}
	if (val)
	{
		delete [] val;
		val = NULL;
	}
	return false;
}

void Journal::ClearJournal()
{
	m_Fp = fopen(m_File.c_str(), "wb");

	if (m_Fp)
	{
		fclose(m_Fp);
		m_Fp = NULL;
	}
}

bool Journal::StartJournal()
{
	m_Fp = fopen(m_File.c_str(), "wb");

	if (!m_Fp)
		return false;

	uint32_t magic = JOURNAL_MAGIC;

	fwrite(&magic, sizeof(uint32_t), 1, m_Fp);

	return true;
}

void Journal::EndJournal()
{
	if (m_Fp)
	{
		fclose(m_Fp);
		m_Fp = NULL;
	}
}

//Stores key/val (id,time,klen,vlen,[],[]
void Journal::Store(const char *name, const char *key, const char *val, time_t stamp)
{
	uint32_t time32 = (uint32_t)stamp;
	uint16_t vlen = (uint16_t)strlen(val);
	uint8_t klen = (uint8_t)strlen(key);

	BeginOp(name, Journal_Store);
		WriteInt(time32);
		WriteByte(klen);
		WriteShort(vlen);
		WriteString(key);
		WriteString(val);
	EndOp();
}

//Erases key (id,klen,[])
void Journal::Erase(const char *name, const char *key)
{
	uint8_t klen = (uint8_t)strlen(key);

	BeginOp(name, Journal_Erase);
		WriteByte(klen);
		WriteString(key);
	EndOp();
}

//Clears (id)
void Journal::Clear(const char *name)
{
	BeginOp(name, Journal_Clear);
	EndOp();
}

//Prunes (id,t1,t2,all)
void Journal::Prune(const char *name, time_t begin, time_t end, bool all)
{
	uint32_t begin32 = (uint32_t)begin;
	uint32_t end32 = (uint32_t)end;
	uint8_t all8 = (uint8_t)all;

	BeginOp(name, Journal_Prune);
		WriteInt(begin32);
		WriteInt(end32);
		WriteByte(all8);
	EndOp();
}

void Journal::BeginOp(const char *name, JournalOp jop)
{
	uint32_t id;

	if (!m_Names.KeyExists(name))
	{
		char name_buf[12];
		id = ++m_LastId;
		sprintf(name_buf, "%d", id);
		m_Names.Store(name, name_buf, false);
		WriteByte(Journal_Name);
		WriteInt(id);
		WriteByte(strlen(name));
		WriteString(name);
	} else {
		id = atoi(m_Names.Retrieve(name)->val.c_str());
	}

	WriteByte(jop);
	WriteInt(id);
}

void Journal::WriteByte(uint8_t num)
{
	fwrite(&num, sizeof(uint8_t), 1, m_Fp);
}

void Journal::WriteShort(uint16_t num)
{
	fwrite(&num, sizeof(uint16_t), 1, m_Fp);
}

void Journal::WriteInt(uint32_t num)
{
	fwrite(&num, sizeof(uint32_t), 1, m_Fp);
}

void Journal::WriteString(const char *str)
{
	fwrite(str, sizeof(char), strlen(str), m_Fp);
}

size_t Journal::EndOp()
{
	return 1;
}