#include "sys_quadstore.h"
#include "system.h"
#include "platform.h"
#include "sys_vr.h"
#include "sys_log.h"
#include "sys_render.h"
#include "scenefile.h"
#include <string.h>
#include <stdlib.h>

#define BLOCKSIZE 1000
#define TILEREFRESH_COUNT 64
#define TOPBUILD_WAIT 333333
#define TOPBUILD_SCRATCH 512

static int yrQuadStore_init(void* loadtoken);
static int yrQuadStore_tick(void);
static int yrQuadStore_save(void* savetoken, void* saveevent, int* savefail);
static int yrQuadStore_canfreeze(void);
static int yrQuadStore_freeze(void);
static int yrQuadStore_unfreeze(void);
static int yrQuadStore_shutdown(void);

static int load_quads(yrReadHandle* rh);

void yrQuadStore_reg(void)
{
	yrSystem_register(sysQuadStore,
					  yrQuadStore_init,
					  yrQuadStore_tick,
					  yrQuadStore_save,
					  yrQuadStore_canfreeze,
					  yrQuadStore_freeze,
					  yrQuadStore_unfreeze,
					  yrQuadStore_shutdown,
					  (1<<sysLog)|(1<<sysPlatform)|(1<<sysVR)|(1<<sysRender));
}

/*****************************
* Quadblock: storage structure
******************************/
struct _yrtag_quadblock;
typedef struct _yrtag_quadblock quadblock;

struct _yrtag_quadblock
{
	yrQuad data[BLOCKSIZE];
	yrQuad_ext extdata[BLOCKSIZE];
	quadblock* nextblock;
};

static void quadblock_init(quadblock* b)
{
	b->nextblock = NULL;
	memset(b->data, 0, sizeof(b->data));
	for(size_t i = 0; i < BLOCKSIZE; ++i) {
		b->data[i].ext = b->extdata + i;
		*(yrQuad**) (b->data + i) = b->data + i + 1;
	}
	*(yrQuad**) (b->data + BLOCKSIZE - 1) = NULL;
}

/********
* Toplist
*********/
struct _yrtag_toplist;
typedef struct _yrtag_toplist toplist;

struct _yrtag_toplist
{
	yrQuad** quad;
	float* metric;
	size_t count;
};

static void top_threadfunc(void* param);

/************
* Static vars
*************/
static quadblock* store_root = NULL;
static yrQuad* freequad = NULL;
static yrQuad* editlocked = NULL;

static size_t quad_count = 0;
static toplist* top_front = NULL;
static toplist* top_back = NULL;
static size_t top_size = 0;
static size_t top_ptr = 0;
static yrEvent top_ready = NULL;
static yrEvent top_go = NULL;
static yrThread top_thread = NULL;
static int top_stop = 0;
static int frozen = 0;
static yrThread savethread = NULL;
static uint32_t nextid = 0;

/*****************
* Public functions
******************/
yrQuad* yrQuadStore_add(yrQuad* q)
{
	YR_ASSERT(q);
	YR_ASSERT(!frozen);
	if(!freequad)
	{
		quadblock* newblock = malloc(sizeof(quadblock));
		if(!newblock) return NULL;
		quadblock_init(newblock);

		quadblock* last = store_root;
		while(last->nextblock) last = last->nextblock;
		last->nextblock = newblock;

		freequad = newblock->data;
	}
	YR_ASSERT(!(freequad->flags & QUAD_VALID));
	yrQuad* dest = freequad;
	freequad = *(yrQuad**) freequad;

	yrQuad_ext* savext = dest->ext;
	*dest = *q;
	dest->ext = savext;
	dest->flags |= QUAD_VALID;
	dest->flags &= ~(QUAD_CLEARCACHE | QUAD_IN_USE | QUAD_TILECACHED);
	dest->id = nextid;
	nextid += 1; //should this value ever wrap around only export will be affected by collisions so probably not worth the overhead of checking

	if(q->texture_bg > 125) q->texture_bg = 125; //MAX_BG_TEXTURE

	memset(dest->ext->tiles, -1, sizeof(dest->ext->tiles));
	memset(dest->ext->cached_tileidx, -1, sizeof(dest->ext->cached_tileidx));
	yrQuadStore_update(dest);
	quad_count += 1;
	return dest;
}

