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

typedef struct Label Label;

enum {
	BadHeap = ~0,
};

/*
 * the plan is to store data to the cache in c->size blocks
 * with the block zero extended to fill it out.  When writing to
 * venti, the block will be zero truncated.  The walker will also check
 * that the block fits within psize or dsize as the case may be.
 */

struct Cache
{
	VtLock	*lk;
	VtSession *z;
	u32int	now;			/* ticks for usage timestamps */
	int	size;			/* max. size of any block; allocated to each block */
	Lump	**heads;		/* hash table for finding address */
	int	nheap;			/* number of available victims */
	Lump	**heap;			/* heap for locating victims */
	long	nblocks;		/* number of blocks allocated */
	Lump	*blocks;		/* array of block descriptors */
	u8int	*mem;			/* memory for all block descriptors */
	Lump	*free;			/* free list of lumps */

	long hashSize;
};

/*
 * the tag for a block is hash(index, parent tag)
 */

struct Label {
	uchar gen[4];
	uchar state;
	uchar type;		/* top bit indicates it is part of a directory */
	uchar tag[4];		/* tag of file it is in */
};


static char ENoDir[] = "directory entry is not allocated";

static void fixHeap(int si, Lump *b);
static int upHeap(int i, Lump *b);
static int downHeap(int i, Lump *b);
static char	*lumpState(int);
static void	lumpSetState(Lump *u, int state);

Cache *
cacheAlloc(VtSession *z, int blockSize, long nblocks)
{
	int i;
	Cache *c;
	Lump *b;

	c = vtMemAllocZ(sizeof(Cache));
	
	c->lk = vtLockAlloc();
	c->z = z;
	c->size = blockSize;
	c->nblocks = nblocks;
	c->hashSize = nblocks;
	c->heads = vtMemAllocZ(c->hashSize*sizeof(Lump*));
	c->heap = vtMemAllocZ(nblocks*sizeof(Lump*));
	c->blocks = vtMemAllocZ(nblocks*sizeof(Lump));
	c->mem = vtMemAllocZ(nblocks * blockSize);
	for(i = 0; i < nblocks; i++){
		b = &c->blocks[i];
		b->lk = vtLockAlloc();
		b->c = c;
		b->data = &c->mem[i * blockSize];
		b->addr = i+1;
		b->state = LumpFree;
		b->heap = BadHeap;
		b->next = c->free;
		c->free = b;
	}
	c->nheap = 0;

	return c;
}

long
cacheGetSize(Cache *c)
{
	return c->nblocks;
}

int
cacheGetBlockSize(Cache *c)
{
	return c->size;
}

int
cacheSetSize(Cache *c, long nblocks)
{
	USED(c);
	USED(nblocks);
	return 0;
}

void
cacheFree(Cache *c)
{
	int i;

	for(i = 0; i < c->nblocks; i++){
		assert(c->blocks[i].ref == 0);
		vtLockFree(c->blocks[i].lk);
	}
	vtMemFree(c->heads);
	vtMemFree(c->blocks);
	vtMemFree(c->mem);
	vtMemFree(c);
}

static u32int
hash(Cache *c, uchar score[VtScoreSize], int type)
{
	u32int h;
	uchar *p = score + VtScoreSize-4;

	h = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
	h += type;
	return h % c->hashSize;
}

static void
findLump(Cache *c, Lump *bb)
{
	Lump *b, *last;
	int h;

	last = nil;
	h = hash(c, bb->score, bb->type);
	for(b = c->heads[h]; b != nil; b = b->next){
		if(last != b->prev)
			vtFatal("bad prev link");
		if(b == bb)
			return;
		last = b;
	}
	vtFatal("block missing from hash table");
}

