/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#define LWS_DLL
#define LWS_INTERNAL
#include <libwebsockets.h>

#include <stdlib.h>
#include <string.h>
#include <uv.h>

struct fobj {
	struct fobj *next;
	const char *name, *uri, *icon, *date;
	time_t m;
	unsigned long size;
};

struct per_session_data__tbl_dir {
	struct fobj base;
	char strings[64 * 1024];
	char reldir[256];
	char *p;
	const char *dir;

#if UV_VERSION_MAJOR > 0
	uv_fs_event_t *event_req;
#endif
	struct lws *wsi;
};

#if UV_VERSION_MAJOR > 0
static void
mon_cb(uv_fs_event_t *handle, const char *filename, int events, int status)
{
	struct per_session_data__tbl_dir *pss = handle->data;

	//lwsl_notice("%s\n", __func__);

	if (pss && pss->wsi)
		lws_callback_on_writable(pss->wsi);
}

static void lws_uv_close_cb(uv_handle_t *handle)
{
	free(handle);
}

static void
lws_protocol_dir_kill_monitor(struct per_session_data__tbl_dir *pss)
{
	if (!pss->event_req)
		return;
	pss->wsi = NULL;
	pss->event_req->data = NULL;
	uv_fs_event_stop(pss->event_req);
	uv_close((uv_handle_t *)pss->event_req, lws_uv_close_cb);
	pss->event_req = NULL;
}
#endif

static int
scan_dir(struct lws *wsi, struct per_session_data__tbl_dir *pss)
{
/* uuh travis... */
#if UV_VERSION_MAJOR > 0
	uv_loop_t *loop = lws_uv_getloop(lws_get_context(wsi), 0);
	char *end = &(pss->strings[sizeof(pss->strings) - 1]);
	struct fobj *prev = &pss->base;
	char path[512], da[200];
	const char *icon;
	uv_dirent_t dent;
	struct fobj *f;
	struct stat st;
	struct tm *tm;
	int ret = 0, n;
	uv_fs_t req;

	lws_protocol_dir_kill_monitor(pss);

	lws_snprintf(path, sizeof(path) - 1, "%s/%s", pss->dir, pss->reldir);
	//lwsl_notice("path = %s\n", path);

	pss->event_req = malloc(sizeof(*pss->event_req));
	if (!pss->event_req)
		return 2;

	pss->wsi = wsi;
	pss->event_req->data = pss;

        uv_fs_event_init(lws_uv_getloop(lws_get_context(wsi), 0),
        		 pss->event_req);
        // The recursive flag watches subdirectories too.
        n = uv_fs_event_start(pss->event_req, mon_cb, path, UV_FS_EVENT_RECURSIVE);
        //lwsl_notice("monitoring %s (%d)\n", path, n);

	if (!uv_fs_scandir(loop, &req, path, 0, NULL)) {
		lwsl_err("Scandir on %s failed\n", path);
		return 2;
	}

	pss->p = pss->strings;

	while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
		lws_snprintf(path, sizeof(path) - 1, "%s/%s/%s", pss->dir, pss->reldir, dent.name);

		if (stat(path, &st)) {
			lwsl_info("unable to stat %s\n", path);
			continue;
		}
		f = malloc(sizeof(*f));
		f->next = NULL;
		f->name = pss->p;
		n = lws_snprintf(pss->p, end - pss->p, "%s", dent.name);
		pss->p += n + 1;
		f->uri = NULL;
		if ((S_IFMT & st.st_mode) == S_IFDIR) {
			n = lws_snprintf(pss->p, end - pss->p, "=%s/%s", pss->reldir, dent.name);
			f->uri = pss->p;
		}
		if (lws_get_mimetype(dent.name, NULL)) {
			n = lws_snprintf(pss->p, end - pss->p, "./serve/%s/%s", pss->reldir, dent.name);
			f->uri = pss->p;
		}
		if (f->uri)
			pss->p += n + 1;

		if (end - pss->p < 100) {
			free(f);
			break;
		}

		icon = " ";
		if ((S_IFMT & st.st_mode) == S_IFDIR)
			icon = "&#x1f4c2;";

		f->icon = pss->p;
		n = lws_snprintf(pss->p, end - pss->p, "%s", icon);
		pss->p += n + 1;

		f->date = pss->p;
		tm = gmtime(&st.st_mtime);
		strftime(da, sizeof(da), "%Y-%b-%d %H:%M:%S %z", tm);
		n = lws_snprintf(pss->p, end - pss->p, "%s", da);
		pss->p += n + 1;

		f->size = st.st_size;
		f->m = st.st_mtime;
		prev->next = f;
		prev = f;
	}

	uv_fs_req_cleanup(&req);

	return ret;
#else
	return 0;
#endif
}

static void
free_scan_dir(struct per_session_data__tbl_dir *pss)
{
	struct fobj *f = pss->base.next, *f1;

	while (f) {
		f1 = f->next;
		free(f);
		f = f1;
	}

	pss->base.next = NULL;
}