void yrQuadStore_remove(yrQuad* q)
{
	YR_ASSERT(!frozen);
	if(editlocked == q) yrQuadStore_editlock(NULL);

	//add to list of free quads
	YR_ASSERT(q);
	YR_ASSERT(q->flags & QUAD_VALID);
	q->flags = 0;
	*(yrQuad**) q = freequad;
	freequad = q;
	quad_count -= 1;
}

void yrQuadStore_update(yrQuad* q)
{
	YR_ASSERT(!frozen);
	YR_ASSERT(q);
	YR_ASSERT(q->flags & QUAD_VALID);
	
	vec4f e1 = vec4f_mul(1.0f/YR_ACCURACY, vec4f_from_vec4i(vec4i_sub(q->v[1], q->v[0])));
	vec4f e2 = vec4f_mul(1.0f/YR_ACCURACY, vec4f_from_vec4i(vec4i_sub(q->v[2], q->v[0])));
	
	q->width = vec3f_length(e1);
	q->height = vec3f_length(e2);
	e1 = vec4f_mul(1.0f/q->width, e1);
	e2 = vec4f_mul(1.0f/q->height, e2);
	q->normal = vec3f_cross(e1, e2);
	q->ext->invtransform.col[0] = e1;
	q->ext->invtransform.col[1] = e2;
	q->ext->invtransform.col[2] = q->normal;
	q->ext->invtransform.col[3].x = 0.0f;
	q->ext->invtransform.col[3].y = 0.0f;
	q->ext->invtransform.col[3].z = 0.0f;
	q->ext->invtransform.col[3].w = 1.0f;
	q->ext->invtransform = mat4f_invert(q->ext->invtransform);

	q->tile_width = (int)(ceilf(q->uv_draw[0] + q->width * UV_PER_METER) - floorf(q->uv_draw[0]));
	q->tile_width = (q->tile_width < 8) ? q->tile_width : 8;
	q->ext->tile_max_idx = q->tile_width * (int)(ceilf(q->uv_draw[1] + q->height * UV_PER_METER) - floorf(q->uv_draw[1]));
}

void yrQuadStore_editlock(yrQuad* q)
{
	YR_ASSERT(!frozen);
	if(editlocked) editlocked->flags &= ~QUAD_IN_USE;
	editlocked = q;
	if(editlocked) editlocked->flags |= QUAD_IN_USE;
	top_front->quad[0] = editlocked;
	top_front->metric[0] = 10000.0f;
}

int yrQuadStore_editlocked(void)
{
	return editlocked != NULL;
}

yrQuad_iter yrQuad_iter_top(void)
{
	return (yrQuad_iter) 0;
}

yrQuad* yrQuad_iter_next(yrQuad_iter* i, float* metric)
{
	size_t idx = (size_t) *i;
	while(idx < top_front->count &&
		  (!top_front->quad[idx] ||
		   !(top_front->quad[idx]->flags & QUAD_VALID))) 
	{
		++idx;
	}
	if(idx >= top_front->count) return NULL;
	yrQuad* out = top_front->quad[idx];
	*metric = top_front->metric[idx];
	*i = (yrQuad_iter) (idx + 1);
	return out;
}

//cold iterator
struct _yrtag_yrQuad_cold_iter
{
	yrReadHandle* rh;
};

yrQuad_cold_iter* yrQuad_cold_iter_init(yrSceneFile* sf, size_t* quadcount)
{
	int err;
	size_t rw, dump;
	yrQuad_cold_iter* out = calloc(1, sizeof(yrQuad_cold_iter));
	if(!out) {yrLog(0, "Out of memory"); return NULL;}

	out->rh = yrSFRead_start(sf);						if(!out->rh) goto onerror;
	err = yrSystem_seeksys(out->rh, sysQuadStore);		if(err) goto onerror;
	rw = yrSFRead_read(out->rh, 8, quadcount);			if(rw != 8) goto onerror;
	rw = yrSFRead_read(out->rh, 8, &dump);				if(rw != 8) goto onerror;
	rw = yrSFRead_read(out->rh, 4, &dump);				if(rw != 4) goto onerror;

	return out;
onerror:
	yrQuad_cold_iter_end(out);
	return NULL;
}

