/*
 * Display directory contents
 */
#include <stdlib.h>
#include <stdio.h>
#include <console.h>
#include <string.h>
#include <com32.h>
#include <dirent.h>
#include <minmax.h>
#include <unistd.h>
#include <getkey.h>

static int rows, cols;		/* Screen parameters */

#define DIR_CHUNK	1024

static const char *type_str(int type)
{
    switch (type) {
    case DT_FIFO:
	return "[fif]";
    case DT_CHR:
	return "[chr]";
    case DT_DIR:
	return "[dir]";
    case DT_BLK:
	return "[blk]";
    case DT_UNKNOWN:
    case DT_REG:
	return "";
    case DT_LNK:
	return "[lnk]";
    case DT_SOCK:
	return "[sck]";
    case DT_WHT:
	return "[wht]";
    default:
	return "[???]";
    }
}

static void free_dirents(struct dirent **dex, size_t n_de)
{
    size_t i;

    for (i = 0; i < n_de; i++)
	free(dex[i]);

    free(dex);
}

static int compare_dirent(const void *p_de1, const void *p_de2)
{
    const struct dirent *de1 = *(const struct dirent **)p_de1;
    const struct dirent *de2 = *(const struct dirent **)p_de2;
    int ndir1, ndir2;

    ndir1 = de1->d_type != DT_DIR;
    ndir2 = de2->d_type != DT_DIR;

    if (ndir1 != ndir2)
	return ndir1 - ndir2;

    return strcmp(de1->d_name, de2->d_name);
}

static int display_directory(const char *dirname)
{
    DIR *dir;
    struct dirent *de;
    struct dirent **dex = NULL;
    size_t n_dex = 0, n_de = 0;
    size_t i, j, k;
    size_t nrows, ncols, perpage;
    size_t endpage;
    int maxlen = 0;
    int pos, tpos, colwidth;

    dir = opendir(dirname);
    if (!dir) {
	printf("Unable to read directory: %s\n", dirname);
	return -1;
    }

    while ((de = readdir(dir)) != NULL) {
	struct dirent *nde;

	if (n_de >= n_dex) {
	    struct dirent **ndex;

	    ndex = realloc(dex, (n_dex + DIR_CHUNK) * sizeof *dex);
	    if (!ndex)
		goto nomem;

	    dex = ndex;
	    n_dex += DIR_CHUNK;
	}

	nde = malloc(de->d_reclen);
	if (!nde)
	    goto nomem;

	memcpy(nde, de, de->d_reclen);
	dex[n_de++] = nde;

	maxlen = max(maxlen, de->d_reclen);
    }

    closedir(dir);

    qsort(dex, n_de, sizeof *dex, compare_dirent);

    maxlen -= offsetof(struct dirent, d_name) + 1;
    ncols = (cols + 2)/(maxlen + 8);
    ncols = min(ncols, n_de);
    ncols = max(ncols, 1U);
    colwidth = (cols + 2)/ncols;
    perpage = ncols * (rows - 1);

    for (i = 0; i < n_de; i += perpage) {
	/* Rows on this page */
	endpage = min(i+perpage, n_de);
	nrows = ((endpage-i) + ncols - 1)/ncols;

	for (j = 0; j < nrows; j++) {
	    pos = tpos = 0;
	    for (k = i+j; k < endpage; k += nrows) {
		pos += printf("%*s%-5s %s",
			      (tpos - pos), "",
			      type_str(dex[k]->d_type),
			      dex[k]->d_name);
		tpos += colwidth;
	    }
	    printf("\n");
	}

	if (endpage >= n_de)
	    break;

	get_key(stdin, 0);
    }

    free_dirents(dex, n_de);
    return 0;

nomem:
    closedir(dir);
    printf("Out of memory error!\n");
    free_dirents(dex, n_de);
    return -1;
}

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

    if (getscreensize(1, &rows, &cols)) {
	/* Unknown screen size? */
	rows = 24;
	cols = 80;
    }

    if (argc < 2)
	rv = display_directory(".");
    else if (argc == 2)
	rv = display_directory(argv[1]);
    else {
	printf("Usage: dir directory\n");
	rv = 1;
    }

    return rv ? 1 : 0;
}