#include "scenefile.h"
#include <stdint.h>
#include "sys_log.h"
#include "platform.h"
#include <stdlib.h>
#include <string.h>

#define magic (0x53577279ul)
#define version (0x00000000ul)
#define truncate_cutoff (24)
#define minimal_expand (2048)
#define pos_sstream (8)
#define pos_dstream (16)
#define pos_tiles (24)
#define pos_firstfree (32)

static int copytemplate(yrFile* dst); //copy the template scene file contents to the new file

/********
* Structs
*********/
struct fileblock
{
	int64_t size;
	int64_t next;
};

struct tiledir
{
	int64_t ptr[256];
};

struct _yrtag_SceneFile
{
	yrFile	file;
	yrFile	file_tile;
	char*	fname;
	int		readonly;
	yrLock	block_lock;
	yrLock	tile_lock;
	
	struct {
		uint32_t last;
		struct tiledir dir8;
		struct tiledir dir16;
		struct tiledir dir24;
		struct tiledir dir32;
		int64_t add8;
		int64_t add16;
		int64_t add24;
		int64_t add32;
	}
	lookup;

	struct {
		int64_t	sstream;
		int64_t	dstream;
		int64_t	tiles;
		int64_t	firstfree;
	} off;
};

struct _yrtag_WriteHandle
{
	yrSceneFile* parent;
	yrFile file;
	int64_t base;
	int64_t streampos;
	int64_t block;
	int64_t blocklen;
	int64_t blockpos;
	size_t prewrite;
};

struct _yrtag_ReadHandle
{
	yrSceneFile* parent;
	yrFile file;
	struct fileblock block;
	int64_t blockpos;
	int64_t streamlen;
	int64_t streampos;
};

/***************************************
* Block management function declarations
****************************************/
int64_t	alloc_block(	yrSceneFile* sf, int64_t* inout_size, int64_t prev_block);
void	free_block(		yrSceneFile* sf, int64_t block);
void	free_stream(	yrSceneFile* sf, int64_t base);
int		merge_block(	yrSceneFile* sf, int64_t block, int64_t append);
void	truncate_block(	yrSceneFile* sf, int64_t block, int64_t fill);

/*****************
* Public functions
******************/
int yrSceneFile_isvalid(const char* fname)
{
	size_t rw;
	uint32_t checkmagic;
	uint32_t checkversion;
	yrFile f = yrFile_open(fname, yrF_read);
	if(!f) return -1;
	rw = yrFile_read(f, 4, &checkmagic);	if(rw != 4) { yrFile_close(f); return -2; }
	rw = yrFile_read(f, 4, &checkversion);	if(rw != 4) { yrFile_close(f); return -2; } 
	yrFile_close(f);
	if(checkmagic == magic);				else { return -3; }
	if(checkversion == version);			else { return -4; }
	return 0;
}

yrSceneFile* yrSceneFile_open(const char* fname, int overwrite)
{
	uint32_t checkmagic;
	uint32_t checkversion;
	size_t rw = 0;
	int newfile = 0;

	//alloc
	yrSceneFile* sf = calloc(1, sizeof(yrSceneFile));
	if(!sf) { yrLogAlert(0, 0xFF8080FF, 5000000, "Out of memory while opening %s.", fname); goto onerror; }
	sf->fname = _strdup(fname);
	if(!sf->fname) { yrLogAlert(0, 0xFF8080FF, 5000000, "Out of memory while opening %s.", fname); goto onerror; }

	//open(r) and check magic + version
	if(!overwrite) {
		if(yr_path_exists(fname)) {
			sf->file = yrFile_open(fname, yrF_read);
			if(!sf->file) {
				yrLogAlert(0, 0xFF8080FF, 5000000, "Could not open %s, another process may be using it.", fname);
				goto onerror;
			}
		}
		else newfile = 1;
	} else {
		newfile = 1;
	}
	
	if(!newfile) {
		rw = yrFile_read(sf->file, 4, &checkmagic);		if(rw != 4) { yrLogAlert(0, 0xFF8080FF, 5000000, "Error while reading file header (%s).", fname); goto onerror;}
		rw = yrFile_read(sf->file, 4, &checkversion);	if(rw != 4) { yrLogAlert(0, 0xFF8080FF, 5000000, "Error while reading file header (%s).", fname); goto onerror;}
		if(checkmagic == magic);					else { yrLogAlert(0, 0xFF8080FF, 5000000, "%s is not a valid scene file.", fname); goto onerror;}
		if(checkversion == version);				else { yrLogAlert(0, 0xFF8080FF, 5000000, "%s was created by an older version of this software and needs to be upgraded.", fname); goto onerror;}
		yrFile_close(sf->file);
		sf->file = NULL;
	}

	//open(w)
	if(newfile) {
		sf->file = yrFile_open(fname, yrF_read | yrF_write | yrF_create);
		if(!sf->file) {
			yrLogAlert(0, 0xFF8080FF, 5000000, "Could not create file (%s).", fname);
			goto onerror;
		} else {
			int err = copytemplate(sf->file);
			if(err) {
				//template copy failed, write an empty file
				checkmagic = magic;
				checkversion = version;
				struct fileblock streamblock = {0};
				size_t blocksize = sizeof(struct fileblock);

				uint64_t sstream = 4 + 4 + 8 + 8 + 8 + 8; //magic, version, 4 x ptr
				uint64_t dstream = sstream + blocksize + 65*8; //sstream length
				uint64_t sstreamdata[65] = {509, 0}; //bg idmap and no bgs
				uint64_t dstreamdata[29] = {224,   80, 80, 96, 196, 196, 80, 80, 80, 80, 80,   0}; //sysptrs + zero coord offset + zero quadcount + zero insertcount + zero id + empty quadrecord + 7 colors
				dstreamdata[25] = 0xFF10101000000000ull;
				dstreamdata[26] = 0xFF101010FFFFFFFFull;
				dstreamdata[27] = 0xFF10C010FFC01010ull;
				dstreamdata[28] = 0xFFC0C0C0FF1010C0ull;
				uint64_t tiles = dstream + blocksize + sizeof(dstreamdata);
				struct tiledir tiledata = {0};
				size_t tilesize = sizeof(struct tiledir);
				uint64_t firstfree = 0;


				rw = yrFile_write(sf->file, 4, &checkmagic);			if(rw != 4) goto createerror;
				rw = yrFile_write(sf->file, 4, &checkversion);			if(rw != 4) goto createerror;
				rw = yrFile_write(sf->file, 8, &sstream);				if(rw != 8) goto createerror;
				rw = yrFile_write(sf->file, 8, &dstream);				if(rw != 8) goto createerror;
				rw = yrFile_write(sf->file, 8, &tiles);					if(rw != 8) goto createerror;
				rw = yrFile_write(sf->file, 8, &firstfree);				if(rw != 8) goto createerror;

				streamblock.size = 65*8;
				rw = yrFile_write(sf->file, blocksize, &streamblock);	if(rw != blocksize) goto createerror;
				rw = yrFile_write(sf->file, 65*8, sstreamdata);			if(rw != 65*8) goto createerror;

				streamblock.size = 29*8;
				rw = yrFile_write(sf->file, blocksize, &streamblock);	if(rw != blocksize) goto createerror;
				rw = yrFile_write(sf->file, 29*8, dstreamdata);			if(rw != 29*8) goto createerror;

				streamblock.size = tilesize;
				rw = yrFile_write(sf->file, blocksize, &streamblock);	if(rw != blocksize) goto createerror;
				rw = yrFile_write(sf->file, tilesize, &tiledata);		if(rw != tilesize) goto createerror;
			}

			//open a second handle for tile management
			sf->file_tile = yrFile_open(fname, yrF_read | yrF_write);
			if(!sf->file_tile) {
				yrLog(0, "Failed to open second handle.", fname);
				goto createerror;
			}

			goto createfinish;
createerror:
			yrLogAlert(0, 0xFF8080FF, 5000000, "Error while creating file header (%s).", fname);
			yrFile_close(sf->file);
			sf->file = NULL;
			yr_delete_file(fname);
			goto onerror;
createfinish: ;
		}
	}
	else {
		sf->file = yrFile_open(fname, yrF_read | yrF_write);
		if(!sf->file) {
			//fallback to read-only
			sf->file = yrFile_open(fname, yrF_read);
			if(sf->file) {
				sf->readonly = 1;
				yrLogAlert(0, 0xFFFFFFFF, 5000000, "This scene (%s) could only be opened in read-only mode, changes will not be saved.", fname);
			}
			else {
				yrLogAlert(0, 0xFF8080FF, 5000000, "Could not open %s, another process may be using it.", fname);
				goto onerror;
			}
		}
		sf->file_tile = yrFile_open(fname, yrF_read | (sf->readonly ? 0 : yrF_write));
		if(!sf->file) {
			yrLogAlert(0, 0xFF8080FF, 5000000, "Could not open a %s, another process may be using it.", fname);
			goto onerror;
		}
	}

	//get pointer to stream start and tile root table
	yrFile_seek(sf->file, pos_sstream, yrF_seekset);
	rw = yrFile_read(sf->file, 8, &sf->off.sstream);	if(rw != 8) { yrLogAlert(0, 0xFF8080FF, 5000000, "Error while reading file header (%s).", fname); goto onerror; }
	rw = yrFile_read(sf->file, 8, &sf->off.dstream);	if(rw != 8) { yrLogAlert(0, 0xFF8080FF, 5000000, "Error while reading file header (%s).", fname); goto onerror; }
	rw = yrFile_read(sf->file, 8, &sf->off.tiles);		if(rw != 8) { yrLogAlert(0, 0xFF8080FF, 5000000, "Error while reading file header (%s).", fname); goto onerror; }
	rw = yrFile_read(sf->file, 8, &sf->off.firstfree);	if(rw != 8) { yrLogAlert(0, 0xFF8080FF, 5000000, "Error while reading file header (%s).", fname); goto onerror; }

	//read the root table
	sf->lookup.add32 = sf->off.tiles + sizeof(struct fileblock);
	yrFile_seek(sf->file, sf->lookup.add32, yrF_seekset);
	rw = yrFile_read(sf->file, sizeof(struct tiledir), &sf->lookup.dir32);
	if(rw != sizeof(struct tiledir)) {
		yrLogAlert(0, 0xFF8080FF, 5000000, "Error while reading file index (%s).", fname);
		goto onerror;
	}

	//read the 00-entry in every tile dir
	sf->lookup.add24 = sf->lookup.dir32.ptr[0];
	if(sf->lookup.add24) {
		yrFile_seek(sf->file, sf->lookup.add24, yrF_seekset);
		rw = yrFile_read(sf->file, sizeof(struct tiledir), &sf->lookup.dir24);
		if(rw != sizeof(struct tiledir)) {
			yrLogAlert(0, 0xFF8080FF, 5000000, "Error while reading file index (%s).", fname);
			goto onerror;
		}
	}
	sf->lookup.add16 = sf->lookup.dir24.ptr[0];
	if(sf->lookup.add16) {
		yrFile_seek(sf->file, sf->lookup.add16, yrF_seekset);
		rw = yrFile_read(sf->file, sizeof(struct tiledir), &sf->lookup.dir16);
		if(rw != sizeof(struct tiledir)) {
			yrLogAlert(0, 0xFF8080FF, 5000000, "Error while reading file index (%s).", fname);
			goto onerror;
		}
	}
	sf->lookup.add8 = sf->lookup.dir16.ptr[0];
	if(sf->lookup.add8) {
		yrFile_seek(sf->file, sf->lookup.add8, yrF_seekset);
		rw = yrFile_read(sf->file, sizeof(struct tiledir), &sf->lookup.dir8);
		if(rw != sizeof(struct tiledir)) {
			yrLogAlert(0, 0xFF8080FF, 5000000, "Error while reading file index (%s).", fname);
			goto onerror;
		}
	}
	sf->lookup.last = 0;

	//locks
	sf->block_lock = yrLock_create();	if(!sf->block_lock){ yrLogAlert(0, 0xFF8080FF, 5000000, "OS error while opening file %s.", fname); goto onerror; }
	sf->tile_lock = yrLock_create();	if(!sf->tile_lock) { yrLogAlert(0, 0xFF8080FF, 5000000, "OS error while opening file %s.", fname); goto onerror; }

	return sf;
onerror:
	if(sf) {
		free(sf->fname);
		if(sf->file_tile) yrFile_close(sf->file_tile);
		if(sf->file) yrFile_close(sf->file);
	}
	free(sf);
	return NULL;
}

