#include <u.h>
#include <libc.h>
#include <bio.h>
#include <fcall.h>

typedef struct NDir NDir;
struct NDir
{
	Dir *d;
	char	*prefix;
};

int	errs = 0;
int	dflag;
int	lflag;
int	mflag;
int	nflag;
int	pflag;
int	qflag;
int	Qflag;
int	rflag;
int	sflag;
int	tflag;
int	Tflag;
int	uflag;
int	Fflag;
int	ndirbuf;
int	ndir;
NDir*	dirbuf;
int	ls(char*, int);
int	compar(NDir*, NDir*);
char*	asciitime(long);
char*	darwx(long);
void	rwx(long, char*);
void	growto(long);
void	dowidths(Dir*);
void	format(Dir*, char*);
void	output(void);
char*	xcleanname(char*);
ulong	clk;
int	swidth;			/* max width of -s size */
int	qwidth;			/* max width of -q version */
int	vwidth;			/* max width of dev */
int	uwidth;			/* max width of userid */
int	mwidth;			/* max width of muid */
int	lwidth;			/* max width of length */
int	gwidth;			/* max width of groupid */
Biobuf	bin;

void
main(int argc, char *argv[])
{
	int i;

	Binit(&bin, 1, OWRITE);
	ARGBEGIN{
	case 'F':	Fflag++; break;
	case 'd':	dflag++; break;
	case 'l':	lflag++; break;
	case 'm':	mflag++; break;
	case 'n':	nflag++; break;
	case 'p':	pflag++; break;
	case 'q':	qflag++; break;
	case 'Q':	Qflag++; break;
	case 'r':	rflag++; break;
	case 's':	sflag++; break;
	case 't':	tflag++; break;
	case 'T':	Tflag++; break;
	case 'u':	uflag++; break;
	default:	fprint(2, "usage: ls [-dlmnpqrstuFQT] [file ...]\n");
			exits("usage");
	}ARGEND

	doquote = needsrcquote;
	quotefmtinstall();
	fmtinstall('M', dirmodefmt);

	if(lflag)
		clk = time(0);
	if(argc == 0)
		errs = ls(".", 0);
	else for(i=0; i<argc; i++)
		errs |= ls(argv[i], 1);
	output();
	exits(errs? "errors" : 0);
}

int
ls(char *s, int multi)
{
	int fd;
	long i, n;
	char *p;
	Dir *db;

	db = dirstat(s);
	if(db == nil){
    error:
		fprint(2, "ls: %s: %r\n", s);
		return 1;
	}
	if((db->qid.type&QTDIR) && dflag==0){
		free(db);
		output();
		fd = open(s, OREAD);
		if(fd == -1)
			goto error;
		n = dirreadall(fd, &db);
		if(n < 0)
			goto error;
		xcleanname(s);
		growto(ndir+n);
		for(i=0; i<n; i++){
			dirbuf[ndir+i].d = db+i;
			dirbuf[ndir+i].prefix = multi? s : 0;
		}
		ndir += n;
		close(fd);
		output();
	}else{
		growto(ndir+1);
		dirbuf[ndir].d = db;
		dirbuf[ndir].prefix = 0;
		xcleanname(s);
		p = utfrrune(s, '/');
		if(p){
			dirbuf[ndir].prefix = s;
			*p = 0;
		}
		ndir++;
	}
	return 0;
}

void
output(void)
{
	int i;
	char buf[4096];
	char *s;

	if(!nflag)
		qsort(dirbuf, ndir, sizeof dirbuf[0], (int (*)(void*, void*))compar);
	for(i=0; i<ndir; i++)
		dowidths(dirbuf[i].d);
	for(i=0; i<ndir; i++) {
		if(!pflag && (s = dirbuf[i].prefix)) {
			if(strcmp(s, "/") ==0)	/* / is a special case */
				s = "";
			sprint(buf, "%s/%s", s, dirbuf[i].d->name);
			format(dirbuf[i].d, buf);
		} else
			format(dirbuf[i].d, dirbuf[i].d->name);
	}
	ndir = 0;
	Bflush(&bin);
}

