#if defined(ENABLE_WWW)

#include <string.h>
#include <time.h>
#include <sqlite3.h>
#include <libgen.h>

#include "../deps/hashids/hashids.h"

#include "bbs.h"

extern struct bbs_config conf;
extern struct user_record *gUser;
extern char *aha(char *input);

static int digit2nibble(int digit) {
	static const char *const hex = "0123456789abcdef";
	static const char *const Hex = "0123456789ABCDEF";

	char *p;
	p = strchr(hex, digit);
	if (p != NULL)
		return p - hex;
	p = strchr(Hex, digit);
	if (p != NULL)
		return p - Hex;
	return -1;
}

static char *www_decode(char *clean_url) {
	stralloc url = EMPTY_STRALLOC;

	assert(clean_url != NULL);
	for (char *s = clean_url; *s != '\0'; ++s) {
		if (*s == '+')
			stralloc_append1(&url, ' ');
		else if (*s != '%')
			stralloc_append1(&url, *s);
		else {
			int hn = 0, ln = 0, ch = 0;
			if (s[1] == '\0' || (hn = digit2nibble(s[1])) < 0) {
				free(url.s);
				return NULL;
			}
			if (s[2] == '\0' || (ln = digit2nibble(s[2])) < 0) {
				free(url.s);
				return NULL;
			}
			stralloc_append1(&url, hn * 16 + ln);
		}
	}
	stralloc_0(&url);

	return url.s;
}

static void www_encode(stralloc *clean, char *url) {
	assert(clean != NULL);
	assert(url != NULL);
	for (char *s = url; *s != '\0'; ++s) {
		if (isalnum(*s) || *s == '~' || *s == '.' || *s == '_')
			stralloc_append1(clean, *s);
		else if (*s == ' ')
			stralloc_append1(clean, '+');
		else {
			stralloc_append1(clean, '%');
			stralloc_cat_Byte(clean, *s);
		}
	}
}

void www_expire_old_links() {
	char pathbuf[PATH_MAX];
	sqlite3 *db;
	sqlite3_stmt *res;
	int rc;
	char sql[] = "delete from wwwhash where expiry <= ?";
	char *ret;
	time_t now = time(NULL);

	snprintf(pathbuf, PATH_MAX, "%s/www_file_hashes.sq3", conf.bbs_path);

	rc = sqlite3_open(pathbuf, &db);
	if (rc != SQLITE_OK) {
		dolog("Cannot open database: %s", sqlite3_errmsg(db));
		return;
	}
	sqlite3_busy_timeout(db, 5000);
	rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
	if (rc != SQLITE_OK) {
		sqlite3_close(db);
		return;
	}
	sqlite3_bind_int(res, 1, now);
	sqlite3_step(res);
	sqlite3_finalize(res);
	sqlite3_close(db);
}

int www_check_hash_expired(char *hash) {
	char pathbuf[PATH_MAX];
	sqlite3 *db;
	sqlite3_stmt *res;
	int rc;
	time_t now = time(NULL);
	char sql[] = "select id from wwwhash where hash = ? and expiry > ?";
	snprintf(pathbuf, PATH_MAX, "%s/www_file_hashes.sq3", conf.bbs_path);
	rc = sqlite3_open(pathbuf, &db);
	if (rc != SQLITE_OK) {
		return 1;
	}
	sqlite3_busy_timeout(db, 5000);
	rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
	if (rc != SQLITE_OK) {
		sqlite3_close(db);
		return 0;
	}

	sqlite3_bind_text(res, 1, hash, -1, 0);
	sqlite3_bind_int(res, 2, now);

	if (sqlite3_step(res) == SQLITE_ROW) {
		sqlite3_finalize(res);
		sqlite3_close(db);
		return 0;
	}
	sqlite3_finalize(res);
	sqlite3_close(db);
	return 1;
}