void yrSceneFile_close(yrSceneFile* sf)
{
	if(!sf) return;
	int errt = 0;
	int err = 0;
	if(sf->file_tile) errt = yrFile_close(sf->file_tile);
	if(sf->file) err = yrFile_close(sf->file);
	if(err || errt) yrLogAlert(0, 0xFF8080FF, 3000000, "Error while closing scene file %s.", sf->fname);
	free(sf->fname);
	free(sf);
}

static int copytemplate(yrFile* dst)
{
	//open template
	if(!yr_path_exists("data/vr/template.ysf")) {
		yrLog(0, "Scene template not found, will create an empty scene.");
		return -1;
	}
	yrFile* t = yrFile_open("data/vr/template.ysf", yrF_read);
	if(!t) {
		yrLog(0, "Could not read scene template, will create an empty scene.");
		return -1;
	}
	//get template data
	int64_t len = yrFile_seek(t, 0, yrF_seekend);
	yrFile_seek(t, 0, yrF_seekset);
	if(!len) {
		yrLog(0, "Invalid scene template, will create an empty scene.");
		yrFile_close(t);
		return -1;
	}
	void* data = malloc(len);
	if(!data) {
		yrLog(0, "Out of memory while copying scene template, will create an empty scene.");
		yrFile_close(t);
		return -1;
	}
	int64_t rw = yrFile_read(t, len, data);
	yrFile_close(t);
	if(rw != len) {
		yrLog(0, "Error while reading scene template, will create an empty scene.");
		free(data);
		return -1;
	}
	//write template data
	rw = yrFile_write(dst, len, data);
	free(data);
	if(rw != len) {
		yrLog(0, "Error while writing scene template, will create an empty scene.");
		yrFile_seek(dst, 0, yrF_seekset);
		return -1;
	}
	//done
	return 0;
}

yrWriteHandle* yrSFWrite_start(yrSceneFile* sf, size_t prewrite)
{
	if(!sf) return NULL;
	if(sf->readonly) return NULL;
	yrWriteHandle* out = malloc(sizeof(yrWriteHandle));
	if(!out) { yrLog(0, "Out of memory"); return NULL; }
	//get file handle
	out->parent = sf;
	out->file = yrFile_open(sf->fname, yrF_read | yrF_write);
	if(!out->file) {
		yrLog(0, "Failed to acquire handle to file %s", sf->fname);
		free(out);
		return NULL;
	}
	//get first block
	out->blocklen = 0;
	out->block = alloc_block(sf, &out->blocklen, 0);
	if(!out->block) {
		yrLog(0, "Failed to acquire block in file %s", sf->fname);
		free(out);
		return NULL;
	}
	//init vars
	out->base = out->block;
	out->streampos = 0;
	out->blockpos = 8; //reseved space for streamlen
	yrFile_seek(out->file, out->base + sizeof(struct fileblock) + out->blockpos, yrF_seekset);

	//prewrite
	out->prewrite = prewrite;
	if(prewrite) {
		const size_t scsize = 128;
		char scratch[128];
		while(prewrite > 0) {
			size_t to_write = (prewrite > scsize) ? scsize : prewrite;
			size_t rw = yrSFWrite_write(out, to_write, scratch);
			if(rw != to_write) {
				yrSFWrite_abort(out);
				return NULL;
			}
			prewrite -= rw;
		}
	}
	return out;
}