void
dowidths(Dir *db)
{
	char buf[256];
	int n;

	if(sflag) {
		n = sprint(buf, "%llud", (db->length+1023)/1024);
		if(n > swidth)
			swidth = n;
	}
	if(qflag) {
		n = sprint(buf, "%lud", db->qid.vers);
		if(n > qwidth)
			qwidth = n;
	}
	if(mflag) {
		n = snprint(buf, sizeof buf, "[%q]", db->muid);
		if(n > mwidth)
			mwidth = n;
	}
	if(lflag) {
		n = sprint(buf, "%ud", db->dev);
		if(n > vwidth)
			vwidth = n;
		n = sprint(buf, "%q", db->uid);
		if(n > uwidth)
			uwidth = n;
		n = sprint(buf, "%q", db->gid);
		if(n > gwidth)
			gwidth = n;
		n = sprint(buf, "%llud", db->length);
		if(n > lwidth)
			lwidth = n;
	}
}

char*
fileflag(Dir *db)
{
	if(Fflag == 0)
		return "";
	if(QTDIR & db->qid.type)
		return "/";
	if(0111 & db->mode)
		return "*";
	return "";
}

void
format(Dir *db, char *name)
{
	int i;

	if(sflag)
		Bprint(&bin, "%*llud ",
			swidth, (db->length+1023)/1024);
	if(mflag){
		Bprint(&bin, "[%q] ", db->muid);
		for(i=2+strlen(db->muid); i<mwidth; i++)
			Bprint(&bin, " ");
	}
	if(qflag)
		Bprint(&bin, "(%.16llux %*lud %.2ux) ",
			db->qid.path,
			qwidth, db->qid.vers,
			db->qid.type);
	if(Tflag)
		Bprint(&bin, "%c ", (db->mode&DMTMP)? 't': '-');

	if(lflag)
		Bprint(&bin, "%M %C %*ud %*q %*q %*llud %s ",
			db->mode, db->type,
			vwidth, db->dev,
			-uwidth, db->uid,
			-gwidth, db->gid,
			lwidth, db->length,
			asciitime(uflag? db->atime: db->mtime));
	Bprint(&bin, Qflag? "%s%s\n": "%q%s\n", name, fileflag(db));
}

void
growto(long n)
{
	if(n <= ndirbuf)
		return;
	ndirbuf = n;
	dirbuf=(NDir *)realloc(dirbuf, ndirbuf*sizeof(NDir));
	if(dirbuf == 0){
		fprint(2, "ls: malloc fail\n");
		exits("malloc fail");
	}
}

int
compar(NDir *a, NDir *b)
{
	long i;
	Dir *ad, *bd;

	ad = a->d;
	bd = b->d;

	if(tflag){
		if(uflag)
			i = bd->atime-ad->atime;
		else
			i = bd->mtime-ad->mtime;
	}else{
		if(a->prefix && b->prefix){
			i = strcmp(a->prefix, b->prefix);
			if(i == 0)
				i = strcmp(ad->name, bd->name);
		}else if(a->prefix){
			i = strcmp(a->prefix, bd->name);
			if(i == 0)
				i = 1;	/* a is longer than b */
		}else if(b->prefix){
			i = strcmp(ad->name, b->prefix);
			if(i == 0)
				i = -1;	/* b is longer than a */
		}else
			i = strcmp(ad->name, bd->name);
	}
	if(i == 0)
		i = (a<b? -1 : 1);
	if(rflag)
		i = -i;
	return i;
}

char*
asciitime(long l)
{
	static char buf[32];
	char *t;

	t = ctime(l);
	/* 6 months in the past or a day in the future */
	if(l<clk-180L*24*60*60 || clk+24L*60*60<l){
		memmove(buf, t+4, 7);		/* month and day */
		memmove(buf+7, t+23, 5);		/* year */
	}else
		memmove(buf, t+4, 12);		/* skip day of week */
	buf[12] = 0;
	return buf;
}

/*
 * Compress slashes, remove trailing slash.  Don't worry about . and ..
 */
char*
xcleanname(char *name)
{
	char *r, *w;

	for(r=w=name; *r; r++){
		if(*r=='/' && r>name && *(r-1)=='/')
			continue;
		if(w != r)
			*w = *r;
		w++;
	}
	*w = 0;
	while(w-1>name && *(w-1)=='/')
		*--w = 0;
	return name;
}
