#define _GNU_SOURCE
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <tidy/tidy.h>
#include <tidy/tidybuffio.h>
#include <time.h>
#include "config.h"

struct memory {
	char *response;
	size_t size;
};

struct submit_action {
	int contest_id;
	int task_id;
	char *local_file;
	char *reported_filename;
};

static size_t write_callback(void *data, size_t size, size_t nmemb, void *userp) {
	size_t realsize = size *nmemb;
	struct memory *mem = (struct memory *)userp;

	char *ptr = realloc(mem->response, mem->size + realsize + 1);
	if (ptr == NULL)
		return 0;

	mem->response = ptr;
	memcpy(&(mem->response[mem->size]), data, realsize);
	mem->size += realsize;
	mem->response[mem->size] = 0;

	return realsize;
}

static void debug_print_cookies(CURL *curl) {
	CURLcode res;
	struct curl_slist *cookies;
	struct curl_slist *nc;
	int i;

	printf("Cookies, curl knows:\n");
	res = curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookies);
	if (res != CURLE_OK) {
		fprintf(stderr, "Curl curl_easy_getinfo failed: %s\n", curl_easy_strerror(res));
		return;
	}
	nc = cookies;
	i = 0;
	while (nc) {
		printf("[%d]: %s\n", i, nc->data);
		nc = nc->next;
		i++;
	}
	if (i == 0) {
		printf("(none)\n");
	}
	curl_slist_free_all(cookies);
}


void setup_curl(CURL *curl,  struct memory *chunk) {
	chunk->response = NULL;
	chunk->size = 0;
	curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // skip cert verification
	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // skip hostname verification (is it measureably faster?)

	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)chunk);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
}

void free_body(struct memory *chunk) {
	free(chunk->response);
	chunk->response = NULL;
	chunk->size=0;
}

void print_body(CURL *curl, struct memory *chunk) {
	printf("%li\n", chunk->size);
	printf("BEGIN >>>>>>\n");
	for (int i = 0; i < chunk->size; i++) {
		printf("%c", chunk->response[i]);
	}
	printf("\n<<<<<< END\n");
}

void perform_get(CURL *curl, char *url) {
	CURLcode res;
	curl_easy_setopt(curl, CURLOPT_URL, url);

	res = curl_easy_perform(curl);
	if (res != CURLE_OK)
		fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}

void search_csrf(TidyDoc doc, TidyNode tnod, char **csrf) {
	TidyNode child;
	for (child = tidyGetChild(tnod); child; child = tidyGetNext(child) ) {
		ctmbstr name = tidyNodeGetName(child);
		if (name) {
			// It is HTML tag
			TidyAttr attr;

			bool it_is_csrf = false;
			const char *it_has_value = NULL;
			for (attr = tidyAttrFirst(child); attr; attr = tidyAttrNext(attr) ) {
				if (!strcmp(tidyAttrName(attr), "id")) {
					if (!strcmp(tidyAttrValue(attr), "csrf_token")) {
						it_is_csrf = true;
					}
				}
				if (!strcmp(tidyAttrName(attr), "value")) {
					it_has_value = tidyAttrValue(attr);
				}
			}
			if (it_is_csrf && it_has_value) {
				//printf("Yay! csrf token = %s\n", it_has_value);
				if (*csrf)
					free(*csrf);
				*csrf = strdup(it_has_value);
			}
		}
		search_csrf(doc, child, csrf);
	}
}

void extract_csrf(struct memory *chunk, char **csrf) {
	TidyDoc tdoc;
	TidyBuffer docbuf = {0};
	TidyBuffer tidy_errbuf = {0};

	tdoc = tidyCreate();

	tidySetErrorBuffer(tdoc, &tidy_errbuf);
	tidyBufInit(&docbuf);

	// copy response buffer
	tidyBufAppend(&docbuf, chunk->response, chunk->size);

	tidyParseBuffer(tdoc, &docbuf); // real parse
	search_csrf(tdoc, tidyGetRoot(tdoc), csrf); // find element
	//fprintf(stderr, "%s\n", tidy_errbuf.bp); // print errors

	tidyBufFree(&docbuf);
	tidyBufFree(&tidy_errbuf);
	tidyRelease(tdoc);
}

void load_login_page (CURL *curl, struct memory *chunk, char **csrf) {
	perform_get(curl, BASE_URL "/");
	free_body(chunk);

	perform_get(curl, BASE_URL "/auth/login");
	//print_body(curl, chunk);
	//print_cookies(curl);
	extract_csrf(chunk, csrf);
	//printf("csrf token = %s\n", csrf);
	free_body(chunk);
}


