#include "stdinc.h"
#include "dat.h"
#include "fns.h"

int			queuewrites = 0;

static Packet		*readilump(Lump *u, IAddr *ia, u8int *score, int rac);

Packet*
readlump(u8int *score, int type, u32int size)
{
	Lump *u;
	Packet *p;
	IAddr ia;
	u32int n;
	int rac;

	qlock(&stats.lock);
	stats.lumpreads++;
	qunlock(&stats.lock);
	u = lookuplump(score, type);
	if(u->data != nil){
		n = packetsize(u->data);
		if(n > size){
			seterr(EOk, "read too small: asked for %d need at least %d", size, n);
			putlump(u);

			return nil;
		}
		p = packetdup(u->data, 0, n);
		putlump(u);

		return p;
	}

	if(lookupscore(score, type, &ia, &rac) < 0){
		//ZZZ place to check for someone trying to guess scores
		seterr(EOk, "no block with that score exists");

		putlump(u);
		return nil;
	}
	if(ia.size > size){
		seterr(EOk, "read too small 1: asked for %d need at least %d", size, ia.size);

		putlump(u);
		return nil;
	}

	p = readilump(u, &ia, score, rac);
	putlump(u);

	return p;
}

/*
 * save away a lump, and return it's score.
 * doesn't store duplicates, but checks that the data is really the same.
 */
int
writelump(Packet *p, u8int *score, int type, u32int creator)
{
	Lump *u;
	int ok;

	qlock(&stats.lock);
	stats.lumpwrites++;
	qunlock(&stats.lock);

	packetsha1(p, score);

	u = lookuplump(score, type);
	if(u->data != nil){
		ok = 0;
		if(packetcmp(p, u->data) != 0){
			seterr(EStrange, "score collision");
			ok = -1;
		}
		packetfree(p);
		putlump(u);
		return ok;
	}

	if(queuewrites)
		return queuewrite(u, p, creator);

	ok = writeqlump(u, p, creator);

	putlump(u);
	return ok;
}

int
writeqlump(Lump *u, Packet *p, int creator)
{
	ZBlock *flat;
	Packet *old;
	IAddr ia;
	int ok;
	int rac;

	if(lookupscore(u->score, u->type, &ia, &rac) == 0){
		/*
		 * if the read fails,
		 * assume it was corrupted data and store the block again
		 */
		old = readilump(u, &ia, u->score, rac);
		if(old != nil){
			ok = 0;
			if(packetcmp(p, old) != 0){
				seterr(EStrange, "score collision");
				ok = -1;
			}
			packetfree(p);
			packetfree(old);

			return ok;
		}
		logerr(EAdmin, "writelump: read %V failed, rewriting: %r\n", u->score);
	}

	flat = packet2zblock(p, packetsize(p));
	ok = storeclump(mainindex, flat, u->score, u->type, creator, &ia);
	freezblock(flat);
	if(ok == 0)
		ok = insertscore(u->score, &ia, 1);
	if(ok == 0)
		insertlump(u, p);
	else
		packetfree(p);

	return ok;
}

static void
readahead(u64int a, Arena *arena, u64int aa, int n)
{	
	u8int buf[ClumpSize];
	Clump cl;
	IAddr ia;

	while(n > 0) {
		if (aa >= arena->used)
			break;
		if(readarena(arena, aa, buf, ClumpSize) < ClumpSize)
			break;
		if(unpackclump(&cl, buf) < 0)
			break;
		ia.addr = a;
		ia.type = cl.info.type;
		ia.size = cl.info.uncsize;
		ia.blocks = (cl.info.size + ClumpSize + (1 << ABlockLog) - 1) >> ABlockLog;
		insertscore(cl.info.score, &ia, 0);
		a += ClumpSize + cl.info.size;
		aa += ClumpSize + cl.info.size;
		n--;
	}
}

static Packet*
readilump(Lump *u, IAddr *ia, u8int *score, int rac)
{
	Arena *arena;
	ZBlock *zb;
	Packet *p, *pp;
	Clump cl;
	u64int a, aa;
	u8int sc[VtScoreSize];

	arena = amapitoa(mainindex, ia->addr, &aa);
	if(arena == nil)
		return nil;

	zb = loadclump(arena, aa, ia->blocks, &cl, sc, paranoid);
	if(zb == nil)
		return nil;

	if(ia->size != cl.info.uncsize){
		seterr(EInconsist, "index and clump size mismatch");
		freezblock(zb);
		return nil;
	}
	if(ia->type != cl.info.type){
		seterr(EInconsist, "index and clump type mismatch");
		freezblock(zb);
		return nil;
	}
	if(scorecmp(score, sc) != 0){
		seterr(ECrash, "score mismatch");
		freezblock(zb);
		return nil;
	}

	if(rac == 0) {
		a = ia->addr + ClumpSize + cl.info.size;
		aa += ClumpSize + cl.info.size;
		readahead(a, arena, aa, 20);
	}

	p = zblock2packet(zb, cl.info.uncsize);
	freezblock(zb);
	pp = packetdup(p, 0, packetsize(p));
	insertlump(u, pp);
	return p;
}