/*
 * This file is part of the UCB release of Plan 9. It is subject to the license
 * terms in the LICENSE file found in the top-level directory of this
 * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
 * part of the UCB release of Plan 9, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms contained
 * in the LICENSE file.
 */

#include <u.h>
#include <lib9.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <9P2000.h>
#include <plumb.h>
#include "dat.h"
#include "fns.h"

static	Block	*blist;

int
tempfile(void)
{
	char buf[128];
	int i, fd;

	snprint(buf, sizeof buf, "/tmp/X%d.%.4sacme", getpid(), getuser());
	for(i='A'; i<='Z'; i++){
		buf[5] = i;
		if(access(buf, AEXIST) == 0)
			continue;
		fd = ocreate(buf, ORDWR|ORCLOSE|OCEXEC, 0600);
		if(fd >= 0)
			return fd;
	}
	return -1;
}

Disk*
diskinit()
{
	Disk *d;

	d = emalloc(sizeof(Disk));
	d->fd = tempfile();
	if(d->fd < 0){
		fprint(2, "acme: can't create temp file: %r\n");
		threadexitsall("diskinit");
	}
	return d;
}

static
uint32_t
ntosize(uint32_t n, uint32_t *ip)
{
	uint32_t size;

	if(n > Maxblock)
		error("internal error: ntosize");
	size = n;
	if(size & (Blockincr-1))
		size += Blockincr - (size & (Blockincr-1));
	/* last bucket holds blocks of exactly Maxblock */
	if(ip)
		*ip = size/Blockincr;
	return size * sizeof(Rune);
}

Block*
disknewblock(Disk *d, uint32_t n)
{
	uint32_t i, j, size;
	Block *b;

	size = ntosize(n, &i);
	b = d->free[i];
	if(b)
		d->free[i] = b->next;
	else{
		/* allocate in chunks to reduce malloc overhead */
		if(blist == nil){
			blist = emalloc(100*sizeof(Block));
			for(j=0; j<100-1; j++)
				blist[j].next = &blist[j+1];
		}
		b = blist;
		blist = b->next;
		b->addr = d->addr;
		d->addr += size;
	}
	b->n = n;
	return b;
}

void
diskrelease(Disk *d, Block *b)
{
	uint32_t i;

	ntosize(b->n, &i);
	b->next = d->free[i];
	d->free[i] = b;
}

void
diskwrite(Disk *d, Block **bp, Rune *r, uint32_t n)
{
	int size, nsize;
	Block *b;

	b = *bp;
	size = ntosize(b->n, nil);
	nsize = ntosize(n, nil);
	if(size != nsize){
		diskrelease(d, b);
		b = disknewblock(d, n);
		*bp = b;
	}
	if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
		error("write error to temp file");
	b->n = n;
}

void
diskread(Disk *d, Block *b, Rune *r, uint32_t n)
{
	int tot, nr;
	char *p;

	if(n > b->n)
		error("internal error: diskread");

	ntosize(b->n, nil);
	n *= sizeof(Rune);
	p = (char*)r;
	for(tot = 0; tot < n; tot += nr){
		nr = pread(d->fd, p+tot, n-tot, b->addr+tot);
		if(nr <= 0)
			error("read error from temp file");
	}
	if(tot != n)
		error("read error from temp file");
}
