#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;
}