#include <u.h>
#include <libc.h>
#include <String.h>
#include "ftpfs.h"

enum
{
	Chunk=		1024,		/* chunk size for buffered data */
	Nfile=		128,		/* maximum number of cached files */
};

/* a file (with cached data) */
struct File
{
	char	*mem;		/* part of file cached in memory */
	ulong	len;		/* length of cached data */
	long	off;		/* current offset into tpath */
	short	fd;		/* fd to cache file */
	char	inuse;
	char	dirty;
	ulong	atime;		/* time of last access */
	Node	*node;
	char 	*template;
};

static File	files[Nfile];
static ulong	now;
static int	ntmp;

/*
 *  lookup a file, create one if not found.  if there are no
 *  free files, free the last oldest clean one.
 */
static File*
fileget(Node *node)
{
	File *fp;
	File *oldest;

	fp = node->fp;
	if(fp)
		return fp;

	oldest = 0;
	for(fp = files; fp < &files[Nfile]; fp++){
		if(fp->inuse == 0)
			break;
		if(fp->dirty == 0 && (oldest == 0 || oldest->atime > fp->atime))
			oldest = fp;
	}

	if(fp == &files[Nfile]){
		uncache(oldest->node);
		fp = oldest;
	}
	node->fp = fp;
	fp->node = node;
	fp->atime = now++;
	fp->inuse = 1;
	fp->fd = -1;
	if(fp->mem){
		free(fp->mem);
		fp->mem = nil;
	}
	return fp;
}

/*
 *  free a cached file
 */
void
filefree(Node *node)
{
	File *fp;

	fp = node->fp;
	if(fp == 0)
		return;

	if(fp->fd >= 0){
		ntmp--;
		close(fp->fd);
		remove(fp->template);
		free(fp->template);
		fp->template = 0;
	}
	fp->fd = -1;
	if(fp->mem){
		free(fp->mem);
		fp->mem = nil;
	}
	fp->len = 0;
	fp->inuse = 0;
	fp->dirty = 0;

	node->fp = 0;
}

/*
 *  satisfy read first from in memory chunk and then from temporary
 *  file.  It's up to the caller to make sure that the file is valid.
 */
int
fileread(Node *node, char *a, long off, int n)
{
	int sofar;
	int i;
	File *fp;

	fp = node->fp;
	if(fp == 0)
		fatal("fileread");

	if(off + n > fp->len)
		n = fp->len - off;

	for(sofar = 0; sofar < n; sofar += i, off += i, a += i){
		if(off >= fp->len)
			return sofar;
		if(off < Chunk){
			i = n;
			if(off + i > Chunk)
				i = Chunk - off;
			memmove(a, fp->mem + off, i);
			continue;
		}
		if(fp->off != off)
			if(seek(fp->fd, off, 0) < 0){
				fp->off = -1;
				return -1;
			}
		i = read(fp->fd, a, n-sofar);
		if(i < 0){
			fp->off = -1;
			return -1;
		}
		if(i == 0)
			break;
		fp->off = off + i;
	}
	return sofar;
}

void
uncachedir(Node *parent, Node *child)
{
	Node *sp;

	if(parent == 0 || parent == child)
		return;
	for(sp = parent->children; sp; sp = sp->sibs)
		if(sp->opens == 0)
		if(sp != child)
		if(sp->fp != nil)
		if(sp->fp->dirty == 0)
		if(sp->fp->fd >= 0){
			filefree(sp);
			UNCACHED(sp);
		}
}

static int
createtmp(File *fp)
{
	char template[32];

	strcpy(template, "/tmp/ftpXXXXXXXXXXX");
	mktemp(template);
	if(strcmp(template, "/") == 0){
		fprint(2, "ftpfs can't create tmp file %s: %r\n", template);
		return -1;
	}
	if(ntmp >= 16)
		uncachedir(fp->node->parent, fp->node);

	fp->fd = create(template, ORDWR|ORCLOSE, 0600);
	fp->template = strdup(template);
	fp->off = 0;
	ntmp++;
	return fp->fd;
}

/*
 *  write cached data (first Chunk bytes stay in memory)
 */
int
filewrite(Node *node, char *a, long off, int n)
{
	int i, sofar;
	File *fp;

	fp = fileget(node);

	if(fp->mem == nil){
		fp->mem = malloc(Chunk);
		if(fp->mem == nil)
			return seterr("out of memory");
	}

	for(sofar = 0; sofar < n; sofar += i, off += i, a += i){
		if(off < Chunk){
			i = n;
			if(off + i > Chunk)
				i = Chunk - off;
			memmove(fp->mem + off, a, i);
			continue;
		}
		if(fp->fd < 0)
			if(createtmp(fp) < 0)
				return seterr("can't create temp file");
		if(fp->off != off)
			if(seek(fp->fd, off, 0) < 0){
				fp->off = -1;
				return seterr("can't seek temp file");
			}
		i = write(fp->fd, a, n-sofar);
		if(i <= 0){
			fp->off = -1;
			return seterr("can't write temp file");
		}
		fp->off = off + i;
	}

	if(off > fp->len)
		fp->len = off;
	if(off > node->d->length)
		node->d->length = off;
	return sofar;
}

/*
 *  mark a file as dirty
 */
void
filedirty(Node *node)
{
	File *fp;

	fp = fileget(node);
	fp->dirty = 1;
}

/*
 *  mark a file as clean
 */
void
fileclean(Node *node)
{
	if(node->fp)
		node->fp->dirty = 0;
}

int
fileisdirty(Node *node)
{
	return node->fp && node->fp->dirty;
}