static int
callback_lws_table_dirlisting(struct lws *wsi, enum lws_callback_reasons reason,
			      void *user, void *in, size_t len)
{
	struct per_session_data__tbl_dir *pss = (struct per_session_data__tbl_dir *)user;
	char j[LWS_PRE + 16384], *p = j + LWS_PRE, *start = p, *q, *q1, *w,
		*end = j + sizeof(j) - LWS_PRE, e[384], s[384], s1[384];
	const struct lws_protocol_vhost_options *pmo;
	struct fobj *f;
	int n, first = 1;

	switch (reason) {
	case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
		break;

	case LWS_CALLBACK_ESTABLISHED:
		lwsl_debug("LWS_CALLBACK_ESTABLISHED\n");
		/*
		 * send client the lwsgt table layout
		 */
		start = "{\"cols\":["
			"  {\"name\": \"Date\"},"
			"  {\"name\": \"Size\", \"align\": \"right\"},"
			"  {\"name\": \"Icon\"},"
			"  {\"name\": \"Name\", \"href\": \"uri\"},"
			"  {\"name\": \"uri\", \"hide\": \"1\" }"
			" ]"
			"}";
		if (lws_write(wsi, (unsigned char *)start, strlen(start),
			      LWS_WRITE_TEXT) < 0)
			return -1;

		/* send a view update next */
		lws_callback_on_writable(wsi);
		break;

	case LWS_CALLBACK_RECEIVE:
		if (len > sizeof(pss->reldir) - 1)
			len = sizeof(pss->reldir) - 1;
		if (!strstr(in, "..") && !strchr(in, '~'))
			lws_strncpy(pss->reldir, in, len + 1);
		else
			len = 0;
		pss->reldir[len] = '\0';
		if (pss->reldir[0] == '/' && !pss->reldir[1])
			pss->reldir[0] = '\0';
		lwsl_info("%s\n", pss->reldir);
		lws_callback_on_writable(wsi);
		break;

	case LWS_CALLBACK_SERVER_WRITEABLE:

		if (scan_dir(wsi, pss))
			return 1;

		p += lws_snprintf(p, end - p, "{\"breadcrumbs\":[");
		q = pss->reldir;

		if (!q[0])
			p += lws_snprintf(p, end - p, "{\"name\":\"top\"}");

		while (*q) {

			q1 = strchr(q, '/');
			if (!q1) {
				if (first)
					strcpy(s, "top1");
				else
					strcpy(s, q);
				s1[0] = '\0';
				q += strlen(q);
			} else {
				n = lws_ptr_diff(q1, q);
				if (n > (int)sizeof(s) - 1)
					n = sizeof(s) - 1;
				if (first) {
					strcpy(s1, "/");
					strcpy(s, "top");
				} else {
					lws_strncpy(s, q, n + 1);

					n = lws_ptr_diff(q1, pss->reldir);
					if (n > (int)sizeof(s1) - 1)
						n = sizeof(s1) - 1;
					lws_strncpy(s1, pss->reldir, n + 1);
				}
				q = q1 + 1;
			}
			if (!first)
				p += lws_snprintf(p, end - p, ",");
			else
				first = 0;

			p += lws_snprintf(p, end - p, "{\"name\":\"%s\"",
					lws_json_purify(e, s, sizeof(e), NULL));
			if (*q) {
				w = s1;
				while (w[0] == '/' && w[1] == '/')
					w++;
				p += lws_snprintf(p, end - p, ",\"url\":\"%s\"",
					lws_json_purify(e, w, sizeof(e), NULL));
			}
			p += lws_snprintf(p, end - p, "}");
			if (!q1)
				break;
		}

		p += lws_snprintf(p, end - p, "],\"data\":[");

		f = pss->base.next;
		while (f) {
			/* format in JSON */
			p += lws_snprintf(p, end - p, "{\"Icon\":\"%s\",",
					lws_json_purify(e, f->icon, sizeof(e), NULL));
			p += lws_snprintf(p, end - p, " \"Date\":\"%s\",",
				lws_json_purify(e, f->date, sizeof(e), NULL));
			p += lws_snprintf(p, end - p, " \"Size\":\"%ld\",",
				f->size);
			if (f->uri)
				p += lws_snprintf(p, end - p, " \"uri\":\"%s\",",
						lws_json_purify(e, f->uri, sizeof(e), NULL));
			p += lws_snprintf(p, end - p, " \"Name\":\"%s\"}",
				lws_json_purify(e, f->name, sizeof(e), NULL));

			f = f->next;

			if (f)
				p += lws_snprintf(p, end - p, ",");
		}

		p += lws_snprintf(p, end - p, "]}");

		free_scan_dir(pss);

		if (lws_write(wsi, (unsigned char *)start, p - start,
			      LWS_WRITE_TEXT) < 0)
			return -1;

		break;

	case LWS_CALLBACK_HTTP_PMO:
		/* find the per-mount options we're interested in */
		lwsl_debug("LWS_CALLBACK_HTTP_PMO\n");
		pmo = (struct lws_protocol_vhost_options *)in;
		while (pmo) {
			if (!strcmp(pmo->name, "dir")) /* path to list files */
				pss->dir = pmo->value;
			pmo = pmo->next;
		}
		if (!pss->dir[0]) {
			lwsl_err("dirlisting: \"dir\" pmo missing\n");
			return 1;
		}
		break;

	case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
		//lwsl_notice("LWS_CALLBACK_HTTP_DROP_PROTOCOL\n");
#if UV_VERSION_MAJOR > 0
		lws_protocol_dir_kill_monitor(pss);
#endif
		break;

	default:
		return 0;
	}

	return 0;

}

static const struct lws_protocols protocols[] = {
	{
		"protocol-lws-table-dirlisting",
		callback_lws_table_dirlisting,
		sizeof(struct per_session_data__tbl_dir),
		0,
	},
};

LWS_VISIBLE int
init_protocol_lws_table_dirlisting(struct lws_context *context,
			       struct lws_plugin_capability *c)
{
	if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
		lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
			 c->api_magic);
		return 1;
	}

	c->protocols = protocols;
	c->count_protocols = LWS_ARRAY_SIZE(protocols);
	c->extensions = NULL;
	c->count_extensions = 0;

	return 0;
}

LWS_VISIBLE int
destroy_protocol_lws_table_dirlisting(struct lws_context *context)
{
	return 0;
}