#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <inttypes.h>
#include <errno.h>
#include <ctype.h>

/* cc -D_FILE_OFFSET_BITS=64 myidx.4.c */

void readable_fs(uint64_t, char *);
void url_print(const char *);
void html_print(const char *);
int hiddenfile(const struct dirent *);
int dirbeforefile(const struct dirent **, const struct dirent **);

int main(void)
{
	int i, numfiles;
	char filename[4096] = "", filesize[32], *uri;
	struct dirent **files;
	struct stat filestat;

	if (getenv("DOCUMENT_ROOT") == NULL || (uri = getenv("DOCUMENT_URI")) == NULL) {
		fputs("Status: 500 Internal Server Error\nContent-Type: text/plain\n\nDOCUMENT_ROOT or DOCUMENT_URI weren't provided", stdout);
		return 1;
	}

	strncat(filename, getenv("DOCUMENT_ROOT"), 4095);
	strncat(filename, uri, 4095);

	if (uri[strlen(uri)-1] != '/') {
		if (access(filename, R_OK) == 0)
			printf("Status: 301 Moved Permanently\nLocation: %s/\n\n", uri);
		else
			fputs("Content-Type: text/html\nStatus: 404 Not Found\n\n<meta name=\"viewport\" content=\"width=device-width\">Not found. <a href=\"/\">Go back to start.</a>", stdout);
		return 1;
	}

	if ((numfiles = scandir(filename, &files, hiddenfile, dirbeforefile)) == -1) {
		printf("Status: 404 Not Found\nContent-Type: text/plain\n\nCouldn't scan directory: %s", strerror(errno));
	} else {
		chdir(filename);
		fputs("Status: 200 OK\nContent-Type: text/html\n\n<!DOCTYPE html><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\"><title>Index of ", stdout);
		fputs(uri, stdout);
		fputs("</title><style>html,body { box-sizing: border-box; } table { border-collapse: collapse; max-width: 100%%; } thead { border-bottom: 2px solid; text-align: left; } .n { word-break: break-all; } a:nth-child(2) { margin-left: 0.5em; }</style><table><thead><tr><th>Name</th><th>Size</th></tr></thead><tbody>", stdout);
		for (i = 0; i < numfiles; ++i) {
			stat(files[i]->d_name, &filestat);
			readable_fs(filestat.st_size, filesize);
			fputs("<tr><td class=\"n\"><a href=\"", stdout);
			url_print(files[i]->d_name);
			if (files[i]->d_type == DT_DIR)
				putchar('/');
			fputs("\">", stdout);
			html_print(files[i]->d_name);
			printf("</a></td><td>%s</td></tr>", files[i]->d_type == DT_DIR ? "(dir)" : filesize);
			free(files[i]);
		}
		free(files);
		fputs("</tbody></table><script>var links = document.getElementsByClassName('n');", stdout);
		// this adds media player integration. register a custom url scheme called "mpv" which opens the received link in your preferred media player.
		//fputs("var lpkey = 'htindex-lastplay-' + location.pathname, playbuttons = [], lastplay, i, a; try { lastplay = localStorage.getItem(lpkey); } catch (e) {} for (i = 0; i < links.length; ++i) if (links[i].childNodes[0].href.slice(-1) !== '/') { a = document.createElement('a'); a.href = 'mpv:' + links[i].childNodes[0].href; if (a.href === lastplay) a.style.border = '1px solid red'; a.appendChild(document.createTextNode('\u25B6')); a.onclick = function () {try { localStorage.setItem(lpkey, this.href); for (var i = 0; i < playbuttons.length; ++i) playbuttons[i].style.border = playbuttons[i] === this ? '1px solid red' : ''; } catch (e) {} return true; }; links[i].appendChild(a); playbuttons.push(a); } /* mpv $(echo $1 | sed 's/^mpv://; s#://#://user:password@#') */", stdout);
		fputs("var input = document.createElement('input'); input.style.width = '100%'; input.style.boxSizing = 'border-box'; input.placeholder = 'Search...'; input.oninput = function () { for (i = 0; i < links.length; ++i) links[i].parentNode.style.display = links[i].childNodes[0].childNodes[0].nodeValue.toLowerCase().indexOf(this.value.toLowerCase()) !== -1 ? null : 'none'; }; document.body.insertBefore(input, document.body.firstChild);</script>", stdout);
	}

	return 0;
}

/* https://stackoverflow.com/a/3898986 */
void readable_fs(uint64_t size, char *result)
{
	const char *sizes[] = {"EiB", "PiB", "TiB", "GiB", "MiB", "KiB", "B"};
	uint64_t multiplier = 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL;
	int i;

	for (i = 0; i < 7; ++i, multiplier /= 1024) {
		if (size < multiplier)
			continue;
		if (size % multiplier == 0)
			sprintf(result, "%llu %s", size / multiplier, sizes[i]);
		else
			sprintf(result, "%.1f %s", (float) size / multiplier, sizes[i]);
		return;
	}
	strcpy(result, "0");
}

int hiddenfile(const struct dirent *file)
{
	return !(file->d_name[0] == '.' && !(file->d_name[1] == '.' && file->d_name[2] == '\0'));
}

int dirbeforefile(const struct dirent **a, const struct dirent **b)
{
	if (strcmp((*a)->d_name, "..") == 0)
		return -1;
	if (strcmp((*b)->d_name, "..") == 0)
		return 1;
	if ((*a)->d_type != (*b)->d_type)
		return (*b)->d_type == DT_DIR;
	return strcasecmp((*a)->d_name[0] == '[' ? (*a)->d_name+1 : (*a)->d_name, (*b)->d_name[0] == '[' ? (*b)->d_name+1 : (*b)->d_name);
}

void url_print(const char str[])
{
	/* à la RFC 1738 */
	static const char allowedspecial[] = "$-_.+!*'(),";

	for (; *str != '\0'; ++str)
		if (isalnum(*str) || strchr(allowedspecial, *str))
			putchar(*str);
		else
			printf("%%%02X", *str);
}

void html_print(const char str[])
{
	for (; *str != '\0'; ++str)
		if (*str == '<')
			fputs("&lt;", stdout);
		else if (*str == '>')
			fputs("&gt;", stdout);
		else if (*str == '&')
			fputs("&amp;", stdout);
		else
			putchar(*str);
}
