#include	"u.h"
#include	"lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"error.h"

#include	"netif.h"

typedef struct Ram	Ram;
struct Ram
{
	QLock lk;
	Ram	*next;
	int	ref;
	/* simple for now */
	uchar **pages;
	int pagecount;
	int size;
	int	qref[2];
	ulong	path;
};

struct
{
	Lock lk;
	ulong	path;
} ramalloc;

enum
{
	Qdir,
	Qdata0,
	Qctl,
};

Dirtab ramdir[] =
{
	".",		{Qdir,0,QTDIR},	0,		DMDIR|0500,
	"data",		{Qdata0},	0,		0600,
	"ctl",	{Qctl},	0,		0600,
};
#define NPIPEDIR 3

static void
raminit(void)
{
}

/*
 *  create a ram, no streams are created until an open
 */
static Chan*
ramattach(char *spec)
{
	Ram *p;
	Chan *c;

	c = devattach('R', spec);
	p = malloc(sizeof(Ram));
	if(p == 0)
		exhausted("memory");
	p->ref = 1;
	p->size = 0;
	p->pagecount = 1;
	p->pages = mallocz(sizeof(char *), 1);
	p->pages[0] = mallocz(BY2PG, 1);
	lock(&ramalloc.lk);
	p->path = ++ramalloc.path;
	unlock(&ramalloc.lk);

	mkqid(&c->qid, NETQID(2*p->path, Qdir), 0, QTDIR);
	c->aux = p;
	c->dev = 0;
	return c;
}

static int
ramgen(Chan *c, char *name, Dirtab *tab, int ntab, int i, Dir *dp)
{
	Qid q;
	int len;
	Ram *p;

	if(i == DEVDOTDOT){
		devdir(c, c->qid, "#R", 0, eve, DMDIR|0555, dp);
		return 1;
	}
	i++;	/* skip . */
	if(tab==0 || i>=ntab)
		return -1;

	tab += i;
	p = c->aux;
	switch((ulong)tab->qid.path){
	case Qdata0:
		len = p->size;
		break;
	case Qctl:
		len = 0;
		break;
	default:
		len = tab->length;
		break;
	}
	mkqid(&q, NETQID(NETID(c->qid.path), tab->qid.path), 0, QTFILE);
	devdir(c, q, tab->name, len, eve, tab->perm, dp);
	return 1;
}


static Walkqid*
ramwalk(Chan *c, Chan *nc, char **name, int nname)
{
	Walkqid *wq;
	Ram *p;

	wq = devwalk(c, nc, name, nname, ramdir, NPIPEDIR, ramgen);
	if(wq != nil && wq->clone != nil && wq->clone != c){
		p = c->aux;
		qlock(&p->lk);
		p->ref++;
		if(c->flag & COPEN){
			print("channel open in ramwalk\n");
			switch(NETTYPE(c->qid.path)){
			case Qdata0:
				p->qref[0]++;
				break;
			case Qctl:
				p->qref[1]++;
				break;
			}
		}
		qunlock(&p->lk);
	}
	return wq;
}

static int
ramstat(Chan *c, uchar *db, int n)
{
	Ram *p;
	Dir dir;

	p = c->aux;

	switch(NETTYPE(c->qid.path)){
	case Qdir:
		devdir(c, c->qid, ".", 0, eve, DMDIR|0555, &dir);
		break;
	case Qdata0:
		devdir(c, c->qid, "data", p->size, eve, 0600, &dir);
		break;
	case Qctl:
		devdir(c, c->qid, "ctl", 0, eve, 0600, &dir);
		break;
	default:
		panic("ramstat");
	}
	n = convD2M(&dir, db, n);
	if(n < BIT16SZ)
		error(Eshortstat);
	return n;
}

/*
 *  if the stream doesn't exist, create it
 */
static Chan*
ramopen(Chan *c, int omode)
{
	Ram *p;

	if(c->qid.type & QTDIR){
		if(omode != OREAD)
			error(Ebadarg);
		c->mode = omode;
		c->flag |= COPEN;
		c->offset = 0;
		return c;
	}

	p = c->aux;
	qlock(&p->lk);
	switch(NETTYPE(c->qid.path)){
	case Qdata0:
		p->qref[0]++;
		break;
	case Qctl:
		p->qref[1]++;
		break;
	}
	qunlock(&p->lk);

	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;
	c->iounit = qiomaxatomic;
	return c;
}