void www_add_hash_to_db(char *hash, time_t expiry) {
	char pathbuf[PATH_MAX];
	sqlite3 *db;
	sqlite3_stmt *res;
	int rc;
	char csql[] = "create table if not exists wwwhash (id INTEGER PRIMARY KEY, hash TEXT, expiry INTEGER)";
	char chsql[] = "select id from wwwhash where hash = ?";
	char usql[] = "update wwwhash SET expiry = ? WHERE hash = ?";
	char isql[] = "insert into wwwhash (hash, expiry) values(?, ?)";

	char *ret;
	char *err_msg = 0;

	snprintf(pathbuf, PATH_MAX, "%s/www_file_hashes.sq3", conf.bbs_path);

	rc = sqlite3_open(pathbuf, &db);
	if (rc != SQLITE_OK) {
		return;
	}
	sqlite3_busy_timeout(db, 5000);

	rc = sqlite3_exec(db, csql, 0, 0, &err_msg);
	if (rc != SQLITE_OK) {

		dolog("SQL error: %s", err_msg);

		sqlite3_free(err_msg);
		sqlite3_close(db);
		return;
	}

	// first check if hash is in database
	rc = sqlite3_prepare_v2(db, chsql, -1, &res, 0);
	if (rc != SQLITE_OK) {
		sqlite3_close(db);
		return;
	}
	sqlite3_bind_text(res, 1, hash, -1, 0);
	rc = sqlite3_step(res);
	if (rc == SQLITE_ROW) {
		// if so, update hash
		sqlite3_finalize(res);
		rc = sqlite3_prepare_v2(db, usql, -1, &res, 0);
		if (rc != SQLITE_OK) {
			sqlite3_close(db);
			return;
		}
		sqlite3_bind_int(res, 1, expiry);
		sqlite3_bind_text(res, 2, hash, -1, 0);
		sqlite3_step(res);
		sqlite3_finalize(res);
		sqlite3_close(db);

		return;
	}
	// if not add hash
	sqlite3_finalize(res);
	rc = sqlite3_prepare_v2(db, isql, -1, &res, 0);
	if (rc != SQLITE_OK) {
		sqlite3_close(db);
		return;
	}
	sqlite3_bind_text(res, 1, hash, -1, 0);
	sqlite3_bind_int(res, 2, expiry);
	sqlite3_step(res);
	sqlite3_finalize(res);
	sqlite3_close(db);
}

char *www_decode_hash(char *hash) {
	unsigned long long numbers[4];
	int dir, sub, fid, uid;
	hashids_t *hashids = hashids_init(conf.bbs_name);
	char pathbuf[PATH_MAX];
	sqlite3 *db;
	sqlite3_stmt *res;
	int rc;
	char sql[] = "select filename from files where approved = 1 and id = ?";
	char *ret;

	if (www_check_hash_expired(hash)) {
		return NULL;
	}

	if (hashids_decode(hashids, hash, numbers) != 4) {
		hashids_free(hashids);
		return NULL;
	}
	hashids_free(hashids);

	uid = (int)numbers[0];
	dir = (int)numbers[1];
	sub = (int)numbers[2];
	fid = (int)numbers[3];

	if (dir >= ptr_vector_len(&conf.file_directories))
		return NULL;
	struct file_directory *fdir = ptr_vector_get(&conf.file_directories, dir);
	assert(fdir != NULL);
	if (sub >= ptr_vector_len(&fdir->file_subs))
		return NULL;
	struct file_sub *fsub = ptr_vector_get(&fdir->file_subs, sub);
	assert(fsub != NULL);

	// get filename from database
	snprintf(pathbuf, sizeof pathbuf, "%s/%s.sq3", conf.bbs_path, fsub->database);
	rc = sqlite3_open(pathbuf, &db);
	if (rc != SQLITE_OK) {
		return NULL;
	}
	sqlite3_busy_timeout(db, 5000);
	rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
	if (rc != SQLITE_OK) {
		sqlite3_close(db);
		return NULL;
	}
	sqlite3_bind_int(res, 1, fid);
	if (sqlite3_step(res) == SQLITE_ROW) {
		ret = strdup(sqlite3_column_text(res, 0));
		sqlite3_finalize(res);
		sqlite3_close(db);

		return ret;
	}
	sqlite3_finalize(res);
	sqlite3_close(db);
	return NULL;
}