static int read_quad(yrReadHandle* rh, yrQuad* out, uint32_t* bg_out, uint32_t tiles_out[64]);
int yrQuad_cold_iter_next(yrQuad_cold_iter* i, yrQuad* out, uint32_t* bg_out, uint32_t tiles_out[64])
{
	if(!i->rh) return -1;

	//read quad
	memset(tiles_out, 0xff, 64 * sizeof(uint32_t));
	int ret = read_quad(i->rh, out, bg_out, tiles_out);

	//error or end of stream
	if(ret) {
		yrSFRead_end(i->rh);
		i->rh = NULL;
		return -1;
	}

	return 0;
}

void yrQuad_cold_iter_end(yrQuad_cold_iter* i)
{
	if(i->rh) yrSFRead_end(i->rh);
	free(i);
}

//cold insertion
static int copy_quad(yrReadHandle* rh, yrWriteHandle* wh);
static int write_quad(yrWriteHandle* wh, yrQuad* q, uint32_t bg, uint32_t tilecount, uint32_t* tiles);
int	yrQuadStore_cold_insert(yrSceneFile* sf, size_t icount, yrQuad* q, uint32_t* bg, uint32_t* tilecount, uint32_t** tiles, int add_offset)
{
	int err;
	size_t rw;
	yrReadHandle* rh = NULL;
	yrWriteHandle* wh = NULL;
	uint32_t qnextid = 0;

	//open handles and copy up to quadstore
	err = yrSystem_initinsert(sf, &rh, &wh, sysQuadStore);				if(err) goto onerror;
	//update count
	size_t qcount = 0;
	rw = yrSFRead_read(rh, 8, &qcount);									if(rw != 8) goto onerror;
	qcount += icount;
	rw = yrSFWrite_write(wh, 8, &qcount);								if(rw != 8) goto onerror;
	qcount -= icount;
	//update insert offset
	size_t ninsert = 0;
	rw = yrSFRead_read(rh, 8, &ninsert);								if(rw != 8) goto onerror;
	ninsert += icount;
	rw = yrSFWrite_write(wh, 8, &ninsert);								if(rw != 8) goto onerror;
	ninsert -= icount;
	//update next id
	rw = yrSFRead_read(rh, 4, &qnextid);								if(rw != 4) goto onerror;
	qnextid += (uint32_t) icount;
	rw = yrSFWrite_write(wh, 4, &qnextid);								if(rw != 4) goto onerror;
	qnextid -= (uint32_t) icount;
	//copy quads
	for(size_t i = 0; i < qcount; i += 1) {
		err = copy_quad(rh, wh);										if(err) goto onerror;
	}
	//insert new quads
	int32_t spacing = (int32_t)(0.1f * YR_ACCURACY);
	for(size_t i = 0; i < icount; i += 1) {
		if(add_offset) {
			q[i].v[0].y += (int32_t)(ninsert + i) * spacing;
			q[i].v[1].y += (int32_t)(ninsert + i) * spacing;
			q[i].v[2].y += (int32_t)(ninsert + i) * spacing;
			q[i].id = (uint32_t)qnextid + (uint32_t)i;
		}
		err = write_quad(wh, q + i, bg[i], tilecount[i], tiles[i]);		if(err) goto onerror;
	}
	//terminating zero quad
	err = copy_quad(rh, wh);											if(err) goto onerror;
	//done
	return yrSystem_endinsert(rh, wh, sysQuadStore);
onerror:
	if(rh || wh) yrSystem_abortinsert(rh, wh);
	return -1;
}

