#include <u.h>
#define NOPLAN9DEFINES
#include <libc.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>

#define DBG if(0)
#define fatal sysfatal

extern int _p9dir(struct stat*, struct stat*, char*, Dir*, char**, char*);

#if defined(__DragonFly__)
static inline int d_reclen(struct dirent *de) { return _DIRENT_DIRSIZ(de); }
#else
static inline int d_reclen(struct dirent *de) { return de->d_reclen; }
#endif

static int
countde(char *p, int n)
{
	char *e;
	int m;
	struct dirent *de;

	e = p+n;
	m = 0;
	while(p < e){
		de = (struct dirent*)p;
		if(d_reclen(de) <= 4+2+2+1 || p+d_reclen(de) > e)
			break;
		if(de->d_name[0]=='.' && de->d_name[1]==0)
			de->d_name[0] = 0;
		else if(de->d_name[0]=='.' && de->d_name[1]=='.' && de->d_name[2]==0)
			de->d_name[0] = 0;
		m++;
		p += d_reclen(de);
	}
	return m;
}

static int
dirpackage(int fd, char *buf, int n, Dir **dp)
{
	int oldwd;
	char *p, *str, *estr;
	int i, nstr, m;
	struct dirent *de;
	struct stat st, lst;
	Dir *d;

	n = countde(buf, n);
	if(n <= 0)
		return n;

	if((oldwd = open(".", O_RDONLY)) < 0)
		return -1;
	if(fchdir(fd) < 0)
		return -1;
		
	p = buf;
	nstr = 0;

	for(i=0; i<n; i++){
		de = (struct dirent*)p;
		memset(&lst, 0, sizeof lst);
		if(de->d_name[0] == 0)
			/* nothing */ {}
		else if(lstat(de->d_name, &lst) < 0)
			de->d_name[0] = 0;
		else{
			st = lst;
			if(S_ISLNK(lst.st_mode))
				stat(de->d_name, &st);
			nstr += _p9dir(&lst, &st, de->d_name, nil, nil, nil);
		}
		p += d_reclen(de);
	}

	d = malloc(sizeof(Dir)*n+nstr);
	if(d == nil){
		fchdir(oldwd);
		close(oldwd);
		return -1;
	}
	str = (char*)&d[n];
	estr = str+nstr;

	p = buf;
	m = 0;
	for(i=0; i<n; i++){
		de = (struct dirent*)p;
		if(de->d_name[0] != 0 && lstat(de->d_name, &lst) >= 0){
			st = lst;
			if((lst.st_mode&S_IFMT) == S_IFLNK)
				stat(de->d_name, &st);
			_p9dir(&lst, &st, de->d_name, &d[m++], &str, estr);
		}
		p += d_reclen(de);
	}

	fchdir(oldwd);
	close(oldwd);
	*dp = d;
	return m;
}


/* return value
 * -1: on error
 * 0: on eof
 * others: filled bytes */
int
mreaddir(DIR *dirp, char *buf, int m)
{
	/* m is sizeof buf. must be > sizeof(struct dirent) */
	struct dirent *ep;
	int n,status;
	n = 0;
	for(;;){
		ep = (struct dirent *)(buf+n);
		status = readdir_r(dirp, ep, &ep);
		if(status || (ep == NULL))
			break;
		if(strcmp(ep->d_name,".") == 0 || strcmp(ep->d_name,"..") == 0)
			continue;
		DBG print("R %d %d %s\n",ep->d_namlen,ep->d_reclen,ep->d_name);
		n += ep->d_reclen;
		if(m -n < sizeof(struct dirent))
			break;
	}
	if(status)
		return -1;
	return n;
}

long
dirreadall(int fd, Dir **dp)
{
	static DIR *dir;
	struct dirent *ep;
	char *p,*buf,*bufend,*nbuf;
	int m, n, bufsz;
	int dentsz = sizeof(struct dirent);
	int allcsz = 1024;/* allocation unit */

	bufsz = allcsz + dentsz;/* what is the best value? */
	DBG print("#DBG mdirread begin\n");
	buf = malloc(bufsz);
	if(buf == nil)
		fatal("# malloc");

	dir = fdopendir(dup(fd));
	if(dir == nil)
		fatal("fdopendir");

	n = 0;
	for(;;){
		m = mreaddir(dir, buf+n, bufsz-n);
		if(m == 0)
			break;
		n += m;
		if(bufsz - n < dentsz){
			bufsz += allcsz;
			nbuf = realloc(buf, bufsz);
			if(nbuf == nil){
				free(buf);
				closedir(dir);
				return -1;
			}
			buf = nbuf;
		}
	}
	/* then n is num of effective bytes in buf */

	bufend = buf + n;
	for(p = buf; p < bufend;){
		ep = (struct dirent *)p;
		DBG print("C %d %d %s\n",ep->d_namlen,ep->d_reclen,ep->d_name);
		p += ep->d_reclen;
	}

	n = dirpackage(fd, buf, n, dp);

	free(buf);
	closedir(dir);

	return n;
}

long
dirread(int fd, Dir **dp)
{
	return dirreadall(fd,dp);
}