size_t yrSFWrite_write(yrWriteHandle* h, size_t len, void* buf)
{
	if(!h) return 0;
	size_t written = 0;
	while(len) {
		//write as much as possible to the current block
		int64_t blockleft = h->blocklen - h->blockpos;
		int64_t writable = ((int64_t) len > blockleft) ? blockleft : (int64_t) len;
		int64_t rw = yrFile_write(h->file, writable, (char*)buf + written);
		written += rw;
		h->blockpos += rw;
		h->streampos += rw;
		len -= rw;
		if(rw < writable) return written;
		if(len) {
			//allocate a new block for the rest
			int64_t newsize = 0;
			int64_t newblock = alloc_block(h->parent, &newsize, h->block);
			if(!newblock) {
				yrLog(0, "Failed to acquire block in file %s", h->parent->fname);
				return written;
			}
			if(merge_block(h->parent, h->block, newblock)) {
				//merge the blocks into one if they are right behind eachother
				h->blocklen += newsize + sizeof(struct fileblock);
			}
			else {
				h->block = newblock;
				h->blocklen = newsize;
				h->blockpos = 0;
				yrFile_seek(h->file, h->block + sizeof(struct fileblock), yrF_seekset);
			}
		}
	}
	return written;
}

int yrSFWrite_end(yrWriteHandle* h, void* prewrite_data)
{
	size_t rw = 0;																		if(!h) return -1;
	struct fileblock pwblock;

	//truncate current block
	truncate_block(h->parent, h->block, h->blockpos);

	//set the stream length at the start
	yrFile_seek(h->file, h->base, yrF_seekset);
	rw = yrFile_read(h->file, sizeof(struct fileblock), &pwblock);						if(rw != sizeof(struct fileblock)) {yrSFWrite_abort(h); return -1;}
	rw = yrFile_write(h->file, 8, &h->streampos);										if(rw != 8) {yrSFWrite_abort(h); return -1;}

	//write prewrite data
	if(prewrite_data) {
		size_t pwlen = h->prewrite;
		size_t written = 0;
		size_t blockleft = pwblock.size - 8;
		while(pwlen > 0) {
			//fill block
			size_t to_write = (pwlen > blockleft) ? blockleft : pwlen;
			rw = yrFile_write(h->file, to_write, (char*)prewrite_data + written);		if(rw != to_write) {yrSFWrite_abort(h); return -1;}
			pwlen -= rw;
			written += rw;
			//go to next block
			if(pwlen > 0) {																if(pwblock.next == 0) {yrLog(0,"Invalid stream"); yrSFWrite_abort(h); return -1;}
				yrFile_seek(h->file, pwblock.next, yrF_seekset);
				rw = yrFile_read(h->file, sizeof(struct fileblock), &pwblock);			if(rw != sizeof(struct fileblock)) {yrSFWrite_abort(h); return -1;}
				blockleft = pwblock.size;
			}
		}
	}

	//change the stream pointer to the stream we just wrote
	int64_t oldstream = h->parent->off.dstream;
	yrLog(2, "Acquiring block lock");
	yrLock_aqcuire(h->parent->block_lock);
	yrFile_seek(h->file, pos_dstream, yrF_seekset);
	rw = yrFile_write(h->file, 8, &h->base);
	yrLock_release(h->parent->block_lock);
	yrLog(2, "Released block lock");													if(rw != 8) {yrSFWrite_abort(h); return -1;}
	h->parent->off.dstream = h->base;

	//free all blocks in the old stream
	if(oldstream) free_stream(h->parent, oldstream);

	//delete handle
	yrFile_close(h->file);
	free(h);
	return 0;
}

int yrSFWrite_static_end(yrWriteHandle* h)
{
	if(!h) return -1;
	//truncate current block
	truncate_block(h->parent, h->block, h->blockpos);
	//set the stream length at the start
	yrFile_seek(h->file, h->base + sizeof(struct fileblock), yrF_seekset);
	size_t foo = yrFile_write(h->file, 8, &h->streampos);
	if(foo != 8) {yrSFWrite_abort(h); return -1;}
	//change the stream pointer to what we just wrote
	int64_t oldstream = h->parent->off.sstream;
	yrLog(2, "Acquiring block lock");
	yrLock_aqcuire(h->parent->block_lock);
	yrFile_seek(h->file, pos_sstream, yrF_seekset);
	foo = yrFile_write(h->file, 8, &h->base);
	yrLock_release(h->parent->block_lock);
	yrLog(2, "Released block lock");
	if(foo != 8) {yrSFWrite_abort(h); return -1;}
	h->parent->off.sstream = h->base;
	//free all blocks in the old stream
	if(oldstream) free_stream(h->parent, oldstream);
	//delete handle
	yrFile_close(h->file);
	free(h);
	return 0;
}

void yrSFWrite_abort(yrWriteHandle* h)
{
	if(!h) return;
	//free all blocks in this stream
	free_stream(h->parent, h->base);
	//delete handle
	yrFile_close(h->file);
	free(h);
}

int64_t yrSFWrite_getptr(yrWriteHandle* h)
{
	if(!h) return -1;
	return h->streampos;
}

size_t yrSFWrite_copy(yrWriteHandle* h, yrReadHandle* src, size_t len)
{
	//create scratchpad
	size_t written = 0;
	const size_t scsize = 1024;
	void* scratch = malloc(scsize);
	if(!scratch) {yrLog(0, "Out of memory"); return 0;}

	//pump data from ReadHandle to WriteHandle
	while(len) {
		size_t to_read = (len > scsize) ? scsize : len;
		size_t r, w;
		//read
		r = yrSFRead_read(src, to_read, scratch);
		if(r != to_read) { free(scratch); return written; }
		//write
		w = yrSFWrite_write(h, r, scratch);
		if(w != r) { free(scratch); return written + w; }
		//update
		written += w;
		len -= w;
	}
	return written;
}

yrReadHandle* yrSFRead_start(yrSceneFile* sf)
{
	if(!sf) return NULL;
	yrReadHandle* out = malloc(sizeof(yrReadHandle));
	if(!out) { yrLog(0, "Out of memory"); return NULL; }
	
	//get file handle
	out->parent = sf;
	out->file = yrFile_open(sf->fname, yrF_read);
	if(!out->file) {
		yrLog(0, "Failed to acquire handle to file %s", sf->fname);
		free(out);
		return NULL;
	}

	//prepare first block and read stream len
	int64_t stream = sf->off.dstream;
	yrFile_seek(out->file, stream, yrF_seekset);
	int64_t foo = yrFile_read(out->file, sizeof(struct fileblock), &out->block);
	if(foo != sizeof(struct fileblock)) { free(out); return NULL; }
	foo = yrFile_read(out->file, 8, &out->streamlen);
	if(foo != 8) { free(out); return NULL; }
	out->blockpos = 8;
	out->streampos = 0;

	return out;
}

yrReadHandle* yrSFRead_static_start(yrSceneFile* sf)
{
	if(!sf) return NULL;
	yrReadHandle* out = malloc(sizeof(yrReadHandle));
	if(!out) { yrLog(0, "Out of memory"); return NULL; }
	
	//get file handle
	out->parent = sf;
	out->file = yrFile_open(sf->fname, yrF_read);
	if(!out->file) {
		yrLog(0, "Failed to acquire handle to file %s", sf->fname);
		free(out);
		return NULL;
	}

	//prepare first block and read stream len
	int64_t stream = sf->off.sstream;
	yrFile_seek(out->file, stream, yrF_seekset);
	int64_t foo = yrFile_read(out->file, sizeof(struct fileblock), &out->block);
	if(foo != sizeof(struct fileblock)) { free(out); return NULL; }
	foo = yrFile_read(out->file, 8, &out->streamlen);
	if(foo != 8) { free(out); return NULL; }
	out->blockpos = 8;
	out->streampos = 0;

	return out;
}