/****************************************
* System functions (init, tick, shutdown)
*****************************************/
static int yrQuadStore_init(void* loadtoken)
{
	frozen = 0;
	nextid = 0;
	quad_count = 0;
	//storage
	store_root = malloc(sizeof(quadblock));
	if(!store_root) { yrLog(0, "Out of memory (quadblock)."); return -1; }
	quadblock_init(store_root);
	freequad = store_root->data;

	//updater thread
	int err = 0;
	top_stop = 0;
	top_size = yrSystem_visible_quads();
	top_ptr = 0;

	for(size_t i = 0; i < 2; ++i)
	{
		top_back = top_front;
		top_front = malloc(sizeof(toplist));
		if(!top_front)
			{ yrLog(0, "Out of memory (toplist)."); err = -1; goto onerror; }
		top_front->quad = malloc(top_size * sizeof(yrQuad*));
		top_front->metric = malloc(top_size * sizeof(float));
		if(!top_front->quad || !top_front->metric)
			{ yrLog(0, "Out of memory (toplist)."); err = -1; goto onerror; }
		top_front->count = 1;
		top_front->quad[0] = NULL;
	}
	top_ready = yrEvent_create();
	top_go = yrEvent_create();
	if(!top_ready || !top_go)
		{ yrLog(0, "OS failed to create synchronization event (toplist)."); err = -1; goto onerror; }
	top_thread = yrThread_create(top_threadfunc, NULL);
	if(!top_thread)
		{ yrLog(0, "OS failed to create thread (toplist)."); err = -1; goto onerror; }

	//load the quads from scenefile
	err = load_quads(loadtoken);
	if(err) goto onerror;

	return 0;
onerror:
	return err;
}

static int yrQuadStore_shutdown(void)
{
	YR_ASSERT(!frozen);

	//clean up the save thread if there is one
	if(savethread) yrThread_join(savethread, 0);
	savethread = NULL;

	//updater thread
	if(top_thread)
	{
		top_stop = 1;
		yrEvent_set(top_go, 1);
		int err = yrThread_join(top_thread, 555000); //after 555ms (up to about 333 of which may be spent in sleep) leak the thread
		if(err > 0) yrLog(1, "Thread join timeout (toplist).");
	}
	top_thread = NULL;

	yrEvent_destroy(top_ready);
	yrEvent_destroy(top_go);
	top_ready = NULL;
	top_go = NULL;

	for(size_t i = 0; i < 2; ++i) {
		if(top_front) {
			free(top_front->quad);
			free(top_front->metric);
			free(top_front);
		}
		top_front = top_back;
	}
	top_front = NULL;
	top_back = NULL;

	//storage
	freequad = NULL;
	editlocked = NULL;
	quadblock* to_remove = store_root;
	while(to_remove) {
		quadblock* foo = to_remove;
		to_remove = to_remove->nextblock;
		free(foo);
	}
	store_root = NULL;
	return 0;
}

static int yrQuadStore_tick(void)
{
	if(frozen) return 0;
	if(yrEvent_wait(top_ready, 0) == 0)
	{
		//a new toplist is available
		toplist* swap = top_front;
		top_front = top_back;
		top_back = swap;
		yrQuadStore_editlock(editlocked);
		yrEvent_set(top_ready, 0);
		yrEvent_set(top_go, 1);
	}
	//clear some quads' tile caches to keep things fresh
	for(size_t i = 0; i < TILEREFRESH_COUNT; ++i) {
		top_ptr = (top_ptr + 1) % top_front->count;
		if(top_front->quad[top_ptr] &&
		   (top_front->quad[top_ptr]->flags & QUAD_VALID))
		{
			top_front->quad[top_ptr]->flags &= ~QUAD_TILECACHED;
		}
	}
	return 0;
}

/************************
* Quad top updater thread
*************************/
static void gen_quadtop(vec4i xyz);
static float quad_metric(vec4i xyz, yrQuad* q);
static void top_sort(size_t ntop, yrQuad** topq, float* topm);
static size_t top_find_candidates(vec4i xyz, float cutoff, size_t count, yrQuad** topq, float* topm, quadblock** iter_block, size_t* iter);
static size_t top_merge(size_t ntop, size_t ntop_max, yrQuad** topq, float* topm, size_t ninsert, yrQuad** insertq, float* insertm);