void do_login (CURL *curl, struct memory *chunk, char **csrf, char *login_email, char *login_password) {
	char *enc_email = curl_easy_escape(curl, login_email, strlen(login_email));
	char *enc_password = curl_easy_escape(curl, login_password, strlen(login_password));
	char *enc_submit = curl_easy_escape(curl, "Přihlásit se", strlen("Přihlásit se"));
	char *post_data;
	asprintf(&post_data, "csrf_token=%s&email=%s&passwd=%s&submit=%s&next=", *csrf, enc_email, enc_password, enc_submit);
	curl_free(enc_email);
	curl_free(enc_password);
	curl_free(enc_submit);

	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);

	perform_get(curl, BASE_URL "/auth/login");
	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
	curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
	free(post_data);
	free_body(chunk);
}

void load_contest (CURL *curl, struct memory *chunk, int contest_id) {
	char *url;
	asprintf(&url, BASE_URL "/user/contest/%i/", contest_id);
	perform_get(curl, url);
	free(url);

	//print_body(curl, chunk);
	free_body(chunk);
}

void load_task (CURL *curl, struct memory *chunk, char **csrf,  int contest_id, int task_id) {
	char *url;
	asprintf(&url, BASE_URL "/user/contest/%i/task/%i/", contest_id, task_id);
	perform_get(curl, url);
	free(url);

	//print_body(curl, chunk);
	extract_csrf(chunk, csrf);
	//printf("csrf token = %s\n", csrf);
	free_body(chunk);
}

bool submit_task (CURL *curl, struct memory *chunk, char **csrf, int contest_id, int task_id, char *local_file, char *reported_filename) {
	char *url;
	asprintf(&url, BASE_URL "/user/contest/%i/task/%i/", contest_id, task_id);
	curl_mime *mime;
	curl_mimepart *part;

	mime = curl_mime_init(curl);

	part = curl_mime_addpart(mime);
	curl_mime_data(part, *csrf, CURL_ZERO_TERMINATED);
	curl_mime_name(part, "csrf_token");

	part = curl_mime_addpart(mime);
	curl_mime_data(part, "Jenduv automatizovany test submit", CURL_ZERO_TERMINATED);
	curl_mime_name(part, "note");

	part = curl_mime_addpart(mime);
	curl_mime_data(part, "Odevzdat", CURL_ZERO_TERMINATED);
	curl_mime_name(part, "submit");

	part = curl_mime_addpart(mime);
	curl_mime_filedata(part, local_file);
	curl_mime_filename(part, reported_filename);
	curl_mime_name(part, "file");

	curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
	curl_easy_setopt(curl, CURLOPT_URL, url);

	char errbuf[CURL_ERROR_SIZE];
	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
	errbuf[0] = 0;

	CURLcode res = curl_easy_perform(curl);

	if(res != CURLE_OK) {
		size_t len = strlen(errbuf);
		fprintf(stderr, "\nlibcurl: (%d) ", res);
		if(len)
			fprintf(stderr, "%s%s", errbuf,
					((errbuf[len - 1] != '\n') ? "\n" : ""));
		else
			fprintf(stderr, "%s\n", curl_easy_strerror(res));
	}

	free(url);
	curl_easy_setopt(curl, CURLOPT_MIMEPOST, NULL);
	curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
	curl_mime_free(mime);

	long http_code = 0;
	curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
	if (http_code != 200) {
		printf("Post file failed:\n");
		print_body(curl, chunk);
		free_body(chunk);
		return false;
	}
	free_body(chunk);
	return true;
}

bool full_test(char *login_email, char *login_password, struct submit_action *actions) {
	bool ret = true;
	CURL *curl;
	curl_global_init(CURL_GLOBAL_DEFAULT);
	curl = curl_easy_init();

	struct memory chunk;
	char *csrf = NULL;
	if (curl) {
		setup_curl(curl, &chunk);
		load_login_page(curl, &chunk, &csrf);

		do_login(curl, &chunk, &csrf, login_email, login_password);

		while (actions->contest_id) {
			/*
			printf("performing action %i %i %s %s\n", actions->contest_id, actions->task_id,
					actions->local_file, actions->reported_filename);
			*/
			load_contest(curl, &chunk, actions->contest_id);
			load_task(curl,  &chunk,&csrf, actions->contest_id, actions->task_id);

			if (!submit_task(curl, &chunk, &csrf,
					actions->contest_id, actions->task_id,
					actions->local_file, actions->reported_filename)) {
				ret = false;
			}

			actions++;
		}

		// cleanup
		curl_easy_cleanup(curl);
	}
	curl_global_cleanup();
	if (csrf)
		free(csrf);
	return ret;
}

int main() {
	char *login_email = "had+ucastnik@kam.mff.cuni.cz";
	char *login_password = "UTX0seFkVim51vkOwER5G8GjB5eEq7G9";

	struct submit_action actions[] = {
		//{ 25, 3, "/home/had/BratruvZpevnik_v4-0.pdf", "zpevnik.pdf"},
		//{ 25, 4, "testsubmit_02.pdf", "moje_reseni.pdf"},
		//{ 25, 5, "/home/had/sada3.pdf", "sendvic.pdf"},
		//{ 25, 6, "/home/had/stitek.pdf", "sken.pdf"},
		{ 0, 0, NULL, NULL}
	};

	full_test(login_email, login_password, actions);

	return 0;
}