size_t yrSFRead_read(yrReadHandle* h, size_t len, void* buf)
{
	if(!h) return 0;
	size_t read = 0;
	while(len) {
		//read as much as possible from the current block
		int64_t blockleft = h->block.size - h->blockpos;
		int64_t streamleft = h->streamlen - h->streampos;
		int64_t readable = ((int64_t)len > blockleft) ? blockleft : (int64_t) len;
		if(readable > streamleft) readable = streamleft;

		if(streamleft == 0 || len == 0) return read;
		int64_t rw = yrFile_read(h->file, readable, (char*)buf + read);
		read += rw;
		h->blockpos += rw;
		h->streampos += rw;
		len -= rw;
		if(rw < readable) return read;
		if(len) {
			//get the next block
			yrFile_seek(h->file, h->block.next, yrF_seekset);
			size_t foo = yrFile_read(h->file, sizeof(struct fileblock), &h->block);
			if(foo != sizeof(struct fileblock)) { h->block.size = 0; h->block.next = 0; return read; }
			h->blockpos = 0;
		}
	}
	return read;
}

void yrSFRead_end(yrReadHandle* h)
{
	if(!h) return;
	//delete handle
	yrFile_close(h->file);
	free(h);
}

int yrSFRead_seek(yrReadHandle* h, int64_t pos, int relative)
{
	//calc destination
	int64_t dst = pos;
	if(relative) dst += h->streampos;
	YR_ASSERT(dst >= 0);
	YR_ASSERT(dst <= h->streamlen);
	if(dst < 0)				{yrLog(0,"Invalid stream seek."); return -1;}
	if(dst > h->streamlen)	{yrLog(0,"Invalid stream seek."); return -1;}

	//rewind if needed
	int64_t diff = dst - h->streampos;
	if(diff < 0) {
		//within the same block
		if(h->blockpos >= -diff) {
			yrFile_seek(h->file, diff, yrF_seekcur);
			h->blockpos += diff;
			h->streampos += diff;
			return 0;
		//go to beginning of stream
		} else {
			yrFile_seek(h->file, h->parent->off.dstream, yrF_seekset); //TODO: what if we're seeking in the sstream?
			size_t rw = yrFile_read(h->file, sizeof(h->block), &h->block);
			if(rw != sizeof(h->block)) return -1;
			diff = dst;
			h->blockpos = 8;
			h->streampos = 0;
		}
	}

	//seek forward to destination
	while(1) {
		//in the current block
		if(diff <= h->block.size - h->blockpos) {
			yrFile_seek(h->file, diff, yrF_seekcur);
			h->blockpos += diff;
			h->streampos += diff;
			return 0;
		}
		//next block
		else {
			diff -= (h->block.size - h->blockpos);
			h->streampos += (h->block.size - h->blockpos);
			h->blockpos = 0;
			YR_ASSERT(h->block.next);
			if(!h->block.next) {yrLog(0,"Invalid stream."); return -1;}
			yrFile_seek(h->file, h->block.next, yrF_seekset);
			size_t rw = yrFile_read(h->file, sizeof(h->block), &h->block);
			if(rw != sizeof(h->block)) return -1;
		}
	}
}

//don't call without acquiring the tile lock first
static int64_t yrSFTile_lookup(yrSceneFile* sf, uint32_t tile)
{
	if(tile == 0xFFFFFFFFul) return 0;
	//1st table is always the same and is therefore already loaded
	//2nd table if not already loaded
	if(sf->lookup.last == 0xFFFFFFFFul || sf->lookup.add24 == 0 ||
	   (tile >> 24) != (sf->lookup.last >> 24))
	{
		sf->lookup.add24 = sf->lookup.dir32.ptr[tile>>24];
		sf->lookup.add16 = 0;
		sf->lookup.add8 = 0;
		if(!sf->lookup.add24) {
			sf->lookup.last = tile;
			return 0;
		}
		yrFile_seek(sf->file_tile, sf->lookup.add24, yrF_seekset);
		size_t rw = yrFile_read(sf->file_tile, sizeof(struct tiledir), &sf->lookup.dir24);
		if(rw != sizeof(struct tiledir)) {
			yrLog(0, "Tile lookup failure for tile %x", tile);
			sf->lookup.last = 0xFFFFFFFFul;
			return -1;
		}
	}
	//3rd table if not already loaded
	if(sf->lookup.last == 0xFFFFFFFFul || sf->lookup.add16 == 0 ||
	   (tile >> 16) != (sf->lookup.last >> 16))
	{
		sf->lookup.add16 = sf->lookup.dir24.ptr[((tile >> 16) & 0xFF)];
		sf->lookup.add8 = 0;
		if(!sf->lookup.add16) {
			sf->lookup.last = tile;
			return 0;
		}
		yrFile_seek(sf->file_tile, sf->lookup.add16, yrF_seekset);
		size_t rw = yrFile_read(sf->file_tile, sizeof(struct tiledir), &sf->lookup.dir16);
		if(rw != sizeof(struct tiledir)) {
			yrLog(0, "Tile lookup failure for tile %x", tile);
			sf->lookup.last = 0xFFFFFFFFul;
			return -1;
		}
	}
	//4th table if not already loaded
	if(sf->lookup.last == 0xFFFFFFFFul || sf->lookup.add8 == 0 ||
	   (tile >> 8) != (sf->lookup.last >> 8))
	{
		sf->lookup.add8 = sf->lookup.dir16.ptr[((tile >> 8) & 0xFF)];
		if(!sf->lookup.add8) {
			sf->lookup.last = tile;
			return 0;
		}
		yrFile_seek(sf->file_tile, sf->lookup.add8, yrF_seekset);
		size_t rw = yrFile_read(sf->file_tile, sizeof(struct tiledir), &sf->lookup.dir8);
		if(rw != sizeof(struct tiledir)) {
			yrLog(0, "Tile lookup failure for tile %x", tile);
			sf->lookup.last = 0xFFFFFFFFul;
			return -1;
		}
	}
	//lookup the tile itself
	sf->lookup.last = tile;
	return sf->lookup.dir8.ptr[tile & 0xFF];
}

int yrSFTile_exists(yrSceneFile* sf, uint32_t tile)
{
	if(!sf) return -1;
	yrLog(2, "Acquiring tile lock");
	yrLock_aqcuire(sf->tile_lock);
	int out = yrSFTile_lookup(sf, tile) != 0;
	yrLock_release(sf->tile_lock);
	yrLog(2, "Released tile lock");
	return out;
}

