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