﻿#include <fcgi_stdio.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>

void printescaped(const char str[])
{
	int i;
	
	if (str == NULL)
		return;
	
	/* https://url.spec.whatwg.org/#application/x-www-form-urlencoded */
	for (i = 0; str[i] != '\0'; ++i)
		if (str[i] == ' ')
			putchar('+');
		else if (str[i] == '*' || str[i] == '-' || str[i] == '.' || str[i] >= '0' && str[i] <= '9' || str[i] >= 'A' && str[i] <= 'Z' || str[i] == '_' || str[i] >= 'a' && str[i] <= 'z')
			putchar(str[i]);
		else
			printf("%%%02X", str[i]);
}

int main(void)
{
	sqlite3 *db;
	sqlite3_stmt *sorts[8], *count, *stats;
	int i;
	
	if (sqlite3_open("levels.sqlite3", &db) != SQLITE_OK)
		return 1;
	
	for (i = 0; i < 8; ++i) {
		char stmt[97];
		const char *orders[] = {
			"id DESC", "id ASC",
			"name ASC", "name DESC",
			"author ASC", "author DESC",
			"score DESC", "score ASC"
		};
		sprintf(stmt, "SELECT id, name, author, score FROM levels%s ORDER BY %s LIMIT ?,?", i >= 6 ? " WHERE score IS NOT NULL" : "", orders[i]);
		if (sqlite3_prepare_v3(db, stmt, 97, SQLITE_PREPARE_PERSISTENT, &sorts[i], NULL) != SQLITE_OK)
			return 1;
	}
	
	if (sqlite3_prepare_v3(db, "SELECT COUNT(id) FROM levels", 29, SQLITE_PREPARE_PERSISTENT, &count, NULL) != SQLITE_OK)
		return 1;
	
	if (sqlite3_prepare_v3(db, "SELECT * FROM levels WHERE id = ?", 34, SQLITE_PREPARE_PERSISTENT, &stats, NULL) != SQLITE_OK)
		return 1;
	
	while (FCGI_Accept() >= 0) {
		if (getenv("PATH_INFO") == NULL)
			puts("Content-Type: text/plain\nStatus: 500 Internal Server Error\n\nPATH_INFO not set");
		else if (strcmp(getenv("PATH_INFO"), "/") == 0) {
			puts("Welcome to the reimplemented portal!");
		} else if (strcmp(getenv("PATH_INFO"), "/crossdomain.xml") == 0) {
			puts(
				"Content-Type: text/x-cross-domain-policy\n\n"
				"<?xml version=\"1.0\"?>"
				"<!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\">"
				"<cross-domain-policy>"
				"<allow-access-from domain=\"*\" secure=\"false\"/>"
				"</cross-domain-policy>"
			);
		} else if (strcmp(getenv("PATH_INFO"), "/levels/leveldesigner/listlevels.php") == 0) {
			int length = 8, start = 0, i = 0, inval = 0, order = 0, c, iter;
			char key[7] = "", val[11] = "";
			const char *orders[] = {
				"newest", "oldest",
				"name", "name2",
				"author", "author2",
				"best", "worst"
			};
			
			/* naïve urlencoded parser, doesn't decode percent escapes */
			while (1) {
				c = getchar();
				if (c == '&' || c == EOF) {
					if (inval) {
						inval = 0;
						if (strcmp(key, "length") == 0)
							length = atoi(val);
						else if (strcmp(key, "start") == 0)
							start = atoi(val);
						else if (strcmp(key, "order") == 0)
							for (iter = 0; iter < 8; ++iter)
								if (strcmp(val, orders[iter]) == 0) {
									order = iter;
									break;
								}
						key[0] = '\0';
					}
					i = 0;
					if (c == EOF)
						break;
				} else if (c == '=') {
					inval = 1;
					i = 0;
				} else if (inval) {
					if (i < 9) {
						val[i++] = c;
						val[i+1] = '\0';
					} else
						val[0] = '\0';
				} else if (i < 5) {
					key[i++] = c;
					key[i+1] = '\0';
				} else
					val[0] = '\0';
			}
			
			fputs("Content-Type: application/x-www-form-urlencoded\n\n&code=", stdout);
			sqlite3_bind_int(sorts[order], 1, start);
			sqlite3_bind_int(sorts[order], 2, length);
			while ((inval = sqlite3_step(sorts[order])) == SQLITE_ROW) {
				int israted = sqlite3_column_type(sorts[order], 3) == SQLITE_NULL;
				printf("%d,%s,%s,%.2f,0,%s,0|",
					sqlite3_column_int(sorts[order], 0),
					sqlite3_column_text(sorts[order], 1),
					sqlite3_column_text(sorts[order], 2),
					sqlite3_column_double(sorts[order], 3),
					israted ? "3,3" : "0,0");
			}
			
			sqlite3_step(count);
			printf("&total=%d", sqlite3_column_int(count, 0));
			sqlite3_reset(count);
		} else if (strcmp(getenv("PATH_INFO"), "/levels/leveldesigner/getstats.php") == 0) {
			int inval, c, i, level;
			char key[4] = "", val[11] = "";
			
			inval = level = i = 0;
			
			while (1) {
				c = getchar();
				if (c == '&' || c == EOF) {
					if (inval) {
						inval = 0;
						if (strcmp(key, "id") == 0)
							level = atoi(val);
					}
					i = 0;
					if (c == EOF)
						break;
				} else if (c == '=') {
					inval = 1;
					i = 0;
				} else if (inval) {
					if (i < 9) {
						val[i++] = c;
						val[i+1] = '\0';
					} else
						val[0] = '\0';
				} else if (i < 2) {
					key[i++] = c;
					key[i+1] = '\0';
				} else
					val[0] = '\0';
			}
			
			puts("Content-Type: application/x-www-form-urlencoded\n");
			sqlite3_bind_int(stats, 1, level);
			if (sqlite3_step(stats) == SQLITE_ROW) {
				fputs("&voteddiff=0&votedscore=0&name=", stdout);
				printescaped(sqlite3_column_text(stats, 1));
				fputs("&author=", stdout);
				printescaped(sqlite3_column_text(stats, 2));
				printf("&score=%.2f&difficulty=0&votes=1&votes1=1&votes2=0&code=", sqlite3_column_double(stats, 5));
				printescaped(sqlite3_column_text(stats, 4));
				fputs("&comment=", stdout);
				printescaped(sqlite3_column_text(stats, 3));
			} else
				fputs("error=yes&reason=NO_LEVEL", stdout);
			sqlite3_reset(stats);
		} else
			fputs("Content-Type: text/plain\nStatus: 404 Not Found\n\nNot found", stdout);
	}
	
	for (i = 0; i < 8; ++i)
		sqlite3_finalize(sorts[i]);
	sqlite3_finalize(count);
	sqlite3_finalize(stats);
	
	if (sqlite3_close(db) != SQLITE_OK)
		fprintf(stderr, "Database did not close cleanly!");
	
	return 0;
}