void
cacheCheck(Cache *c)
{
	u32int size, now;
	int i, k, refed, free;
	static uchar zero[VtScoreSize];
	Lump *p;

	size = c->size;
	now = c->now;

	free = 0;
	for(p=c->free; p; p=p->next)
		free++;
	for(i = 0; i < c->nheap; i++){
		if(c->heap[i]->heap != i)
			vtFatal("mis-heaped at %d: %d", i, c->heap[i]->heap);
		if(i > 0 && c->heap[(i - 1) >> 1]->used2 - now > c->heap[i]->used2 - now)
			vtFatal("bad heap ordering");
		k = (i << 1) + 1;
		if(k < c->nheap && c->heap[i]->used2 - now > c->heap[k]->used2 - now)
			vtFatal("bad heap ordering");
		k++;
		if(k < c->nheap && c->heap[i]->used2 - now > c->heap[k]->used2 - now)
			vtFatal("bad heap ordering");
	}

	refed = 0;
	for(i = 0; i < c->nblocks; i++){
		if(c->blocks[i].data != &c->mem[i * size])
			vtFatal("mis-blocked at %d", i);
		if(c->blocks[i].ref && c->blocks[i].heap == BadHeap){
			refed++;
		}
		if(memcmp(zero, c->blocks[i].score, VtScoreSize))
			findLump(c, &c->blocks[i]);
	}
if(refed > 0)fprint(2, "cacheCheck: nheap %d refed %d free %d\n", c->nheap, refed, free);
	assert(c->nheap + refed + free == c->nblocks);
	refed = 0;
	for(i = 0; i < c->nblocks; i++){
		if(c->blocks[i].ref) {
if(1)fprint(2, "%d %V %d %s\n", c->blocks[i].type, c->blocks[i].score, c->blocks[i].ref, lumpState(c->blocks[i].state));
			refed++;
		}
	}
if(refed > 0)fprint(2, "cacheCheck: in used %d\n", refed);
}

/*
 * delete an arbitrary block from the heap
 */
static void
delHeap(Lump *db)
{
	fixHeap(db->heap, db->c->heap[--db->c->nheap]);
	db->heap = BadHeap;
}

static void
fixHeap(int si, Lump *b)
{
	int i;

	i = upHeap(si, b);
	if(i == si)
		downHeap(i, b);
}

static int
upHeap(int i, Lump *b)
{
	Lump *bb;
	u32int now;
	int p;
	Cache *c;
	
	c = b->c;
	now = c->now;
	for(; i != 0; i = p){
		p = (i - 1) >> 1;
		bb = c->heap[p];
		if(b->used2 - now >= bb->used2 - now)
			break;
		c->heap[i] = bb;
		bb->heap = i;
	}
	c->heap[i] = b;
	b->heap = i;

	return i;
}

static int
downHeap(int i, Lump *b)
{
	Lump *bb;
	u32int now;
	int k;
	Cache *c;
	
	c = b->c;
	now = c->now;
	for(; ; i = k){
		k = (i << 1) + 1;
		if(k >= c->nheap)
			break;
		if(k + 1 < c->nheap && c->heap[k]->used2 - now > c->heap[k + 1]->used2 - now)
			k++;
		bb = c->heap[k];
		if(b->used2 - now <= bb->used2 - now)
			break;
		c->heap[i] = bb;
		bb->heap = i;
	}
	c->heap[i] = b;
	b->heap = i;
	return i;
}


/* called with c->lk held */
Lump *
cacheBumpLump(Cache *c)
{
	Lump *b;

	/*
	 * missed: locate the block with the oldest second to last use.
	 * remove it from the heap, and fix up the heap.
	 */
	if(c->free) {
		b = c->free;
		c->free = b->next;
	} else {
		for(;;){
			if(c->nheap == 0) {
				cacheCheck(c);
				assert(0);
				return nil;
			}
			b = c->heap[0];
			delHeap(b);
			if(b->ref == 0)
				break;
		}

		/*
		 * unchain the block from hash chain
		 */
		if(b->prev == nil)
			c->heads[hash(c, b->score, b->type)] = b->next;
		else
			b->prev->next = b->next;
		if(b->next != nil)
			b->next->prev = b->prev;

	}

	/*
	 * the new block has no last use, so assume it happens sometime in the middle
	 */
	b->used = (b->used2 + c->now) / 2;
	b->asize = 0;

	return b;
}