static int yrSFTile_delete_nolock(yrSceneFile* sf, uint32_t tile)
{
	if(!sf) return -1;

	//lookup
	int64_t add = yrSFTile_lookup(sf, tile);
	if(add <= 0) {
		return 0;
	}

	//actual delete
	free_block(sf, add);

	//update tiledir
	size_t idx = tile & 0xFF;
	sf->lookup.dir8.ptr[idx] = 0;
	yrFile_seek(sf->file_tile, sf->lookup.add8 + idx*8, yrF_seekset);
	size_t rw = yrFile_write(sf->file_tile, 8, &sf->lookup.dir8.ptr[idx]);
	if(rw != 8) {
		yrLog(0, "Error while writing tiledir update (%x)", tile);
		return -1;
	}

	//tiledir cleanup

	//cleanup lowest dir
	int empty = 1;
	for(size_t i = 0; empty && i < 256; ++i)
		if(sf->lookup.dir8.ptr[i])
			empty = 0;
	if(empty) {
		free_block(sf, sf->lookup.add8 - sizeof(struct fileblock));
		sf->lookup.add8 = 0;
		sf->lookup.dir16.ptr[(tile >> 8) & 0xFF] = 0;

		//this may propagate to the next level
		empty = 1;
		for(size_t i = 0; empty && i < 256; ++i)
			if(sf->lookup.dir16.ptr[i])
				empty = 0;

		if(empty) {
			free_block(sf, sf->lookup.add16 - sizeof(struct fileblock));
			sf->lookup.add16 = 0;
			sf->lookup.dir24.ptr[(tile >> 16) & 0xFF] = 0;

			//this may again propagate to the next level
			empty = 1;
			for(size_t i = 0; empty && i < 256; ++i)
				if(sf->lookup.dir24.ptr[i])
					empty = 0;

			if(empty) {
				free_block(sf, sf->lookup.add24 - sizeof(struct fileblock));
				sf->lookup.add24 = 0;
				sf->lookup.dir32.ptr[tile >> 24] = 0;
				//no more propagation, never delete the root table
				//write change to the root table
				yrFile_seek(sf->file_tile, sf->lookup.add32 + 8*(tile >> 24), yrF_seekset);
				size_t rw = yrFile_write(sf->file_tile, 8, &sf->lookup.add24);
				if(rw != 8) {
					sf->lookup.last = 0xFFFFFFFFul;
					yrLog(0, "Error while writing tiledir update (%x)", tile);
					return -1;
				}
			}
			else {
				//write change to this table
				yrFile_seek(sf->file_tile, sf->lookup.add24 + 8*((tile >> 16) & 0xFF), yrF_seekset);
				size_t rw = yrFile_write(sf->file_tile, 8, &sf->lookup.add16);
				if(rw != 8) {
					sf->lookup.last = 0xFFFFFFFFul;
					yrLog(0, "Error while writing tiledir update (%x)", tile);
					return -1;
				}
			}
		}
		else {
			//write change to this table
			yrFile_seek(sf->file_tile, sf->lookup.add16 + 8*((tile >> 8) & 0xFF), yrF_seekset);
			size_t rw = yrFile_write(sf->file_tile, 8, &sf->lookup.add8);
			if(rw != 8) {
				sf->lookup.last = 0xFFFFFFFFul;
				yrLog(0, "Error while writing tiledir update (%x)", tile);
				return -1;
			}
		}
	}
	return 0;
}

int yrSFTile_delete(yrSceneFile* sf, uint32_t tile)
{
	yrLog(2, "Acquiring tile lock");
	yrLock_aqcuire(sf->tile_lock);
	int out = yrSFTile_delete_nolock(sf, tile);
	yrLock_release(sf->tile_lock);
	yrLog(2, "Released tile lock");
	return out;
}

int yrSFTile_read(yrSceneFile* sf, uint32_t tile, size_t* len, void** buf)
{
	if(!sf) return -1;
	*len = 0;
	size_t rw;
	struct fileblock block;
	yrLog(2, "Acquiring tile lock");
	yrLock_aqcuire(sf->tile_lock);

	//lookup
	int64_t add = yrSFTile_lookup(sf, tile);
	if(add <= 0) {
		yrLock_release(sf->tile_lock);
		yrLog(2, "Released tile lock");
		return -1;
	}
	
	// alloc and read
	yrFile_seek(sf->file_tile, add, yrF_seekset);
	rw = yrFile_read(sf->file_tile, sizeof(struct fileblock), &block);
	if(rw != sizeof(struct fileblock)) {
		yrLock_release(sf->tile_lock);
		yrLog(2, "Released tile lock");
		return -1;
	}
	*len = (size_t) block.size;
	*buf = malloc(*len);
	if(!*buf) {
		yrLog(0, "Out of memory");
		yrLock_release(sf->tile_lock);
		yrLog(2, "Released tile lock");
		return -1;
	}
	rw = yrFile_read(sf->file_tile, (int64_t) *len, *buf);
	if(rw != *len) {
		free(*buf);
		yrLock_release(sf->tile_lock);
		yrLog(2, "Released tile lock");
		return -1;
	}
	yrLock_release(sf->tile_lock);
	yrLog(2, "Released tile lock");
	return 0;
}