static void top_threadfunc(void* param)
{
	for(;;) {
		vec4i xyz = vec4i_add(yrVR_get_coord_offset(), vec4i_from_vec4f(vec4f_mul(YR_ACCURACY, yrVR_world_from_tracked(0).col[3])));
		gen_quadtop(xyz);

		yrEvent_set(top_ready, 1);
		yrEvent_wait(top_go, 0xFFFFFFFFFFFFFFFFull);
		yrEvent_set(top_go, 0);
		if(top_stop) break;
		yr_sleep(TOPBUILD_WAIT);
	}
}

static void gen_quadtop(vec4i xyz)
{
	yrQuad** quad = top_back->quad;
	float* metric = top_back->metric;
	top_back->count = 1;
	quad[0] = NULL; //reserved for editlocked quad
	metric[0] = 0.0f;

	quadblock* iterblock = store_root;
	size_t iter = 0;
	static yrQuad* cands_quad[TOPBUILD_SCRATCH];
	static float cands_metric[TOPBUILD_SCRATCH];
	while(iterblock)
	{
		float cutoff = (top_back->count == top_size) ? metric[top_size - 1] : 0.0f;
		size_t cands = TOPBUILD_SCRATCH;
		//find potential quads to add
		cands = top_find_candidates(xyz, cutoff, cands, cands_quad, cands_metric, &iterblock, &iter);
		//sort candidates
		top_sort(cands, cands_quad, cands_metric);
		//merge lists
		top_back->count = top_merge(top_back->count - 1, top_size - 1, quad + 1, metric + 1, cands, cands_quad, cands_metric) + 1;
	}
}

static float quad_metric(vec4i xyz, yrQuad* q)
{
	vec4i center = vec4i_add(q->v[1], q->v[2]);
	center.m = _mm_srai_epi32(center.m, 1); //divide by 2
	vec4f diff = vec4f_from_vec4i(vec4i_sub(center, xyz));
	float dist2 = vec3f_dot(diff, diff);
	return 1.0f / dist2;
}	

static void top_sort(size_t ntop, yrQuad** topq, float* topm)
{
	if(ntop == 0) return;
	if(ntop == 1) return;

	size_t pivi = ntop/2;
	float pivv = topm[pivi];
	size_t idxtop = 0;
	size_t idxbot = ntop - 1;
	while(idxtop < idxbot && idxbot != 0xFFFFFFFFFFFFFFFFull) {
		while(idxtop != idxbot && topm[idxtop] >= pivv) ++idxtop;
		while(idxtop != idxbot && topm[idxbot] < pivv) --idxbot;
		if(idxtop == idxbot) break;

		yrQuad* swapq = topq[idxtop];
		float swapm = topm[idxtop];
		topq[idxtop] = topq[idxbot];
		topm[idxtop] = topm[idxbot];
		topq[idxbot] = swapq;
		topm[idxbot] = swapm;
		idxtop += 1;
		idxbot -= 1;
	}
	if(idxtop == idxbot && topm[idxtop] >= pivv) ++idxtop;

	if(idxtop) {
		if(idxtop == ntop) {
			yrQuad* swapq = topq[idxtop - 1];
			float swapm = topm[idxtop - 1];
			topq[idxtop - 1] = topq[pivi];
			topm[idxtop - 1] = topm[pivi];
			topq[pivi] = swapq;
			topm[pivi] = swapm;
			idxtop -= 1;
		}
		top_sort(idxtop, topq, topm);
	}
	top_sort(ntop - idxtop, topq + idxtop, topm + idxtop);
}