char *www_create_link(int dir, int sub, int fid) {
	char url[PATH_MAX];
	char *ret;
	char *hashid;
	int sizereq;
	time_t expiry;

	hashids_t *hashids = hashids_init(conf.bbs_name);

	sizereq = hashids_estimate_encoded_size_v(hashids, 4, (unsigned long long)gUser->id,
	                                          (unsigned long long)dir, (unsigned long long)sub, (unsigned long long)fid);

	hashid = (char *)malloz(sizereq + 1);

	if (hashids_encode_v(hashids, hashid, 4, (unsigned long long)gUser->id,
 	    (unsigned long long)dir, (unsigned long long)sub, (unsigned long long)fid) == 0) {
		hashids_free(hashids);
		free(hashid);
		return NULL;
	}

	hashids_free(hashids);

	snprintf(url, sizeof url, "%sfiles/%s", conf.www_url, hashid);

	// add link into hash database
	expiry = time(NULL) + 86400;
	www_add_hash_to_db(hashid, expiry);

	free(hashid);

	ret = strdup(url);

	return ret;
}
char *www_files_display_listing(int dir, int sub) {
	static const char *sql = "select id, filename, description, size, dlcount, uploaddate from files where approved=1 ORDER BY filename";

	stralloc page = EMPTY_STRALLOC;
	char pathbuf[PATH_MAX];
	char *aha_out = NULL;
	sqlite3 *db = NULL;
	sqlite3_stmt *res = NULL;
	int rc = 0;

	if (dir >= ptr_vector_len(&conf.file_directories))
		return NULL;
	struct file_directory *fdir = ptr_vector_get(&conf.file_directories, dir);
	assert(fdir != NULL);
	if (sub >= ptr_vector_len(&fdir->file_subs))
		return NULL;
	struct file_sub *fsub = ptr_vector_get(&fdir->file_subs, sub);
	assert(fsub != NULL);

	stralloc_copys(&page, "<div class=\"content-header\"><h2>Files: ");
	stralloc_cats(&page, fdir->name);
	stralloc_cats(&page, " - ");
	stralloc_cats(&page, fsub->name);
	stralloc_cats(&page, "</h2></div>\n");

	snprintf(pathbuf, sizeof pathbuf, "%s/%s.sq3", conf.bbs_path, fsub->database);
	rc = sqlite3_open(pathbuf, &db);
	if (rc != SQLITE_OK) {
		free(page.s);
		return NULL;
	}
	sqlite3_busy_timeout(db, 5000);
	rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
	if (rc != SQLITE_OK) {
		sqlite3_close(db);
		free(page.s);
		return NULL;
	}
	stralloc_cats(&page, "<table class=\"fileentry\"><thead><tr><td>Filename</td><td>Size</td><td>Description</td></tr></thead><tbody>\n");
	while (sqlite3_step(res) == SQLITE_ROW) {
		char *filename = strdup((char *)sqlite3_column_text(res, 1));
		char *base_filename = basename(filename);
		stralloc_cats(&page, "<tr><td class=\"filename\"><a href=\"");
		stralloc_cats(&page, conf.www_url);
		stralloc_cats(&page, "files/areas/");
		stralloc_cat_long(&page, dir);
		stralloc_append1(&page, '/');
		stralloc_cat_long(&page, sub);
		stralloc_append1(&page, '/');
		www_encode(&page, base_filename);
		stralloc_cats(&page, "\">");
		stralloc_cats(&page, base_filename);
		stralloc_cats(&page, "</a></td>");

		int size = sqlite3_column_int(res, 3);
		stralloc_cats(&page, "<td class=\"filesize\">");
		int c = 'b';
		if (size > 1024) {
			size /= 1024;
			c = 'K';
		}
		if (size > 1024) {
			size /= 1024;
			c = 'M';
		}
		if (size > 1024) {
			size /= 1024;
			c = 'G';
		}
		stralloc_cat_long(&page, size);
		stralloc_append1(&page, c);
		stralloc_cats(&page, "</td>");

		stralloc_cats(&page, "<td class=\"filedesc\">");
		char *description = strdup((char *)sqlite3_column_text(res, 2));
		for (char *p = description; *p != '\0'; ++p) {
			if (*p == '\n')
				*p = '\r';
		}
		aha_out = aha(description);
		stralloc_cats(&page, aha_out);
		free(aha_out);
		free(description);
		free(filename);

		stralloc_cats(&page, "</td></tr>\n");
	}
	stralloc_cats(&page, "</tbody></table>\n");
	stralloc_0(&page);

	sqlite3_finalize(res);
	sqlite3_close(db);

	return page.s;
}