int yrSFTile_write(yrSceneFile* sf, uint32_t tile, size_t len, void* buf)
{
	if(!sf) return -1;
	if(tile == 0xFFFFFFFFul) return -1;
	if(sf->readonly) return 0; //pretend this worked because we already warned the user about read-only mode
	yrLog(2, "Acquiring tile lock");
	yrLock_aqcuire(sf->tile_lock);

	//allocate space in the file
	int64_t blocksize = (int64_t) len;
	int64_t block = alloc_block(sf, &blocksize, 0);
	if(!block)  {
		yrLock_release(sf->tile_lock);
		yrLog(2, "Released tile lock");
		yrLog(0, "Failed to acquire block in file %s", sf->fname);
		return -1;
	}

	//write new data
	yrFile_seek(sf->file_tile, block + sizeof(struct fileblock), yrF_seekset);
	size_t rw = yrFile_write(sf->file_tile, (int64_t) len, buf);
	if(rw != len) {
		yrLock_release(sf->tile_lock);
		yrLog(2, "Released tile lock");
		free_block(sf, block);
		return -1;
	}

	//delete old entry if it exists
	yrSFTile_delete_nolock(sf, tile);
	
	//fill out the tile dir entries where required

	//1st table is always the same and is therefore already created
	//create 2nd table if necessary
	size_t idx = tile >> 24;
	if(sf->lookup.dir32.ptr[idx] == 0)
	{
		//alloc block for new table
		int64_t tablesize = sizeof(struct tiledir);
		int64_t table = alloc_block(sf, &tablesize, 0);
		if(!table)  {
			sf->lookup.last = 0xFFFFFFFFul;
			yrLog(0, "Failed to acquire block in file %s", sf->fname);
			yrLock_release(sf->tile_lock);
			yrLog(2, "Released tile lock");
			return -1;
		}
		memset(&sf->lookup.dir24, 0, sizeof(struct tiledir));
		sf->lookup.add24 = table + sizeof(struct fileblock);
		sf->lookup.add16 = 0;
		sf->lookup.add8 = 0;
		//write new table
		yrFile_seek(sf->file_tile, sf->lookup.add24, yrF_seekset);
		size_t rw = yrFile_write(sf->file_tile, sizeof(struct tiledir), &sf->lookup.dir24);
		if(rw != sizeof(struct tiledir)) {
			sf->lookup.last = 0xFFFFFFFFul;
			yrLog(0, "Error while writing tiledir update (%x)", tile);
			free_block(sf, table);
			yrLock_release(sf->tile_lock);
			yrLog(2, "Released tile lock");
			return -1;
		}
		//write in parent table
		sf->lookup.dir32.ptr[idx] = sf->lookup.add24;
		yrFile_seek(sf->file_tile, sf->lookup.add32 + 8*idx, yrF_seekset);
		rw = yrFile_write(sf->file_tile, 8, &sf->lookup.add24);
		if(rw != 8) {
			sf->lookup.last = 0xFFFFFFFFul;
			yrLog(0, "Error while writing tiledir update (%x)", tile);
			free_block(sf, table);
			yrLock_release(sf->tile_lock);
			yrLog(2, "Released tile lock");
			return -1;
		}
	}
	//create 3rd table if necessary
	idx = (tile >> 16) & 0xFF;
	if(sf->lookup.dir24.ptr[idx] == 0)
	{
		//alloc block for new table
		int64_t tablesize = sizeof(struct tiledir);
		int64_t table = alloc_block(sf, &tablesize, 0);
		if(!table)  {
			sf->lookup.last = 0xFFFFFFFFul;
			yrLog(0, "Failed to acquire block in file %s", sf->fname);
			yrLock_release(sf->tile_lock);
			yrLog(2, "Released tile lock");
			return -1;
		}
		memset(&sf->lookup.dir16, 0, sizeof(struct tiledir));
		sf->lookup.add16 = table + sizeof(struct fileblock);
		sf->lookup.add8 = 0;
		//write new table
		yrFile_seek(sf->file_tile, sf->lookup.add16, yrF_seekset);
		size_t rw = yrFile_write(sf->file_tile, sizeof(struct tiledir), &sf->lookup.dir16);
		if(rw != sizeof(struct tiledir)) {
			sf->lookup.last = 0xFFFFFFFFul;
			yrLog(0, "Error while writing tiledir update (%x)", tile);
			free_block(sf, table);
			yrLock_release(sf->tile_lock);
			yrLog(2, "Released tile lock");
			return -1;
		}
		//write in parent table
		sf->lookup.dir24.ptr[idx] = sf->lookup.add16;
		yrFile_seek(sf->file_tile, sf->lookup.add24 + 8*idx, yrF_seekset);
		rw = yrFile_write(sf->file_tile, 8, &sf->lookup.add16);
		if(rw != 8) {
			sf->lookup.last = 0xFFFFFFFFul;
			yrLog(0, "Error while writing tiledir update (%x)", tile);
			free_block(sf, table);
			yrLock_release(sf->tile_lock);
			yrLog(2, "Released tile lock");
			return -1;
		}
	}
	//create 4th table if necessary
	idx = (tile >> 8) & 0xFF;
	if(sf->lookup.dir16.ptr[idx] == 0)
	{
		//alloc block for new table
		int64_t tablesize = sizeof(struct tiledir);
		int64_t table = alloc_block(sf, &tablesize, 0);
		if(!table)  {
			sf->lookup.last = 0xFFFFFFFFul;
			yrLog(0, "Failed to acquire block in file %s", sf->fname);
			yrLock_release(sf->tile_lock);
			yrLog(2, "Released tile lock");
			return -1;
		}
		memset(&sf->lookup.dir8, 0, sizeof(struct tiledir));
		sf->lookup.add8 = table + sizeof(struct fileblock);
		//write new table
		yrFile_seek(sf->file_tile, sf->lookup.add8, yrF_seekset);
		size_t rw = yrFile_write(sf->file_tile, sizeof(struct tiledir), &sf->lookup.dir8);
		if(rw != sizeof(struct tiledir)) {
			sf->lookup.last = 0xFFFFFFFFul;
			yrLog(0, "Error while writing tiledir update (%x)", tile);
			free_block(sf, table);
			yrLock_release(sf->tile_lock);
			yrLog(2, "Released tile lock");
			return -1;
		}
		//write in parent table
		sf->lookup.dir16.ptr[idx] = sf->lookup.add8;
		yrFile_seek(sf->file_tile, sf->lookup.add16 + 8*idx, yrF_seekset);
		rw = yrFile_write(sf->file_tile, 8, &sf->lookup.add8);
		if(rw != 8) {
			sf->lookup.last = 0xFFFFFFFFul;
			yrLog(0, "Error while writing tiledir update (%x)", tile);
			free_block(sf, table);
			yrLock_release(sf->tile_lock);
			yrLog(2, "Released tile lock");
			return -1;
		}
	}
	//write new tile to 4th table
	idx = tile & 0xFF;
	sf->lookup.dir8.ptr[idx] = block;
	yrFile_seek(sf->file_tile, sf->lookup.add8 + 8*idx, yrF_seekset);
	rw = yrFile_write(sf->file_tile, 8, &block);
	if(rw != 8) {
		sf->lookup.last = 0xFFFFFFFFul;
		yrLog(0, "Error while writing tiledir update (%x)", tile);
		yrLock_release(sf->tile_lock);
		yrLog(2, "Released tile lock");
		return -1;
	}
	sf->lookup.last = tile;
	yrLock_release(sf->tile_lock);
	yrLog(2, "Released tile lock");
	return 0;
}

/***************************
* Block management functions
****************************/
int64_t	alloc_block(yrSceneFile* sf, int64_t* inout_size, int64_t prev_block)
{
	if(!sf) return 0;
	if(*inout_size < 0) return 0;
	yrLog(2, "Acquiring block lock");
	yrLock_aqcuire(sf->block_lock);
	struct fileblock fb;
	const int64_t fbsize = sizeof(struct fileblock);
	size_t rw;
	int64_t out = 0;

	//check the first free block
	if(sf->off.firstfree) {
		yrFile_seek(sf->file, sf->off.firstfree, yrF_seekset);
		rw = yrFile_read(sf->file, fbsize, &fb);
		if(rw != fbsize) {
			yrLock_release(sf->block_lock);
			yrLog(2, "Released block lock");
			return 0;
		}
		if(!*inout_size || fb.size >= *inout_size) {
			//update the free-list and use this block
			out = sf->off.firstfree;
			sf->off.firstfree = fb.next;
			yrFile_seek(sf->file, pos_firstfree, yrF_seekset);
			rw = yrFile_write(sf->file, 8, &fb.next);
			if(rw != 8) {
				yrLog(0, "Error while writing fileheader update.");
				yrLock_release(sf->block_lock);
				yrLog(2, "Released block lock");
				return 0;
			}
		}
	
		//check the rest of the free list
		if(!out) {
			int64_t lastblock = sf->off.firstfree;
			int64_t nextblock = fb.next;
			while(nextblock != 0)
			{
				yrFile_seek(sf->file, nextblock, yrF_seekset);
				rw = yrFile_read(sf->file, fbsize, &fb);
				if(rw != fbsize) {
					yrLock_release(sf->block_lock);
					yrLog(2, "Released block lock");
					return 0;
				}
				if(fb.size >= *inout_size) { //if *inout_size were 0 we would have gotten the first free block already
					//use this block
					out = nextblock;
					int64_t pos = lastblock + (int64_t)&(((struct fileblock*)NULL)->next);
					yrFile_seek(sf->file, pos, yrF_seekset);
					rw = yrFile_write(sf->file, 8, &fb.next);
					if(rw != 8) {
						yrLog(0, "Error while writing blockheader update.");
						yrLock_release(sf->block_lock);
						yrLog(2, "Released block lock");
						return 0;
					}
					break;
				}
				lastblock = nextblock;
				nextblock = fb.next;
			}
		}
	}

	//create a new block if necessary
	if(!out) {
		out = yrFile_seek(sf->file, 0, yrF_seekend);
		//expand the file by writing zeroes, exactly the thing SetFileValidData() was made for except using sfvd of course requires special privileges
		int64_t towrite = *inout_size + fbsize;
		if(towrite < minimal_expand) towrite = minimal_expand;
		int64_t written = 0;
		unsigned char empty[minimal_expand] = {0}; //set to zero to avoid exposing part of the stack (at least that's what i think could happen)
		while(written != towrite) {
			int64_t foo = ((towrite - written) > minimal_expand) ? minimal_expand : (towrite - written);
			rw = yrFile_write(sf->file, foo, empty);
			if(rw != foo) {
				//couldn't expand, try to recover the partial expansion
				yrLog(0, "Error while expanding file, attempting to prepend partial to freelist.");
				fb.next = sf->off.firstfree;
				fb.size = written + rw - fbsize;
				if(fb.size < 0) {
					yrLog(0, "Expansion too small to recover");
					yrLock_release(sf->block_lock);
					yrLog(2, "Released block lock");
					return 0;
				}
				yrFile_seek(sf->file, out, yrF_seekset);
				rw = yrFile_write(sf->file, fbsize, &fb);
				if(rw != fbsize) {
					yrLog(0, "Expansion recovery failed: failed to write blockheader");
					yrLock_release(sf->block_lock);
					yrLog(2, "Released block lock");
					return 0;
				}
				yrFile_seek(sf->file, pos_firstfree, yrF_seekset);
				rw = yrFile_write(sf->file, 8, &out);
				if(rw != 8) {
					yrLog(0, "Expansion recovery failed: failed to write fileheader");
					yrLock_release(sf->block_lock);
					yrLog(2, "Released block lock");
					return 0;
				}
				sf->off.firstfree = out;
				yrLock_release(sf->block_lock);
				yrLog(2, "Released block lock");
				return 0;
			}
			written += rw;
		}
		fb.size = written - fbsize;
		fb.next = 0;
		yrFile_seek(sf->file, out, yrF_seekset);
		rw = yrFile_write(sf->file, fbsize, &fb);
		if(rw != fbsize) {
			yrLog(0, "Error while expanding file, failed to initialize blockheader");
			yrLock_release(sf->block_lock);
			yrLog(2, "Released block lock");
			return 0;
		}
	}

	YR_ASSERT(out);
	fb.next = 0;
	int64_t pos_next = out + (int64_t)&(((struct fileblock*)NULL)->next);
	yrFile_seek(sf->file, pos_next, yrF_seekset);
	rw = yrFile_write(sf->file, 8, &fb.next);
	if(rw != 8) {
		yrLog(0, "Block alloc failed to update blockheader");
		yrLock_release(sf->block_lock);
		yrLog(2, "Released block lock");
		return 0;
	}

	//truncate the block if it is too large
	if(*inout_size &&
	   fb.size - *inout_size > truncate_cutoff)
	{
		int64_t leftover = fb.size - fbsize - *inout_size;
		fb.size = *inout_size;

		yrFile_seek(sf->file, out, yrF_seekset);
		rw = yrFile_write(sf->file, fbsize, &fb);
		if(rw != fbsize) {
			yrLog(0, "Block truncate failed to write blockheader");
			yrLock_release(sf->block_lock);
			yrLog(2, "Released block lock");
			return 0;
		}
		struct fileblock foo;
		foo.next = sf->off.firstfree;
		foo.size = leftover;
		int64_t truncpos = out + fbsize + fb.size;
		yrFile_seek(sf->file, truncpos, yrF_seekset);
		rw = yrFile_write(sf->file, fbsize, &foo);
		if(rw != fbsize) {
			yrLog(0, "Block truncate failed to write blockheader for new block, file now has dead area but is otherwise fine");
		}
		yrFile_seek(sf->file, pos_firstfree, yrF_seekset);
		rw = yrFile_write(sf->file, 8, &truncpos);
		if(rw != 8) {
			yrLog(0, "Block truncate failed: could not prepend to free list, file now has dead area but is otherwise fine");
		}
		sf->off.firstfree = truncpos;

	}
	*inout_size = fb.size;

	//append it to the previous block if given
	if(prev_block) {
		int64_t pos = prev_block + (int64_t)&(((struct fileblock*)NULL)->next);
		yrFile_seek(sf->file, pos, yrF_seekset);
		rw = yrFile_write(sf->file, 8, &out);
		if(rw != 8) {
			yrLog(0, "Failed to append newly allocated block to stream");
			yrLock_release(sf->block_lock);
			yrLog(2, "Released block lock");
			return 0;
		}
	}

	//done
	yrLock_release(sf->block_lock);
	yrLog(2, "Released block lock");
	return out;
}