static size_t top_find_candidates(vec4i xyz, float cutoff, size_t count, yrQuad** topq, float* topm, quadblock** iter_block, size_t* iter)
{
	size_t idx = 0;
	while(*iter_block) {
		for(; *iter < BLOCKSIZE; ++*iter) {
			if(idx == count) return idx;
			yrQuad* q = (*iter_block)->data + *iter;
			if(!q || !(q->flags & QUAD_VALID)) continue;
			float m = quad_metric(xyz, q);
			if(m <= cutoff) continue;
			topq[idx] = q;
			topm[idx] = m;
			idx += 1;
		}
		*iter = 0;
		*iter_block = (*iter_block)->nextblock;
	}
	return idx;
}

static size_t top_merge(size_t ntop, size_t ntop_max, yrQuad** topq, float* topm, size_t ninsert, yrQuad** insertq, float* insertm)
{
	//find cut point for the original list and insert list
	size_t cut_top = ntop;
	size_t nfree = ntop_max - ntop;
	size_t cut_ins = (nfree > ninsert) ? ninsert : nfree;
	while(cut_top != 0 &&
		  cut_ins != ninsert &&
		  topm[cut_top - 1] < insertm[cut_ins])
	{
		cut_top -= 1;
		cut_ins += 1;
	}
	//continue by merging the list ("zipping")
	size_t count = cut_top + cut_ins;
	size_t write = count;
	while(write != 0)
	{
		write -= 1;
		YR_ASSERT(cut_top != 0 || cut_ins != 0);
		if(cut_top == 0) {
			cut_ins -= 1;
			topq[write] = insertq[cut_ins];
			topm[write] = insertm[cut_ins];
		}
		else if(cut_ins == 0) {
			cut_top -= 1;
			topq[write] = topq[cut_top];
			topm[write] = topm[cut_top];
		}
		else if(insertm[cut_ins-1] < topm[cut_top-1]) {
			cut_ins -= 1;
			topq[write] = insertq[cut_ins];
			topm[write] = insertm[cut_ins];
		}
		else {
			cut_top -= 1;
			topq[write] = topq[cut_top];
			topm[write] = topm[cut_top];
		}
	}
	return count;
}


/*******************
* Autosave functions
********************/

struct saveparam {
	yrWriteHandle* wh;
	yrEvent se;
	int* fail;
}
sp;

static void save_threadfunc(void* p);

static int yrQuadStore_save(void* savetoken, void* saveevent, int* savefail)
{
	sp.wh = savetoken; //no risk of collision because only one save can be in progress at once
	sp.se = saveevent;
	sp.fail = savefail;
	if(savethread) yrThread_join(savethread, 0);//try to clean up the previous save thread
	savethread = yrThread_create(save_threadfunc, NULL);
	return 0;
}

static int yrQuadStore_canfreeze(void) { return 1; }
static int yrQuadStore_freeze(void) {frozen = 1; return 0;}
static int yrQuadStore_unfreeze(void) {frozen = 0; return 0;}

struct quadrecord
{
	int32_t v[3][3];
	float uv_back[2][2];
	float uv_draw[2];
	yrColor color;
	uint16_t flags;
	uint16_t padding;
	uint32_t id;
	uint32_t background;
	uint32_t tile_count;
	uint32_t tiles[64];
};

static int copy_quad(yrReadHandle* rh, yrWriteHandle* wh)
{
	struct quadrecord qr;
	size_t qr_basesize = (size_t)&(((struct quadrecord*)NULL)->tiles);
	
	//read quad
	size_t rw = yrSFRead_read(rh, qr_basesize, &qr);
	if(rw != qr_basesize) {
		yrLog(0, "Error reading serialized quad.");
		return -1;
	}
	//read tiles
	if(qr.tile_count > 64) {
		yrLog(0, "Serialized quad is corrupted.");
		return -1;
	}
	size_t tilessize = qr.tile_count * sizeof(uint32_t);
	if(tilessize) {
		rw = yrSFRead_read(rh, tilessize, qr.tiles);
		if(rw != tilessize) {
			yrLog(0, "Error reading serialized quad.");
			return -1;
		}
	}
	//write quad
	rw = yrSFWrite_write(wh, qr_basesize, &qr);
	if(rw != qr_basesize) {
		yrLog(0, "Error writing serialized quad.");
		return -1;
	}
	//write tiles
	if(tilessize) {
		rw = yrSFWrite_write(wh, tilessize, qr.tiles);
		if(rw != tilessize) {
			yrLog(0, "Error writing serialized quad.");
			return -1;
		}
	}
	return 0;
}