static void
ramclose(Chan *c)
{
	Ram *p;

	p = c->aux;
	qlock(&p->lk);

	if(c->flag & COPEN){
		switch(NETTYPE(c->qid.path)){
		case Qdata0:
			p->qref[0]--;
			break;
		case Qctl:
			p->qref[1]--;
			break;
		}
	}

	/*
	 *  free the structure on last close
	 */
	p->ref--;
	if(p->ref == 0){
		int i;
		qunlock(&p->lk);
		for(i = 0; i < p->pagecount; i++)
			free(p->pages[i]);
		free(p->pages);
		free(p);
	} else
		qunlock(&p->lk);
}

static long 
rampageread(Ram *p, void *va, long n, vlong offset)
{
	int i;
	long total, offinpage, leninpage;

	total = n;

	/* figure out what range we can actually read */
	if(offset > p->size)
		return 0;
	if(offset + n > p->size) 
		n = p->size - offset;
	/* granular copy */
	for(i = offset / BY2PG; n > 0; i++) {
		/* i is the page */
		offinpage = offset & (BY2PG - 1);
		leninpage = BY2PG - offinpage;
		/* unless there is too little left ... */
		if(leninpage > n)
			leninpage = n;
		memcpy(va, p->pages[i] + offinpage, leninpage);
		offset += offinpage;
		n -= leninpage; 
		va += leninpage;
	}
	return total;
}

static long
ramread(Chan *c, void *va, long n, vlong offset)
{
	Ram *p;
	char *buf, *s, *e;

	p = c->aux;

	switch(NETTYPE(c->qid.path)){
	case Qdir:
		return devdirread(c, va, n, ramdir, NPIPEDIR, ramgen);
	case Qdata0:
		return rampageread(p, va, n, offset);
	case Qctl:
		buf = smalloc(8192);
		s = buf;
		e = buf + 8192;
		s = seprint(s, e, "pages %p count %d ", p->pages, p->pagecount);
		seprint(s, e, "size %d\n", p->size);
		n = readstr(offset, va, n, buf);
		free(buf);
		return n;
	default:
		panic("ramread");
	}
	return -1;	/* not reached */
}

/* for the range offset .. offset + n, make sure we have pages */
static 
void pages(Ram *p, long n, vlong offset)
{
	int i;
	int newpagecount;
	uchar **newpages;

	newpagecount = (offset + n + BY2PG-1)/BY2PG;
	if(newpagecount > p->pagecount) {
		newpages = mallocz(sizeof(char *) * newpagecount, 1);
		if(!newpages)
			error("No more pages in devram");
		memcpy(newpages, p->pages, sizeof(char *) * p->pagecount);
		free(p->pages);
		p->pages = newpages;
		p->pagecount = newpagecount;
		/* now allocate them */
		for(i = offset / BY2PG; i < newpagecount; i++) {
			if(p->pages[i])
				continue;
			p->pages[i] = mallocz(BY2PG, 1);
		}
	}
}

static long 
rampagewrite(Ram *p, void *va, long n, vlong offset)
{
	int i;
	long total, offinpage, leninpage;
	long newsize;

	total = n;
	pages(p, n, offset);
	
	/* granular copy */
	newsize = offset + n;
	for(i = offset / BY2PG; n > 0; i++) {
		/* i is the page */
		offinpage = offset & (BY2PG - 1);
		leninpage = BY2PG - offinpage;
		/* unless there is too little left ... */
		if(leninpage > n)
			leninpage = n;
		memcpy(p->pages[i] + offinpage, va, leninpage);
		offset += leninpage;
		n -= leninpage; 
		va += leninpage;
	}
	p->size = newsize > p->size? newsize : p->size;
	return total;
}
static long
ramwrite(Chan *c, void *va, long n, vlong offset)
{
	Ram *p;
	int i;
	uchar **new;
	uchar *page;

	if(!islo())
		print("ramwrite hi %lux\n", getcallerpc(&c));
	p = c->aux;
	switch(NETTYPE(c->qid.path)){
	case Qdata0:
		n = rampagewrite(p, va, n, offset);
		break;

	case Qctl:
		if(strcmp(va, "free") == 0) {
			new = mallocz(sizeof(char *), 1);
			page = p->pages[0];
			for(i = 1; i < p->pagecount; i++)
				free(p->pages[i]);
			free(p->pages);
			p->pages = new;
			p->pages[0] = page;
			p->size = 0;
			p->pagecount = 1;
		} else {
			error("bad command");
		}
		break;

	default:
		panic("ramwrite");
	}

	return n;
}


Dev ramdevtab = {
	'R',
	"ram",

	devreset,
	raminit,
	devshutdown,
	ramattach,
	ramwalk,
	ramstat,
	ramopen,
	devcreate,
	ramclose,
	ramread,
	devbread,
	ramwrite,
	devbwrite,
	devremove,
	devwstat,
};