Lump *
cacheAllocLump(Cache *c, int type, int size, int dir)
{
	Lump *b;
	ulong h;

	assert(size <= c->size);

again:
	vtLock(c->lk);
	b = cacheBumpLump(c);
	if(b == nil) {
		vtUnlock(c->lk);
fprint(2, "cache is full\n");
		/* XXX should be better */
		sleep(100);
		goto again;
	}

	vtLock(b->lk);

	assert(b->ref == 0);
	b->ref++;
	b->used2 = b->used;
	b->used = c->now++;

	/* convert addr into score */
	memset(b->score, 0, VtScoreSize-4);
	b->score[VtScoreSize-4] = b->addr>>24;
	b->score[VtScoreSize-3] = b->addr>>16;
	b->score[VtScoreSize-2] = b->addr>>8;
	b->score[VtScoreSize-1] = b->addr;
	
	b->dir = dir;
	b->type = type;
	b->gen = 0;
	b->asize = size;
	b->state = LumpFree;

	h = hash(c, b->score, b->type);

	/* chain onto correct hash */
	b->next = c->heads[h];
	c->heads[h] = b;
	if(b->next != nil)
		b->next->prev = b;
	b->prev = nil;

	vtUnlock(c->lk);

	vtZeroExtend(type, b->data, 0, size);
	lumpSetState(b, LumpActive);

	return b;
}

int
scoreIsLocal(uchar score[VtScoreSize])
{
	static uchar zero[VtScoreSize];
	
	return memcmp(score, zero, VtScoreSize-4) == 0;
}

Lump *
cacheGetLump(Cache *c, uchar score[VtScoreSize], int type, int size)
{
	Lump *b;
	ulong h;
	int n;
	static uchar zero[VtScoreSize];

	assert(size <= c->size);

	h = hash(c, score, type);

again:
	/*
	 * look for the block in the cache
	 */
	vtLock(c->lk);
	for(b = c->heads[h]; b != nil; b = b->next){
		if(memcmp(b->score, score, VtScoreSize) == 0 && b->type == type)
			goto found;
	}

	/* should not be looking for a temp block */
	if(scoreIsLocal(score)) {
		if(memcmp(score, zero, VtScoreSize) == 0)
			vtSetError("looking for zero score");
		else
			vtSetError("missing local block");
		vtUnlock(c->lk);
		return nil;
	}

	b = cacheBumpLump(c);
	if(b == nil) {
		vtUnlock(c->lk);
		sleep(100);
		goto again;
	}

	/* chain onto correct hash */
	b->next = c->heads[h];
	c->heads[h] = b;
	if(b->next != nil)
		b->next->prev = b;
	b->prev = nil;

	memmove(b->score, score, VtScoreSize);	
	b->type = type;
	b->state = LumpFree;

found:
	b->ref++;
	b->used2 = b->used;
	b->used = c->now++;
	if(b->heap != BadHeap)
		fixHeap(b->heap, b);

	vtUnlock(c->lk);

	vtLock(b->lk);
	if(b->state != LumpFree)
		return b;
	
	n = vtRead(c->z, score, type, b->data, size);
	if(n < 0) {
fprint(2, "vtRead failed: %V %d %d: %R\n", score, type, size);
abort();
		lumpDecRef(b, 1);
		return nil;
	}
	if(!vtSha1Check(score, b->data, n)) {
		vtSetError("vtSha1Check failed");
		lumpDecRef(b, 1);
		return nil;
	}
	vtZeroExtend(type, b->data, n, size);
	b->asize = size;
	lumpSetState(b, LumpVenti);

	return b;
}

static char *
lumpState(int state)
{
	switch(state) {
	default:
		return "Unknown!!";
	case LumpFree:
		return "Free";
	case LumpActive:
		return "Active";
	case LumpSnap:
		return "Snap";
	case LumpZombie:
		return "Zombie";
	case LumpVenti:
		return "Venti";
	}
}

static void
lumpSetState(Lump *u, int state)
{
//	if(u->state != LumpFree)
//		fprint(2, "%V: %s -> %s\n", u->score, lumpState(u->state), lumpState(state));
	u->state = state;
}
	
int
lumpGetScore(Lump *u, int offset, uchar score[VtScoreSize])
{
	uchar *sp;
	VtRoot root;
	VtEntry dir;

	vtLock(u->lk);

	switch(u->type) {
	default:
		vtSetError("bad type");
		goto Err;
	case VtPointerType0:
	case VtPointerType1:
	case VtPointerType2:
	case VtPointerType3:
	case VtPointerType4:
	case VtPointerType5:
	case VtPointerType6:
		if((offset+1)*VtScoreSize > u->asize)
			sp = nil;
		else
			sp = u->data + offset*VtScoreSize;
		break;
	case VtRootType:
		if(u->asize < VtRootSize) {
			vtSetError("runt root block");
			goto Err;
		}
		if(!vtRootUnpack(&root, u->data))
			goto Err;
		sp = root.score;
		break;
	case VtDirType:
		if((offset+1)*VtEntrySize > u->asize) {
			vtSetError(ENoDir);
			goto Err;
		}
		if(!vtEntryUnpack(&dir, u->data, offset))
			goto Err;
		if(!dir.flags & VtEntryActive) {
			vtSetError(ENoDir);
			goto Err;
		}
		sp = dir.score;
		break;
	}

	if(sp == nil)
		memmove(score, vtZeroScore, VtScoreSize);
	else
		memmove(score, sp, VtScoreSize);

	vtUnlock(u->lk);
	return !scoreIsLocal(score);
Err:
	vtUnlock(u->lk);
	return 0;
}