static void free_block_nolock(yrSceneFile* sf, int64_t block)
{
	size_t rw;

	//prepend to free list
	int64_t pos = block + (int64_t)&(((struct fileblock*)NULL)->next);
	yrFile_seek(sf->file, pos, yrF_seekset);
	rw = yrFile_write(sf->file, 8, &sf->off.firstfree);
	if(rw != 8) {
		yrLog(0, "Failed to update blockheader, file may be corrupted.");
		return;
	}
	//update pointer to free block list
	sf->off.firstfree = block;
	yrFile_seek(sf->file, pos_firstfree, yrF_seekset);
	rw = yrFile_write(sf->file, 8, &sf->off.firstfree);
	if(rw != 8) {
		yrLog(0, "Failed to update blockheader, file may be corrupted.");
		return;
	}
}

void free_block(yrSceneFile* sf, int64_t block)
{
	if(!sf) return;
	yrLog(2, "Acquiring block lock");
	yrLock_aqcuire(sf->block_lock);
	free_block_nolock(sf, block);
	yrLock_release(sf->block_lock);
	yrLog(2, "Released block lock");
}

static int merge_block_nolock(yrSceneFile* sf, int64_t block, int64_t append);
static void blocks_sort(size_t num, struct fileblock* blocks)
{
	if(num == 0) return;
	if(num == 1) return;

	size_t pivi = num/2;
	int64_t pivv = blocks[pivi].next; //re-using the next variable to keep the pointer to self
	size_t idxtop = 0;
	size_t idxbot = num - 1;
	while(idxtop < idxbot && idxbot != 0xFFFFFFFFFFFFFFFFull) {
		while(idxtop != idxbot && blocks[idxtop].next <= pivv) ++idxtop;
		while(idxtop != idxbot && blocks[idxbot].next > pivv) --idxbot;
		if(idxtop == idxbot) break;

		struct fileblock swap = blocks[idxtop];
		blocks[idxtop] = blocks[idxbot];
		blocks[idxbot] = swap;
		idxtop += 1;
		idxbot -= 1;
	}
	if(idxtop == idxbot && blocks[idxtop].next <= pivv) ++idxtop;

	YR_ASSERT(idxtop);
	if(idxtop == num) { //ensure progress
		struct fileblock swap = blocks[idxtop-1];
		blocks[idxtop-1] = blocks[pivi];
		blocks[pivi] = swap;
		idxtop -= 1;
	}
	blocks_sort(idxtop, blocks);
	blocks_sort(num - idxtop, blocks + idxtop);
}

void free_stream(yrSceneFile* sf, int64_t base)
{
	if(!sf) return;
	if(!base) return;
	yrLog(2, "Acquiring block lock");
	yrLock_aqcuire(sf->block_lock);
	const int64_t fbsize = sizeof(struct fileblock);
	size_t rw;

	//alloc list
	size_t blist_size = 8;
	size_t blist_ptr = 0;
	struct fileblock* blist = malloc(blist_size * sizeof(struct fileblock));
	if(!blist) {
		yrLock_release(sf->block_lock);
		yrLog(2, "Released block lock");
		yrLog(0, "Out of memory");
		return;
	}

	//gather list
	int64_t nextblock = base;
	while(nextblock != 0)
	{
		if(blist_ptr == blist_size) {
			//expand
			void* re = realloc(blist, (blist_size + (blist_size >> 1)) * sizeof(struct fileblock));
			if(!re) break; //we'll do the rest later if possible
			blist = re;
			blist_size += (blist_size >> 1);
		}
		yrFile_seek(sf->file, nextblock, yrF_seekset);
		rw = yrFile_read(sf->file, fbsize, blist + blist_ptr);
		if(rw != fbsize) {
			free(blist);
			yrLog(0, "Read error while freeing stream");
			yrLock_release(sf->block_lock);
			yrLog(2, "Released block lock");
			return;
		}
		int64_t thisblock = nextblock;
		nextblock = blist[blist_ptr].next;
		blist[blist_ptr].next = thisblock;
		blist_ptr += 1;
	}

	//sort list
	blocks_sort(blist_ptr, blist);

	//merge entries in list
	size_t r = 1;
	size_t w = 0;
	for(; r < blist_ptr; r += 1)
	{
		if(merge_block_nolock(sf, blist[w].next, blist[r].next));
		else {
			w += 1;
			blist[w] = blist[r];
		}
	}
	w += 1;

	//free entries
	for(size_t i = 0; i < w; i += 1)
		free_block_nolock(sf, blist[i].next);
	
	free(blist);
	yrLock_release(sf->block_lock);
	yrLog(2, "Released block lock");
	if(nextblock) free_stream(sf, nextblock); //realloc failed so  we're doing the rest now
}