static int write_quad(yrWriteHandle* wh, yrQuad* q, uint32_t bg, uint32_t tilecount, uint32_t* tiles)
{
	struct quadrecord qr;
	size_t qr_basesize = (size_t)&(((struct quadrecord*)NULL)->tiles);
	qr.v[0][0] = q->v[0].x;
	qr.v[0][1] = q->v[0].y;
	qr.v[0][2] = q->v[0].z;
	qr.v[1][0] = q->v[1].x;
	qr.v[1][1] = q->v[1].y;
	qr.v[1][2] = q->v[1].z;
	qr.v[2][0] = q->v[2].x;
	qr.v[2][1] = q->v[2].y;
	qr.v[2][2] = q->v[2].z;
	qr.uv_back[0][0] = q->uv_back[0][0];
	qr.uv_back[0][1] = q->uv_back[0][1];
	qr.uv_back[1][0] = q->uv_back[1][0];
	qr.uv_back[1][1] = q->uv_back[1][1];
	qr.uv_draw[0] = q->uv_draw[0];
	qr.uv_draw[1] = q->uv_draw[1];
	qr.color = q->color;
	qr.flags = q->flags & (QUAD_BG_AR11 | QUAD_BG_PIN | QUAD_BG_TILE);
	qr.padding = 0;
	qr.id = q->id;
	qr.background = bg;
			
	qr.tile_count = tilecount;
	size_t tilessize = qr.tile_count * sizeof(uint32_t);
	if(tilessize) memcpy(qr.tiles, tiles, tilessize);
			
	size_t rw = yrSFWrite_write(wh, qr_basesize + tilessize, &qr);
	if(rw != qr_basesize + tilessize) {
		yrLog(0, "Error writing serialized quad.");
		return -1;
	}
	return 0;
}

static void save_threadfunc(void* p)
{
	struct quadrecord qr;
	size_t qr_basesize = (size_t)&(((struct quadrecord*)NULL)->tiles);

	size_t rw;
	rw = yrSFWrite_write(sp.wh, 8, &quad_count); 
	if(rw != 8) {
		yrLog(0, "Error while serializing quad stream.");
		*sp.fail = 1;
		yrEvent_set(sp.se, 1);
		return;
	}
	size_t quad_inserted = 0; //write a zero here, only the cold insertion function uses this
	rw = yrSFWrite_write(sp.wh, 8, &quad_inserted); 
	if(rw != 8) {
		yrLog(0, "Error while serializing quad stream.");
		*sp.fail = 1;
		yrEvent_set(sp.se, 1);
		return;
	}
	rw = yrSFWrite_write(sp.wh, 4, &nextid); 
	if(rw != 4) {
		yrLog(0, "Error while serializing quad stream.");
		*sp.fail = 1;
		yrEvent_set(sp.se, 1);
		return;
	}

	for(quadblock* block = store_root; block != NULL; block = block->nextblock)
	for(size_t idx = 0; idx < BLOCKSIZE; idx += 1)
	{
		if(block->data[idx].flags & QUAD_VALID)
		{
			yrQuad* q = block->data + idx;
			int err = write_quad(sp.wh, q, yrRender_bg_to_id(q->texture_bg), q->ext->tile_max_idx, q->ext->tiles);
			if(err) {
				*sp.fail = 1;
				break;
			}
		}
	}
	//write an empty quad to mark end of serialization
	memset(&qr, 0, sizeof(qr));
	rw = yrSFWrite_write(sp.wh, qr_basesize, &qr);
	if(rw != qr_basesize) {
		yrLog(0, "Error writing serialized quad.");
		*sp.fail = 1;
	}

	yrEvent_set(sp.se, 1);
}