Lump *
lumpWalk(Lump *u, int offset, int type, int size, int readOnly, int lock)
{
	Lump *v, *vv;
	Cache *c;
	uchar score[VtScoreSize], *sp;
	VtRoot root;
	VtEntry dir;
	int split, isdir;

	c = u->c;
	vtLock(u->lk);

Again:
	v = nil;
	vv = nil;

	isdir = u->dir;
	switch(u->type) {
	default:
		vtSetError("bad type");
		goto Err;
	case VtPointerType0:
	case VtPointerType1:
	case VtPointerType2:
	case VtPointerType3:
	case VtPointerType4:
	case VtPointerType5:
	case VtPointerType6:
		if((offset+1)*VtScoreSize > u->asize)
			sp = nil;
		else
			sp = u->data + offset*VtScoreSize;
		break;
	case VtRootType:
		if(u->asize < VtRootSize) {
			vtSetError("runt root block");
			goto Err;
		}
		if(!vtRootUnpack(&root, u->data))
			goto Err;
		sp = root.score;
		break;
	case VtDirType:
		if((offset+1)*VtEntrySize > u->asize) {
			vtSetError(ENoDir);
			goto Err;
		}
		if(!vtEntryUnpack(&dir, u->data, offset))
			goto Err;
		if(!(dir.flags & VtEntryActive)) {
			vtSetError(ENoDir);
			goto Err;
		}
		isdir = (dir.flags & VtEntryDir) != 0;
//		sp = dir.score;
		sp = u->data + offset*VtEntrySize + 20;
		break;
	}

	if(sp == nil)
		memmove(score, vtZeroScore, VtScoreSize);
	else
		memmove(score, sp, VtScoreSize);
	
	vtUnlock(u->lk);


if(0)fprint(2, "lumpWalk: %V:%s %d:%d-> %V:%d\n", u->score, lumpState(u->state), u->type, offset, score, type);
	v = cacheGetLump(c, score, type, size);
	if(v == nil)
		return nil;

	split = 1;
	if(readOnly)
		split = 0;

	switch(v->state) {
	default:
		assert(0);
	case LumpFree:
fprint(2, "block is free %V!\n", v->score);
		vtSetError("phase error");
		goto Err2;
	case LumpActive:	
		if(v->gen < u->gen) {
print("LumpActive gen\n");
			lumpSetState(v, LumpSnap);
			v->gen = u->gen;
		} else
			split = 0;
		break;
	case LumpSnap:
	case LumpVenti:
		break;
	}
	
	/* easy case */
	if(!split) {
		if(!lock)
			vtUnlock(v->lk);
		return v;
	}

	if(sp == nil) {
		vtSetError("bad offset");
		goto Err2;
	}

	vv = cacheAllocLump(c, v->type, size, isdir);
	/* vv is locked */
	vv->gen = u->gen;
	memmove(vv->data, v->data, v->asize);
if(0)fprint(2, "split %V into %V\n", v->score, vv->score);

	lumpDecRef(v, 1);
	v = nil;

	vtLock(u->lk);
	if(u->state != LumpActive) {
		vtSetError("bad parent state: can not happen");
		goto Err;
	}

	/* check that nothing changed underfoot */
	if(memcmp(sp, score, VtScoreSize) != 0) {
		lumpDecRef(vv, 1);
fprint(2, "lumpWalk: parent changed under foot\n");
		goto Again;
	}

	/* XXX - hold Active blocks up - will go eventually */
	lumpIncRef(vv);

	/* change the parent */
	memmove(sp, vv->score, VtScoreSize);
	
	vtUnlock(u->lk);
	
	if(!lock)
		vtUnlock(vv->lk);
	return vv;
Err:
	vtUnlock(u->lk);
	lumpDecRef(v, 0);
	lumpDecRef(vv, 1);
	return nil;
Err2:
	lumpDecRef(v, 1);
	return nil;
	
}

