#include <stdio.h>
#include <time.h>
#include <netinet/in.h>

typedef struct {
	int             ID;
	char           *title;
	char           *link;
	char           *date;
	int             upVotes;
	int             downVotes;
	void           *next;
}  Article;

Article        *root;

char * getVal(char *query, char *key) {
	char           *str = malloc(1);
	int             match = 0;
	int             pMatch = 0;
	char           *cur = query;
	while (*cur != '\0') {
		if (pMatch >= 1 && (*cur == '&' || *cur == '\n' || *cur == ' ')) {
			break;
		} else if (pMatch >= 1) {
			str = realloc(str, pMatch + 1);
			*(str + pMatch - 1) = *cur;
			++pMatch;
		} else if (match == strlen(key)) {
			pMatch = 1;
		} else if (*cur == key[match]) {
			++match;
		} else {
			match = 0;
		} cur++;
	} if (pMatch >= 1) {
		str[pMatch - 1] = '\0';
		return str;
	} free(str);
	return NULL;
} 

char from_hex(char ch) {
	return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
} 

char to_hex(char code) {
	static char     hex[] = "0123456789abcdef";
	return hex[code & 15];
} 

char * url_decode(char *str) {
	char           *pstr = str, *buf = malloc(strlen(str) + 1), *pbuf = buf;
	while (*pstr) {
		if (*pstr == '%') {
			if (pstr[1] && pstr[2]) {
				*pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
				pstr += 2;
			}
		} else if (*pstr == '+') {
			*pbuf++ = ' ';
		} else {
			*pbuf++ = *pstr;
		} pstr++;
	} *pbuf = '\0';
	return buf;
}

void send_headers(FILE * f, int status, char *title, char *extra, char *mime, int length) {
	char            timebuf[128];
	fprintf(f, "%s %d %s\r\n", "HTTP/1.0", status, title);
	fprintf(f, "Server: %s\r\n", "redditserver/1.0");
	if (extra)
		fprintf(f, "%s\r\n", extra);
	if (mime)
		fprintf(f, "Content-Type: %s\r\n", mime);
	if (length >= 0)
		fprintf(f, "Content-Length: %d\r\n", length);
	fprintf(f, "Connection: close\r\n");
	fprintf(f, "\r\n");
} 

void reddit(FILE * f) {
	fprintf(f, "<HTML><HEAD><TITLE>Reddit Clone</TITLE></HEAD>\r\n");
	fprintf(f, "<BODY><H1>Submit</H1><form action=\"/\" method=\"get\">Title:<input type=\"text\" name=\"title\" /> Link:<input type=\"text\" name=\"link\" /><input type=\"submit\" value=\"Submit\" /></form>\r\n");
	if (root) {
		fprintf(f, "<h1>Articles</h1>");
		Article        *cur = root;
		while (cur != NULL) {
			fprintf(f, "<p><font size=\"5\">%d<b><a href=\"/?up=%d\">&uarr</b></font><a href=\"%s\">%s</a><br /><font size=\"5\">&nbsp;&nbsp;<b><a href=\"/?down=%d\">&darr</a></b></font>%s -- %s</p>", cur->upVotes - cur->downVotes, cur->ID, cur->link, cur->title, cur->ID, cur->link, cur->date);
			cur = cur->next;
		}
	} fprintf(f, "</BODY></HTML>\r\n");
} 


int fsize(FILE * f) {
	fseek(f, 0, SEEK_END);
	int             size = ftell(f);
	fseek(f, 0, SEEK_SET);
	return size;
} 

int main(int argc, char *argv[]) 
{
	int             sock;
	int             port = 1337;
	struct sockaddr_in sin;
	sock = socket(AF_INET, SOCK_STREAM, 0);
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_port = htons(port);
	while (bind(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0) {
		printf("Failed bind\n");
		sleep(2);
	} listen(sock, 5);
	printf("HTTP server listening on port %d\n", port);
	root = NULL;
	while (1) {
		int             s;
		int             size;
		int             hsize;
		char           *buff;
		FILE           *t;
		FILE           *ht;
		FILE           *f;
		s = accept(sock, NULL, NULL);
		if (s < 0)
			break;
		char           *header = calloc(255, 1);
		size = read(s, header, 255);
		char           *titleEnc = getVal(header, "title");
		char           *linkEnc = getVal(header, "link");
		if (titleEnc && linkEnc) {
			char           *title = url_decode(titleEnc);
			char           *link = url_decode(linkEnc);
			free(linkEnc);
			free(titleEnc);
			printf("Title: %s -- Link: %s\n", title, link);
			char           *s = malloc(30);
			size_t          i;
			struct tm       tim;
			time_t          now;
			now = time(NULL);
			tim = *(localtime(&now));
			i = strftime(s, 30, "%b %d, %Y; %H:%M:%S\n", &tim);
			Article        *new = malloc(sizeof(Article));
			new->title = title;
			new->link = link;
			new->next = NULL;
			new->date = s;
			new->upVotes = 0;
			new->downVotes = 0;
			if (!root) {
				root = new;
				new->ID = 0;
			} else {
				int             dupe = 0;
				Article        *cur = root;
				Article        *prev = NULL;
				while (cur != NULL) {
					if (strcmp(cur->title, new->title) == 0 || strcmp(cur->link, new->link) == 0) {
						dupe = 1;
					} prev = cur;
					cur = cur->next;
				} if (dupe == 0) {
					new->ID = prev->ID + 1;
					prev->next = new;
				} else
					free(new);
			}
		} char         *upID = getVal(header, "up");
		if (upID) {
			int             ID = atoi(upID);
			Article        *cur = root;
			while (cur != NULL) {
				if (cur->ID == ID) {
					cur->upVotes++;
					break;
				} cur = cur->next;
			}
		} char         *dID = getVal(header, "down");
		if (dID) {
			int             ID = atoi(dID);
			Article        *cur = root;
			while (cur != NULL) {
				if (cur->ID == ID) {
					cur->downVotes++;
					break;
				} cur = cur->next;
			}
		} t = tmpfile();
		ht = tmpfile();
		reddit(t);
		size = fsize(t);
		send_headers(ht, 200, "Ok", NULL, "text/html", size);
		hsize = fsize(ht);
		buff = malloc(hsize);
		fread(buff, 1, hsize, ht);
		write(s, buff, hsize);
		free(buff);
		buff = malloc(size);
		fread(buff, 1, size, t);
		write(s, buff, size);
		free(buff);
		fclose(t);
	}
}