static int merge_block_nolock(yrSceneFile* sf, int64_t block, int64_t append)
{
	struct fileblock fb0;
	struct fileblock fb1;
	const int64_t fbsize = sizeof(struct fileblock);
	size_t rw;

	//get first block size
	yrFile_seek(sf->file, block, yrF_seekset);
	rw = yrFile_read(sf->file, fbsize, &fb0);
	if(rw != fbsize) {
		return 0;
	}
	//if the append block is right behind
	if(block + fbsize + fb0.size == append)
	{
		//get append block size
		yrFile_seek(sf->file, append, yrF_seekset);
		rw = yrFile_read(sf->file, fbsize, &fb1);
		if(rw != fbsize) {
			return 0;
		}
		//and combine
		fb0.size += fbsize + fb1.size;
		fb0.next = 0; //if we're writing a stream this is correct, if we're freeing a stream this doesn't matter
		yrFile_seek(sf->file, block, yrF_seekset);
		rw = yrFile_write(sf->file, fbsize, &fb0);
		if(rw != fbsize) {
			yrLog(0, "Failed to update blockheader, file may be corrupted.");
			return 0;
		} else {
			return 1;
		}
	}
	return 0;
}

int merge_block(yrSceneFile* sf, int64_t block, int64_t append)
{
	yrLog(2, "Acquiring block lock");
	yrLock_aqcuire(sf->block_lock);
	int out = merge_block_nolock(sf, block, append);
	yrLock_release(sf->block_lock);
	yrLog(2, "Released block lock");
	return out;
}

void truncate_block(yrSceneFile* sf, int64_t block, int64_t fill)
{
	if(!sf) return;
	yrLog(2, "Acquiring block lock");
	yrLock_aqcuire(sf->block_lock);

	struct fileblock fb;
	const int64_t fbsize = sizeof(struct fileblock);
	size_t rw;

	//get block size
	yrFile_seek(sf->file, block, yrF_seekset);
	rw = yrFile_read(sf->file, fbsize, &fb);
	if(rw != fbsize) {
		yrLock_release(sf->block_lock);
		yrLog(2, "Released block lock");
		return;
	}

	//truncate if necessary
	if(fb.size - fill > truncate_cutoff)
	{
		int64_t leftover = fb.size - fill - fbsize;
		fb.size = fill;
		//update old block
		yrFile_seek(sf->file, block, yrF_seekset);
		rw = yrFile_write(sf->file, fbsize, &fb);
		if(rw != fbsize) {
			yrLock_release(sf->block_lock);
			yrLog(2, "Released block lock");
			yrLog(0, "Failed to update blockheader, file may be corrupted.");
			return;
		}
		//create new block
		fb.size = leftover;
		fb.next = sf->off.firstfree;
		sf->off.firstfree = block + fill + fbsize;
		yrFile_seek(sf->file, sf->off.firstfree, yrF_seekset);
		rw = yrFile_write(sf->file, fbsize, &fb);
		if(rw != fbsize) {
			yrLock_release(sf->block_lock);
			yrLog(2, "Released block lock");
			yrLog(0, "Failed to update blockheader, file may be corrupted.");
			return;
		}
		//update pointer to free block list
		yrFile_seek(sf->file, pos_firstfree, yrF_seekset);
		rw = yrFile_write(sf->file, 8, &sf->off.firstfree);
		if(rw != 8) {
			yrLock_release(sf->block_lock);
			yrLog(2, "Released block lock");
			yrLog(0, "Failed to update fileheader, file may be corrupted.");
			return;
		}
	}
	yrLock_release(sf->block_lock);
	yrLog(2, "Released block lock");
}

/************
* Backgrounds
*************/

int yrSceneFile_backgrounds_get(yrSceneFile* sf, unsigned* count, size_t** bgsize, void** blob, uint32_t* lastid, uint32_t* idmap)
{
	*count = 0;
	*bgsize = NULL;
	*blob = NULL;
	size_t rw;
	yrReadHandle* h = NULL;
	//open static stream
	h = yrSFRead_static_start(sf);					if(!h) {yrLog(0, "Could not open static stream in file '%s'.", sf->fname); goto onerror;}
	//read id map
	rw = yrSFRead_read(h, 4, lastid);				if(rw != 4) {yrLog(0, "Read error"); goto onerror;}
	rw = yrSFRead_read(h, 125*4, idmap);			if(rw != 125*4) {yrLog(0, "Read error"); goto onerror;} //BG_MAX_TEX
	//read bg count
	unsigned char bgcount;
	rw = yrSFRead_read(h, 1, &bgcount);				if(rw != 1) {yrLog(0, "Read error"); goto onerror;}
	*count = bgcount;
	if(!bgcount) {
		//no backgrounds
		yrSFRead_end(h);
		*bgsize = NULL;
		*blob = NULL;
		return 0;
	}
	//read bg data len
	unsigned bgdatalen;
	rw = yrSFRead_read(h, 4, &bgdatalen);			if(rw != 4) {yrLog(0, "Read error"); goto onerror;}
	//reserve sizes
	*bgsize = calloc(bgcount, sizeof(size_t));		if(!*bgsize) {yrLog(0, "Out of memory"); goto onerror;}
	//reserve blob
	*blob = malloc(bgdatalen);						if(!*blob) {yrLog(0, "Out of memory"); goto onerror;}
	//read bgs into blob
	unsigned pngsize;
	size_t blob_off = 0;
	for(unsigned i = 0; i < bgcount; ++i)
	{
		rw = yrSFRead_read(h, 4, &pngsize);							if(rw != 4) {yrLog(0, "Read error"); goto onerror;}
		rw = yrSFRead_read(h, pngsize, ((char*)*blob) + blob_off);	if(rw != pngsize) {yrLog(0, "Read error"); goto onerror;}
		(*bgsize)[i] = pngsize;
		blob_off += pngsize;
	}
	return 0;
onerror:
	free(*bgsize);
	free(*blob);
	*bgsize = NULL;
	*blob = NULL;
	*count = 0;
	if(h) yrSFRead_end(h);
	return -1;
}

int yrSceneFile_backgrounds_set(yrSceneFile* sf, unsigned count, size_t* bgsize, void** bgblob, uint32_t lastid, uint32_t* idmap)
{
	yrWriteHandle* h = NULL;
	size_t rw;
	//create new stream
	h = yrSFWrite_start(sf, 0);						if(!h) {yrLog(0, "Could not create new stream in file '%s'.", sf->fname); goto onerror;}
	//write id map
	rw = yrSFWrite_write(h, 4, &lastid);			if(rw != 4) {yrLog(0, "Write error"); goto onerror;}
	rw = yrSFWrite_write(h, 125*4, idmap);			if(rw != 125*4) {yrLog(0, "Write error"); goto onerror;} //BG_MAX_TEX
	//write count
	unsigned char bgcount = (unsigned char) count;
	rw = yrSFWrite_write(h, 1, &bgcount);			if(rw != 1) {yrLog(0, "Write error"); goto onerror;}
	//write bg data len
	unsigned bgdatalen = 0;
	for(unsigned i = 0; i < bgcount; ++i)
		bgdatalen += (unsigned) bgsize[i];
	rw = yrSFWrite_write(h, 4, &bgdatalen);			if(rw != 4) {yrLog(0, "Write error"); goto onerror;}
	//write backgrounds
	unsigned pngsize;
	for(unsigned i = 0; i < bgcount; ++i) {
		pngsize = (unsigned) bgsize[i];
		rw = yrSFWrite_write(h, 4, &pngsize);				if(rw != 4) {yrLog(0, "Write error"); goto onerror;}
		rw = yrSFWrite_write(h, pngsize, bgblob[i]);		if(rw != pngsize) {yrLog(0, "Write error"); goto onerror;}
	}
	//finalize stream
	return yrSFWrite_static_end(h);
onerror:
	if(h) yrSFWrite_abort(h);
	return -1;
}