void
lumpFreeEntry(Lump *u, int entry)
{
	uchar score[VtScoreSize];
	int type;
	ulong gen;
	VtEntry dir;
	Cache *c;

	c = u->c;
	vtLock(u->lk);
	if(u->state == LumpVenti)
		goto Exit;

	switch(u->type) {
	default:
		fprint(2, "freeing bad lump type: %d\n", u->type);
		return;
	case VtPointerType0:
		if((entry+1)*VtScoreSize > u->asize)
			goto Exit;
		memmove(score, u->data + entry*VtScoreSize, VtScoreSize);
		memmove(u->data + entry*VtScoreSize, vtZeroScore, VtScoreSize);
		type = u->dir?VtDirType:VtDataType;
		break;
	case VtPointerType1:
	case VtPointerType2:
	case VtPointerType3:
	case VtPointerType4:
	case VtPointerType5:
	case VtPointerType6:
		if((entry+1)*VtScoreSize > u->asize)
			goto Exit;
		memmove(score, u->data + entry*VtScoreSize, VtScoreSize);
		memmove(u->data + entry*VtScoreSize, vtZeroScore, VtScoreSize);
		type = u->type-1;
		break;
	case VtDirType:
		if((entry+1)*VtEntrySize > u->asize)
			goto Exit;
		if(!vtEntryUnpack(&dir, u->data, entry))
			goto Exit;
		if(!dir.flags & VtEntryActive)
			goto Exit;
		gen = dir.gen;
		if(gen != ~0)
			gen++;
		if(dir.depth == 0)
			type = (dir.flags&VtEntryDir)?VtDirType:VtDataType;
		else
			type = VtPointerType0 + dir.depth - 1;
		memmove(score, dir.score, VtScoreSize);
		memset(&dir, 0, sizeof(dir));
		dir.gen = gen;
		vtEntryPack(&dir, u->data, entry);
		break;
	case VtDataType:
		type = VtErrType;
		break;
	}
	vtUnlock(u->lk);
	if(type == VtErrType || !scoreIsLocal(score))
		return;

	u = cacheGetLump(c, score, type, c->size);
	if(u == nil)
		return;
	lumpDecRef(u, 1);
	/* XXX remove extra reference */
	lumpDecRef(u, 0);
	return;
Exit:
	vtUnlock(u->lk);
	return;

}

void
lumpCleanup(Lump *u)
{
	int i, n;

	switch(u->type) {
	default:
		return;
	case VtPointerType0:
	case VtPointerType1:
	case VtPointerType2:
	case VtPointerType3:
	case VtPointerType4:
	case VtPointerType5:
	case VtPointerType6:
		n = u->asize/VtScoreSize;
		break;	
	case VtDirType:
		n = u->asize/VtEntrySize;
		break;
	}

	for(i=0; i<n; i++)
		lumpFreeEntry(u, i);
}


void
lumpDecRef(Lump *b, int unlock)
{
	int i;
	Cache *c;

	if(b == nil)
		return;

	if(unlock)
		vtUnlock(b->lk);

	c = b->c;
	vtLock(c->lk);
	if(--b->ref > 0) {
		vtUnlock(c->lk);
		return;
	}
	assert(b->ref == 0);

	switch(b->state) {
	default:
		fprint(2, "bad state: %s\n", lumpState(b->state));
		assert(0);
	case LumpActive:
		/* hack - but will do for now */
		b->ref++;
		vtUnlock(c->lk);
		lumpCleanup(b);
		vtLock(c->lk);
		b->ref--;
		lumpSetState(b, LumpFree);
		break;
	case LumpZombie:
		lumpSetState(b, LumpFree);
		break;
	case LumpFree:
	case LumpVenti:
		break;
	}

	/*
	 * reinsert in the free heap
	 */
	if(b->heap == BadHeap) {
		i = upHeap(c->nheap++, b);
		c->heap[i] = b;
		b->heap = i;
	}

	vtUnlock(c->lk);
}

Lump *
lumpIncRef(Lump *b)
{
	Cache *c;

	c = b->c;

	vtLock(c->lk);
	assert(b->ref > 0);
	b->ref++;
	vtUnlock(c->lk);
	return b;
}