static int read_quad(yrReadHandle* rh, yrQuad* out, uint32_t* bg_out, uint32_t tiles_out[64])
{
	struct quadrecord qr;
	size_t qr_basesize = (size_t)&(((struct quadrecord*)NULL)->tiles);
	
	//read quad
	size_t rw = yrSFRead_read(rh, qr_basesize, &qr);
	if(rw != qr_basesize) {
		yrLog(0, "Error reading serialized quad.");
		return -1;
	}
	//check end of stream
	if( !qr.v[0][0] && !qr.v[0][1] && !qr.v[0][2] &&
		!qr.v[1][0] && !qr.v[1][1] && !qr.v[1][2] &&
		!qr.v[2][0] && !qr.v[2][1] && !qr.v[2][2])
	{
		return 1;
	}
	//read tiles
	if(qr.tile_count > 64) {
		yrLog(0, "Serialized quad is corrupted.");
		return -1;
	}
	size_t tilessize = qr.tile_count * sizeof(uint32_t);
	if(tilessize) {
		rw = yrSFRead_read(rh, tilessize, qr.tiles);
		if(rw != tilessize) {
			yrLog(0, "Error reading serialized quad.");
			return -1;
		}
	}
	//reconstruct quad
	out->v[0].x = qr.v[0][0];
	out->v[0].y = qr.v[0][1];
	out->v[0].z = qr.v[0][2];
	out->v[0].w = YR_ACCURACY;
	out->v[1].x = qr.v[1][0];
	out->v[1].y = qr.v[1][1];
	out->v[1].z = qr.v[1][2];
	out->v[1].w = YR_ACCURACY;
	out->v[2].x = qr.v[2][0];
	out->v[2].y = qr.v[2][1];
	out->v[2].z = qr.v[2][2];
	out->v[2].w = YR_ACCURACY;
	out->uv_back[0][0] = qr.uv_back[0][0];
	out->uv_back[0][1] = qr.uv_back[0][1];
	out->uv_back[1][0] = qr.uv_back[1][0];
	out->uv_back[1][1] = qr.uv_back[1][1];
	out->uv_draw[0] = qr.uv_draw[0];
	out->uv_draw[1] = qr.uv_draw[1];
	out->color = qr.color;
	out->flags = qr.flags;
	out->texture_bg = 125; //MAX_BG_INDEX
	out->id = qr.id;
	//TODO: consider "repairing" quads: normal from e0, e1; create a new v2 so e1 is perpendicular to e0 and the normal while maintaining length
	*bg_out = qr.background;
	memcpy(tiles_out, qr.tiles, tilessize);	
	return 0;
}

static int load_quads(yrReadHandle* rh)
{
	yrQuad quad;
	uint32_t background;
	uint32_t tiles[64];
	uint32_t highest_id = 0;
	size_t qcount, qinsert;
	size_t rw;

	rw = yrSFRead_read(rh, 8, &qcount); 
	if(rw != 8) { yrLog(0, "Error in serialized quad stream."); return -1; }
	rw = yrSFRead_read(rh, 8, &qinsert); 
	if(rw != 8) { yrLog(0, "Error in serialized quad stream."); return -1; }
	rw = yrSFRead_read(rh, 4, &highest_id); 
	if(rw != 4) { yrLog(0, "Error in serialized quad stream."); return -1; }
	highest_id = 0;

	int ret;
	memset(tiles, 0xff, sizeof(tiles));
	while(0 == (ret = read_quad(rh, &quad, &background, tiles)))
	{
		uint32_t qid = quad.id;
		yrQuad* foo = yrQuadStore_add(&quad);
		if(foo) {
			foo->id = qid;
			foo->texture_bg = yrRender_bg_from_id(background);
			if(qid > highest_id) highest_id = qid;
			memcpy(foo->ext->tiles, tiles, sizeof(tiles));
		} else {
			yrLog(0, "Error adding serialized quad.");
			return -1;
		}
		memset(tiles, 0xff, sizeof(tiles));
	}
	if(ret < 0) return ret;

	nextid = highest_id + 1;
	return 0;
}