char *www_files_areas() {
	stralloc page = EMPTY_STRALLOC;

	stralloc_copys(&page, "<div class=\"content-header\"><h2>File Directories</h2></div>\n");
	for (size_t i = 0; i < ptr_vector_len(&conf.file_directories); ++i) {
		struct file_directory *dir = ptr_vector_get(&conf.file_directories, i);
		if (!dir->display_on_web)
			continue;
		stralloc_cats(&page, "<div class=\"conference-list-item\">");
		stralloc_cats(&page, dir->name);
		stralloc_cats(&page, "</div>\n");
		for (size_t j = 0; j < ptr_vector_len(&dir->file_subs); ++j) {
			struct file_sub *sub = ptr_vector_get(&dir->file_subs, j);
			stralloc_cats(&page, "<div class=\"area-list-item\"><a href=\"");
			stralloc_cats(&page, conf.www_url);
			stralloc_cats(&page, "files/areas/");
			stralloc_cat_long(&page, i);
			stralloc_append1(&page, '/');
			stralloc_cat_long(&page, j);
			stralloc_cats(&page, "/\">");
			stralloc_cats(&page, sub->name);
			stralloc_cats(&page, "</a></div>\n");
		}
	}
	stralloc_0(&page);

	return page.s;
}

char *www_files_get_from_area(int dir, int sub, char *clean_file) {
	static const char *sql = "SELECT filename FROM files WHERE approved=1 AND filename LIKE ? ESCAPE \"^\"";

	stralloc filenamelike = EMPTY_STRALLOC;
	sqlite3 *db = NULL;
	sqlite3_stmt *res = NULL;
	int rc = 0;
	char pathbuf[PATH_MAX];
	char *ret = NULL;
	char *file = NULL;

	file = www_decode(clean_file);
	stralloc_copys(&filenamelike, "%");
	for (char *p = file; *p != '\0'; ++p) {
		if (*p == '^' || *p == '_' || *p == '%')
			stralloc_append1(&filenamelike, '^');
		stralloc_append1(&filenamelike, *p);
	}
	stralloc_0(&filenamelike);
	free(file);

	if (dir >= ptr_vector_len(&conf.file_directories))
		return NULL;
	struct file_directory *fdir = ptr_vector_get(&conf.file_directories, dir);
	assert(fdir != NULL);
	if (sub >= ptr_vector_len(&fdir->file_subs))
		return NULL;
	struct file_sub *fsub = ptr_vector_get(&fdir->file_subs, sub);
	assert(fsub != NULL);

	snprintf(pathbuf, sizeof pathbuf, "%s/%s.sq3", conf.bbs_path, fsub->database);
	rc = sqlite3_open(pathbuf, &db);
	if (rc != SQLITE_OK) {
		free(filenamelike.s);
		return NULL;
	}
	sqlite3_busy_timeout(db, 5000);
	rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
	if (rc != SQLITE_OK) {
		sqlite3_close(db);
		free(filenamelike.s);
		return NULL;
	}
	sqlite3_bind_text(res, 1, filenamelike.s, -1, 0);
	rc = sqlite3_step(res);
	if (rc == SQLITE_ROW) {
		ret = strdup(sqlite3_column_text(res, 0));
	}

	free(filenamelike.s);
	sqlite3_finalize(res);
	sqlite3_close(db);
	return ret;
}

#endif
