// vim:foldmethod=marker
/* ----------------------------------------------------------------------------    

 Online Moodle/Elearning/KMOOC test help
 Greasyfork: <https://greasyfork.org/en/scripts/38999-moodle-elearning-kmooc-test-help>
 GitLab: <https://gitlab.com/YourFriendlyNeighborhoodDealer/moodle-test-userscript>

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program. If not, see <https://www.gnu.org/licenses/>.

 ------------------------------------------------------------------------- */

// TODO:
// default is not active on new subjects -> TEST

var data; // all data, which is in the resource txt
var addEventListener; // add event listener function
var lastChangeLog =
	'- Performance javítások\n - Összes tárgy letiltása alapból, mert sok tárgy van, és belassulhat :c\n- Az azért jó, hogy sok tárgy van c:\nEllenőrizd, hogy mely tárgyak aktívak a menüben!';
var serverAdress = "https://questionmining.tk/";

// forcing pages for testing. unless you test, do not set these to true!
// only one of these should be true for testing
const forceTestPage = false; 
const forceResultPage = false;
const forceDefaultPage = false;
const logElementGetting = false;
const log = false;

var motdShowCount = 3;
var motd = "";
var lastestVersion = "";

var minMatchAmmount = 49;

//: Class descriptions {{{
class Question {
	constructor(q, a, i) {
		this.Q = q;
		this.A = a;
		this.I = i;
	}
	toString() {
		var r = "?" + this.Q + "\n!" + this.A;
		if (this.I)
			r += "\n>" + this.I;
		return r;
	}
	HasQuestion() {
		return this.Q != undefined;
	}
	HasAnswer() {
		return this.A != undefined;
	}
	HasImage() {
		return this.I != undefined;
	}
	IsComplete() {
		return this.HasQuestion() && this.HasAnswer();
	}
	Compare(q2, i) {
		if (typeof q2 == 'string') {
			var qmatchpercent = Question.CompareString(this.Q, q2);

			if (i == undefined || i.length == 0)
				return qmatchpercent;
			else {
				if (this.HasImage()) {
					const imatchpercent = this.HasImage() ? Question.CompareString(this.I.join(" "), i.join(" ")) :
						0;
					return (qmatchpercent + imatchpercent) / 2;
				} else {
					qmatchpercent -= 30;
					if (qmatchpercent < 0)
						return 0;
					else
						return qmatchpercent;
				}
			}
		} else {
			const qmatchpercent = Question.CompareString(this.Q, q2.Q);
			const amatchpercent = Question.CompareString(this.A, q2.A);
			if (this.I != undefined) {
				const imatchpercent = this.I == undefined ? Question.CompareString(this.I.join(" "), q2.I.join(
					" ")) : 0;
				return (qmatchpercent + amatchpercent + imatchpercent) / 3;
			} else {
				return (qmatchpercent + amatchpercent) / 2;
			}
		}
	}
	static CompareString(s1, s2) {
		//if (s1 == undefined || s2 == undefined)
		//	return 0;
		s1 = SimplifyStringForComparison(s1).split(" ");
		s2 = SimplifyStringForComparison(s2).split(" ");
		var match = 0;
		for (var i = 0; i < s1.length; i++)
			if (s2.includes(s1[i]))
				match++;
		var percent = Math.round(((match / s1.length) * 100).toFixed(2)); // matched words percent
		var lengthDifference = Math.abs(s2.length - s1.length);
		percent -= lengthDifference * 3;
		if (percent < 0)
			percent = 0;
		return percent;
	}
}

class Subject {
	constructor(n) {
		this.Name = n;
		this.Questions = [];
	}
	get length() {
		return this.Questions.length;
	}
	AddQuestion(q) {
		this.Questions.push(q);
	}
	Search(q, img) {
		var r = [];
		for (var i = 0; i < this.length; i++) {
			let percent = this.Questions[i].Compare(q, img);
			if (percent > minMatchAmmount)
				r.push({
					q: this.Questions[i],
					match: percent
				});
		}

		for (var i = 0; i < r.length; i++)
			for (var j = i; j < r.length; j++)
				if (r[i].match < r[j].match) {
					var tmp = r[i];
					r[i] = r[j];
					r[j] = tmp;
				}

		return r;
	}
	toString() {
		var r = [];
		for (var i = 0; i < this.Questions.length; i++)
			r.push(this.Questions[i].toString());
		return "+" + this.Name + "\n" + r.join("\n");
	}
}

class QuestionDB {
	constructor() {
		this.Subjects = [];
	}
	get length() {
		return this.Subjects.length;
	}
	get activeIndexes() {
		var r = [];
		for (var i = 0; i < this.length; i++) {
			if (GM_getValue("Is" + i + "Active")) {
				r.push(i);
			}
		}
		return r;
	}
	GetIfActive(ind) {
		return GM_getValue("Is" + ind + "Active");
	}
	ChangeActive(i, value) {
		GM_setValue("Is" + i + "Active", !!value);
	}
	AddQuestion(subj, q) {
		var i = 0;
		while (i < this.Subjects.length && this.Subjects[i].Name != subj)
			i++;
		if (i < this.Subjects.length)
			this.Subjects[i].AddQuestion(q);
		else {
			const n = new Subject(subj);
			n.AddQuestion(q);
			this.Subjects.push(n);
		}
	}
	Search(q, img) {
		var r = [];
		for (var i = 0; i < this.length; i++)
			if (this.GetIfActive(i))
				r = r.concat(this.Subjects[i].Search(q, img));

		for (var i = 0; i < r.length; i++)
			for (var j = i; j < r.length; j++)
				if (r[i].match < r[j].match) {
					var tmp = r[i];
					r[i] = r[j];
					r[j] = tmp;
				}

		return r;
	}
	AddSubject(subj) {
		var i = 0;
		while (i < this.length && subj.Name != this.Subjects[i].Name)
			i++;

		if (i < this.length) {
			this.Subjects.concat(subj.Questions);
		} else {
			this.Subjects.push(subj);
		}
	}
	toString() {
		var r = [];
		for (var i = 0; i < this.Subjects.length; i++)
			r.push(this.Subjects[i].toString());
		return r.join("\n\n");
	}
}
//: }}}

//: Main function {{{
function Main() {
	'use strict';
	Init(function(count, subjCount) {
		var url = location.href;
		try {
			if ((url.includes("/quiz/") && url.includes("attempt.php")) || forceTestPage) { // if the current page is a test
				HandleQuiz();
			} else if ((url.includes("/quiz/") && url.includes("review.php")) || forceResultPage) { // if the current window is a test-s result
				HandleResults(url);
			} else if ((!url.includes("/quiz/") && !url.includes("review.php") && !url.includes(".pdf")) ||
				(forceDefaultPage)) { // if the current window is any other window than a quiz or pdf.
				HandleUI(url, count, subjCount);
			}
		} catch (e) {
			ShowMessage({
				m: "Fatál error. Check console (f12). Kattints az üzenetre az összes kérdés/válaszért manuális kereséshez!",
				isSimple: true
			}, undefined, function() {
				GM_openInTab(serverAdress + 'legacy', {
					active: true
				});
			});
			Exception(e, "script error at main:");
		}
		if (url.includes("eduplayer")) // if the current site is a video site
			AddVideoHotkeys(url); // adding video hotkeys
		Log(
			"Itteni hibák 100% a moodle hiba. Kivéve, ha oda van írva hogy script error ;) Ha ilyesmi szerepel itt, akkor olvasd el a segítség szekciót! Nagy esélyel a kérdéseket nem lehetett beolvasni."
		);
	});
}
//: }}}

//: DOM getting stuff {{{
// all dom getting stuff are in this sections, so on 
// moodle dom change, stuff breaks here

function GetAllQuestionsDropdown() {
	if (logElementGetting)
		Log("getting dropdown question");
	return document.getElementById("responseform").getElementsByTagName("p")[0].innerText;
}

function GetAllQuestionsQtext() {
	if (logElementGetting)
		Log("getting all questions qtext");
	return document.getElementById("responseform").getElementsByClassName("qtext"); // getting questions
}

function GetAllQuestionsP() {
	if (logElementGetting)
		Log("getting all questions by tag p");
	return document.getElementById("responseform").getElementsByTagName("p");
}

function GetFormulationClearfix() {
	if (logElementGetting)
		Log("getting formulation clearfix lol");
	return document.getElementsByClassName("formulation clearfix");
}

function GetAnswerOptions() {
	// TODO
	if (logElementGetting)
		Log("getting all answer options");
	return GetFormulationClearfix()[0].childNodes[3].innerText;
}

function GetQuestionImages() {
	if (logElementGetting)
		Log("getting question images");
	return GetFormulationClearfix()[0].getElementsByTagName("img");
}

function GetCurrentSubjectName() {
	if (logElementGetting)
		Log("getting current subjects name");
	return document.getElementById("page-header").innerText.split("\n")[0];
}

function GetVideo() {
	if (logElementGetting)
		Log("getting video stuff");
	return document.getElementsByTagName("video")[0];
}

function GetVideoElement() {
	if (logElementGetting)
		Log("getting video element");
	return document.getElementById("videoElement").parentNode;
}

function GetAllAnswer(index) {
	if (logElementGetting)
		Log("getting all answers, ind: " + index);
	return document.getElementsByClassName("answer")[index].childNodes;
}

function GetInputType(answers, i) {
	if (logElementGetting)
		Log("getting input type");
	return answers[i].getElementsByTagName("input")[0].type;
}

function GetFormResult() {
	if (logElementGetting)
		Log("getting form result");
	var t = document.getElementsByTagName("form")[0].childNodes[0].childNodes;
	if (t.length > 0 && t[0].tagName == undefined) // debreceni moodle
		return document.getElementsByTagName("form")[1].childNodes[0].childNodes;
	else
		return t;
}

function GetQText(i) {
	if (logElementGetting)
		Log("getting qtext by index: " + i);
	var results = GetFormResult(); // getting results element
	return results[i].getElementsByClassName("qtext");
}

function GetDropboxes(i) {
	if (logElementGetting)
		Log("getting dropboxes by index: " + i);
	var results = GetFormResult(); // getting results element
	return results[i].getElementsByTagName("select");
}

function GetCurrQuestion(i) {
	if (logElementGetting)
		Log("getting curr questions by index: " + i);
	return document.getElementsByTagName("form")[0].childNodes[0].childNodes[i].childNodes[1].childNodes[
		0].innerText;
}

function GetCurrentAnswer(i) {
	if (logElementGetting)
		Log("getting curr answer by index: " + i);
	var results = GetFormResult(); // getting results element
	var t = results[i].getElementsByClassName("formulation clearfix")[0].getElementsByTagName("span");
	if (t.length > 2)
		return t[1].innerHTML.split("<br>")[1];
}

function GetSelectAnswer() {
	if (logElementGetting)
		Log("getting selected answer");
	var t = document.getElementsByTagName("select");
	if (t.length > 0)
		return t[0].options[document.getElementsByTagName("select")[
			0].selectedIndex].innerText;
}

function GetAnswerNode(i) {
	if (logElementGetting)
		Log("getting answer node");
	var results = GetFormResult(); // getting results element
	var r = results[i].getElementsByClassName("answer")[0].childNodes;
	var ret = [];
	for (var j = 0; j < r.length; j++)
		if (r[j].tagName != undefined && r[j].tagName.toLowerCase() == "div")
			ret.push(r[j]);
	return ret;
}

function GetPossibleAnswers(i) {
	if (logElementGetting)
		Log("getting possible answers");
	var results = GetFormResult(); // getting results element
	var items = GetFormResult()[i].getElementsByTagName("label");
	var r = [];
	for (var j = 0; j < items.length; j++) {

		function TryGetCorrect(j) {
			var cn = items[j].parentNode.className;
			if (cn.includes("correct"))
				return cn.includes("correct") && !cn.includes("incorrect");
		}
		r.push({
			value: items[j].innerText,
			iscorrect: TryGetCorrect(j)
		});
	}
	return r;
}

function GetRightAnswerIfCorrectShown(i) {
	if (logElementGetting)
		Log("getting right answer if correct shown");
	var results = GetFormResult(); // getting results element
	return results[i].getElementsByClassName("rightanswer");
}

function GetWrongAnswerIfCorrectNotShown(i) {
	if (logElementGetting)
		Log("getting wrong answer if correct not shown");
	var results = GetFormResult(); // getting results element
	var n = results[i].getElementsByTagName("i")[0].parentNode;
	if (n.className.includes("incorrect"))
		return results[i].getElementsByTagName("i")[0].parentNode.innerText;
	else
		return "";
}

function GetRightAnswerIfCorrectNotShown(i) {
	if (logElementGetting)
		Log("Getting right answer if correct not shown");
	var results = GetFormResult(); // getting results element
	var n = results[i].getElementsByTagName("i")[0].parentNode;
	if (n.className.includes("correct") && !n.className.includes("incorrect"))
		return results[i].getElementsByTagName("i")[0].parentNode.innerText;
	else
		return "";
}

function GetFormCFOfResult(result) {
	if (logElementGetting)
		Log("getting formulation clearfix");
	return result.getElementsByClassName("formulation clearfix")[0];
}

function GetResultText(i) {
	if (logElementGetting)
		Log("getting result text");
	var results = GetFormResult(); // getting results element
	return GetFormCFOfResult(results[i]).getElementsByTagName("p");
}

function GetResultImage(i) {
	if (logElementGetting)
		Log("getting result image");
	var results = GetFormResult(); // getting results element
	return GetFormCFOfResult(results[i]).getElementsByTagName("img");
}

// this function should return the question, posible answers, and image names
function GetQuestionFromTest() {
	var questions; // the important questions
	var allQuestions; // all questions
	try // trying to get tha current questions
	{
		allQuestions = GetAllQuestionsQtext(); // getting questions
		if (allQuestions.length == 0) // if there are no found questions
		{
			var ddq = GetAllQuestionsDropdown();
			if (EmptyOrWhiteSpace(ddq)) {
				var questionData = "";
				for (var j = 0; j < allQuestions.length; j++) {
					allQuestions = GetAllQuestionsQtext()[j].childNodes;
					for (var i = 0; i < allQuestions.length; i++) {
						if (allQuestions[i].data != undefined && !EmptyOrWhiteSpace(allQuestions[i].data)) // if the current element has some text data to add
						{
							questionData += allQuestions[i].data + " "; // adding text to question data
						}
					}
				}
				questions = [questionData];
			} else
				questions = [ddq];
		} else // if there is
		{
			questions = [];
			for (var i = 0; i < allQuestions.length; i++) {
				questions.push(allQuestions[i].innerText);
			}
		}
	} catch (e) {
		Exception(e, "script error at getting question:");
	}
	var imgNodes = ""; // the image nodes for questions
	try {
		imgNodes = GetQuestionImages(); // getting question images, if there is any
		AddImageNamesToImages(imgNodes); // adding image names to images, so its easier to search for, or even guessing
	} catch (e) {
		Log(e);
		Log("Some error with images");
	}
	return {
		imgnodes: imgNodes,
		allQ: allQuestions,
		q: questions
	};
}

// gets the question from the result page
// i is the index of the question
function GetQuestionFromResult(i) {
	var temp = GetQText(i);
	var currQuestion = "";
	if (temp.length > 0) // if the qtext element is not 0 length
	{
		currQuestion = temp[0].innerText; // adding the question to curr question as .q
	} else {
		// this is black magic fuckery a bit
		if (GetDropboxes(i).length > 0) // if there is dropdown list in the current question
		{
			var allNodes = GetResultText(i);
			currQuestion = "";
			for (var k = 0; k < allNodes.length; k++) {
				var allQuestions = GetResultText(i)[k].childNodes;
				for (var j = 0; j < allQuestions.length; j++) {
					if (allQuestions[j].data != undefined && !EmptyOrWhiteSpace(allQuestions[j].data)) {
						currQuestion += allQuestions[j].data + " ";
					}
				}
			}
		} else {
			try {
				currQuestion = GetCurrQuestion(i);
			} catch (e) {
				currQuestion = "REEEEEEEEEEEEEEEEEEEEE"; // this shouldnt really happen sry guys
			}
		}
	}
	return currQuestion;
}

// tries to get right answer from result page
// i is the index of the question
function GetRightAnswerFromResult(i) {
	var possibleAnswers = GetPossibleAnswers(i);
	var fun = [];

	// the basic type of getting answers
	fun.push(function TryGet0(i) {
		var temp = GetRightAnswerIfCorrectShown(i); // getting risht answer
		if (temp.length > 0) // if the rightanswer element is not 0 length
			return temp[0].innerText; // adding the answer to curr question as .a
	});

	fun.push(function TryGet1(i) {
		if (GetDropboxes(i).length > 0) // if there is dropdown list in the current question
			return GetCurrentAnswer(i);
	});

	// if the correct answers are not shown, and the selected answer
	// is correct
	fun.push(function TryGet2(i) {
		return GetRightAnswerIfCorrectNotShown(i);
	});

	// if there is dropbox in the question
	fun.push(function TryGet3(i) {
		return GetSelectAnswer();
	});

	// if the correct answers are not shown, and the selected answer
	// is incorrect, and there are only 2 options
	fun.push(function TryGet4(i) {
		if (possibleAnswers.length == 2) {
			for (var k = 0; k < possibleAnswers.length; k++)
				if (possibleAnswers[k].iscorrect == undefined)
					return possibleAnswers[k].value;
		}
	});

	fun.push(function TryGetFinal(i) {
		return undefined;
	});

	var j = 0;
	var currAnswer;
	while (j < fun.length && EmptyOrWhiteSpace(currAnswer)) {
		currAnswer = fun[j](i);
		j++;
	}

	return currAnswer;
}

// version 2 of getting right answer from result page
// i is the index of the question
function GetRightAnswerFromResultv2(i) {
	try {
		var items = GetAnswerNode(i);
		for (var j = 0; j < items.length; j++) {
			var cn = items[j].className;
			if (cn.includes("correct") && !cn.includes("incorrect"))
				return items[j].innerText;
		}
		if (items.length == 2) {
			for (var j = 0; j < items.length; j++) {
				var cn = items[j].className;
				if (!cn.includes("correct"))
					return items[j].innerText;
			}
		}
	} catch (e) {
		Log("error at new nodegetting, trying the oldschool way");
	}
}

//: }}}

//: Main logic stuff {{{

//: Loading {{{

function Init(cwith) {
	if (false) // reset, only for testing!
	{
		GM_setValue("version16", undefined);
		GM_setValue("version15", undefined);
		GM_setValue("firstRun", undefined); // GM_getValue("lastVerson") == undefined => firstrun
		GM_setValue("showQuestions", undefined);
		GM_setValue("showSplash", undefined);
	}
	var url = location.href; // window location
	var count = -1; // loaded question count. stays -1 if the load failed.
	// --------------------------------------------------------------------------------------
	// event listener fuckery
	// --------------------------------------------------------------------------------------
	try {
		// adding addeventlistener stuff, for the ability to add more event listeners for the same event
		addEventListener = (function() {
			if (document.addEventListener) {
				return function(element, event, handler) {
					element.addEventListener(event, handler, false);
				};
			} else {
				return function(element, event, handler) {
					element.attachEvent('on' + event, handler);
				};
			}
		}());
	} catch (e) {
		Exception(e, "script error at addEventListener:");
	}
	VersionActions();
	count = Load(cwith); // loads resources
	if (!url.includes(".pdf"))
		ShowMenu();
	return count;
}

function VersionActions() {
	var firstRun = GM_getValue("firstRun"); // if the current run is the frst
	if (firstRun == undefined || firstRun == true) // if its undefined, or true
	{
		GM_setValue("firstRun", false); // setting it to false
		ShowHelp(); // showing help
	}
	var version15 = GM_getValue("version15"); // if the current run is the frst
	if (version15 == undefined || version15 == true) // if its undefined, or true
	{
		GM_setValue("useNetDB", "0");
		GM_setValue("version15", false); // setting it to false
		GM_setValue("firstRun", true);
		document.write(
			'<h1>Moodle teszt userscript:<h1><h3>1.5.0 verzió: a script mostantól XMLHTTP kéréseket küld szerver fele! Erre a userscript futtató kiegészitőd is figyelmeztetni fog! Ha ez történik, a script rendes működése érdekében engedélyezd! Ha nem akarod, hogy ez történjen, akkor ne engedélyezd, vagy a menüben válaszd ki a "helyi fájl használata" opciót!</h3> <h3>Elküldött adatok: minden teszt után a kérdés-válasz páros. Fogadott adatok: Az összes eddig ismert kérdés. Érdemes help-et elolvasni!!!</h3><h5>Ez az ablak frissités után eltűnik. Ha nem, akkor a visza gombbal próbálkozz.</h5>'
		);
		document.close();
		throw "something, so this stuff stops";
	}

	var version16 = GM_getValue("version16"); // if the current run is the frst
	if (version16 == undefined || version16 == true) // if its undefined, or true
	{
		var i = 0;
		while (GM_getValue("Is" + i + "Active") != undefined) {
			GM_setValue("Is" + i + "Active", false);
			i++;
		}
		GM_setValue("version16", false); // setting it to false
	}

}

function ReadFile(cwith) {
	var resource = "";
	try {
		resource = GM_getResourceText("data"); // getting data from txt
		if (resource == null) {
			throw {
				message: "data file not found"
			};
		}
		if (EmptyOrWhiteSpace(resource)) {
			throw {
				message: "data file empty"
			};
		}
	} catch (e) {
		Exception(e, "script error at reading file:");
	}
	NLoad(resource, cwith);
}

function ReadNetDB(cwith, useNetDB) {
	function NewXMLHttpRequest() {
		const url = serverAdress + "data.json";
		GM_xmlhttpRequest({
			method: "GET",
			synchronous: true,
			url: url,
			onload: function(response) {
				NLoad(response.responseText, cwith);
			},
			onerror: function() {
				NLoad(undefined, cwith); // server down
			}
		});
	}
	try {
		Log("Sending XMLHTTP Request...");
		return NewXMLHttpRequest();
	} catch (e) {
		Exception(e, "script error at reading online database:");
	}
}

/*
 * Returns a question database from the given data.
 * Parameter should be raw read file in string with "\n"-s
 * TODO: ??? -s are not listed as errors, tho works correctly
 * */
function ParseRawData(data) {

	const d = data.split("\n");
	const r = new QuestionDB();
	var logs = [];
	var currSubj = ""; // the current subjects name
	var ExpectedIdentifier = ['+', '?'];
	let currQuestion = new Question();

	var i = -1;
	while (i < d.length) {
		let currIdentifier;
		let skipped = 0;
		do {
			if (skipped >= 1)
				logs.push(i + ": " + d[i]);
			i++;
			if (i >= d.length) {
				if (currQuestion.IsComplete())
					r.AddQuestion(currSubj, currQuestion);
				return {
					result: r,
					logs: logs
				};
			}
			currIdentifier = d[i][0];
			skipped++;
		} while (!ExpectedIdentifier.includes(currIdentifier) && i < d.length);

		let currData = d[i].substring(1).trim();

		if (currIdentifier == '+') {
			if (currQuestion.IsComplete())
				r.AddQuestion(currSubj, currQuestion);
			currQuestion = new Question();
			currSubj = currData;
			ExpectedIdentifier = ['?'];
			continue;
		}

		if (currIdentifier == '?') {
			if (currQuestion.IsComplete()) {
				r.AddQuestion(currSubj, currQuestion);
				currQuestion = new Question();
			}
			// overwriting is allowed here, bcus:
			// ?????!>
			currQuestion.Q = currData;
			ExpectedIdentifier = ['!', '?'];
			continue;
		}

		if (currIdentifier == '!') {
			// if dont have question continue
			if (!currQuestion.HasQuestion())
				throw "No question! (A)";
			// dont allow overwriting
			// ?!!!! 
			if (!currQuestion.HasAnswer()) {
				currData = currData.replace("A helyes válaszok: ", "");
				currData = currData.replace("A helyes válasz: ", "");

				currQuestion.A = currData;
			}
			ExpectedIdentifier = ['?', '>', '+'];
			continue;
		}

		if (currIdentifier == '>') {
			// if dont have question or answer continue
			if (!currQuestion.HasQuestion())
				throw "No question! (I)";
			if (!currQuestion.HasAnswer())
				throw "No asnwer! (I)";
			// dont allow overwriting
			// ?!>>>
			if (!currQuestion.HasImage()) {
				try {
					currQuestion.I = JSON.parse(currData);
				} catch (e) {
					currQuestion.I = [currData];
				}
			}
			ExpectedIdentifier = ['?', '+'];
			continue;
		}
	}

	return {
		result: r,
		logs: logs
	};
}

function Load(cwith) {
	var useNetDB = GM_getValue("useNetDB");
	if (useNetDB != undefined && useNetDB == 1)
		return ReadNetDB(cwith, useNetDB);
	else
		return ReadFile(cwith);
}

function LoadMOTD(resource) {
	try {
		motd = resource.motd;
	} catch (e) {
		Log("Error loading motd :c");
		Log(e);
	}
}

function LoadVersion(resource) {
	try {
		lastestVersion = resource.version;
	} catch (e) {
		Log("Error loading version :c");
		Log(e);
	}
}

// loading stuff
function NLoad(resource, cwith) {
	var count = -1;
	var subjCount = 0;
	try {
		var d = {};
		try {
			d = JSON.parse(resource);
		} catch (e) {
			Log("Old data, trying with old methods....");
			d = ParseRawData(resource).result;
		}
		var r = new QuestionDB();
		var rt = [];
		var allCount = -1;
		LoadMOTD(d);
		LoadVersion(d);

		for (var i = 0; i < d.Subjects.length; i++) {
			let s = new Subject(d.Subjects[i].Name);
			if (GM_getValue("Is" + i + "Active")) {
				var j = 0;
				for (j = 0; j < d.Subjects[i].Questions.length; j++) {
					var currQ = d.Subjects[i].Questions[j];
					s.AddQuestion(new Question(currQ.Q, currQ.A, currQ.I));
				}
				rt.push({
					name: d.Subjects[i].Name,
					count: j
				});
				allCount += j;
				subjCount++;
			}
			r.AddSubject(s);
		}
		data = r;
		count = allCount + 1; // couse starting with -1 to show errors

		var i = 0;
		while (i < data.length && !GM_getValue("Is" + i + "Active")) {
			i++;
		}
		if (i >= data.length)
			document.getElementById("HelperMenuButton").style.background = "yellow";

	} catch (e) {
		Exception(e, "script error at loading:");
		count = -1; // returns -1 if error
	}
	cwith(count, subjCount);
}

//: }}}

//: UI handling {{{
function HandleUI(url, count, subjCount) {
	var newVersion = false; // if the script is newer than last start
	var loaded = count != -1; // if script could load stuff

	try // try, cus im suspicious
	{
		newVersion = GM_info.script.version !== GM_getValue("lastVerson");
	} catch (e) {}
	var greetMsg = ""; // message to show at the end
	var timeout = null; // the timeout. if null, it wont be hidden
	// no new version, nothing loaded
	if (!newVersion && !loaded) // --------------------------------------------------------------------------------------------------------------
	{
		greetMsg =
			"Hiba a @resource tagnál, vagy a fileval van gond! (Lehet át lett helyezve, vagy üres.) Ellenőrizd az elérési utat, vagy hogy a Tampermonkey bővítmény eléri-e a fájlokat. Ha netes forrást használsz, akkor nem elérhető a szerver! Segítségért kattints!";
	}
	var showSplash = (GM_getValue("showSplash") == undefined) || GM_getValue("showSplash"); // getting value, if splash screen should be shown. Its true, if its undefined, or true
	// no new version, everything loaded, and show splash is enabled. otherwise something happened, so showing it
	if (!newVersion && loaded && showSplash) // ------------------------------------------------------------------------------------------------
	{
		timeout = 5;
		greetMsg = "Moodle/Elearning/KMOOC segéd v. " + GM_info.script.version + ". ";

		if (lastestVersion != undefined && GM_info.script.version != lastestVersion) {
			greetMsg += "Új verzió elérhető: " + lastestVersion + " ";
			timeout = undefined;
		}
		greetMsg += count + " kérdés és " + subjCount + " tárgy betöltve. (click for help).";
		if (data.length > 0) {
			var toAdd = [];
			for (var i = 0; i < data.length; i++) {
				if (data.GetIfActive(i)) {
					toAdd.push(data.Subjects[i].Name + " (" + data.Subjects[i].length + ")");
				}
			}
			if (toAdd.length != 0) {
				greetMsg += "\nAktív tárgyak: " + toAdd.join(", ") + ".";
			} else {
				greetMsg += "\nNincs aktív tárgyad. Menüből válassz ki eggyet!";
				timeout = undefined;
			}
		} else {
			greetMsg += " Az adatfájlban nem adtál meg nevet. Vagy nem elérhető a szerver. Katt a helpért!";
		}
	}
	// new version, nothing loaded
	if (newVersion && !loaded) // --------------------------------------------------------------------------------------------------------------
	{
		greetMsg = "Moodle/Elearning/KMOOC segéd v. " + GM_info.script.version +
			". Új verzió!\n Írd át a @resouce tagnál az elírési utat! Kivéve ha üres a file, akkor töltsd fel :) Nincs kérdés betöltve! Segítséghez kattints. Changelog:\n" +
			lastChangeLog; // showing changelog too
	}
	// new version, everything loaded -> set lastVerson to current
	if (newVersion && loaded) // --------------------------------------------------------------------------------------------------------------
	{
		greetMsg = "Moodle/Elearning/KMOOC segéd v. " + GM_info.script.version + ". " + count +
			" kérdés és " + subjCount + " tárgy betöltve. Verzió frissítve " + GM_info.script.version +
			"-re. Changelog:\n" + lastChangeLog;
		GM_setValue("lastVerson", GM_info.script.version); // setting lastVersion
	}
	if (!EmptyOrWhiteSpace(motd)) {

		var prevmotd = GM_getValue("motd");
		if (prevmotd != motd) {
			greetMsg += "\nMOTD:\n" + motd;
			timeout = null;
			GM_setValue("motdcount", motdShowCount);
			GM_setValue("motd", motd);
		} else {
			var motdcount = GM_getValue("motdcount");
			if (motdcount == undefined) {
				GM_setValue("motdcount", motdShowCount);
				motdcount = motdShowCount;
			}

			motdcount--;
			if (motdcount > 0) {
				greetMsg += "\nMOTD:\n" + motd;
				timeout = null;
				GM_setValue("motdcount", motdcount);
			}
		}
	}
	ShowMessage({
		m: greetMsg,
		isSimple: true
	}, timeout, ShowHelp); // showing message. If "m" is empty it wont show it, thats how showSplash works.
}

//: }}}

//: Answering stuffs {{{

function HandleQuiz() {
	var q = GetQuestionFromTest();
	var questions = q.q;
	var allQuestions = q.allQ;
	var imgNodes = q.imgnodes;
	// ------------------------------------------------------------------------------------------------------
	var answers = [];
	for (var j = 0; j < questions.length; j++) // going thru all answers
	{
		var question = RemoveUnnecesarySpaces(questions[j]); // simplifying question
		var result = data.Search(question, SimplifyImages(imgNodes));
		var r = PrepareAnswers(result, j);
		if (r != undefined)
			answers.push(r);
		HighLightAnswer(result, j); // highlights the answer for the current result
	}
	console.log(GetAnswerOptions());
	ShowAnswers(answers);
}

function PrepareAnswers(result, j) {
	if (result.length > 0) // if there are more than zero results
	{
		var allMessages = []; // preparing all messages
		for (var k = 0; k < result.length; k++) // going throuh all results
		{
			var msg = ""; // the current message
			if ((GM_getValue("showQuestions") == undefined) || GM_getValue("showQuestions")) // if the question should be shown
			{
				msg += result[k].q.Q + "\n"; // adding the question if yes
			}
			msg += result[k].q.A.replace(/, /g, "\n"); // adding answer
			if (result[k].q.HasImage()) // and adding image, if it exists
			{
				msg += "\n" + result[k].q.I; // if it has image part, adding that too
			}
			allMessages.push({
				m: msg,
				p: result[k].match
			});
		}
		return allMessages;
	}
}

function ShowAnswers(answers) {
	if (answers.length > 0) { // if there are more than 0 answer
		ShowMessage(answers);
	} else {
		ShowMessage({
			m: "Nincs találat :( Kattints az üzenetre az összes kérdés/válaszért manuális kereséshez! Előfordulhat, hogy a tárgyat nem válsztottad ki a menüben.",
			isSimple: true
		}, undefined, function() {
			GM_openInTab(serverAdress + 'legacy', {
				active: true
			});
		});
	}
}

//: }}}

//: Quiz saving {{{

function HandleResults(url) {
	var allResults = new QuestionDB();
	SaveQuiz(GetQuiz(), data); // saves the quiz questions and answers
}

function ShowSaveQuizDialog(addedQ, allQ, allOutput, output, sendSuccess, sentData) {
	var msg = "";
	if (addedQ > 0) // if there are more than 0 new question
	{
		msg = "Klikk ide a nyers adatokhoz. " + addedQ +
			" új kérdés! Ne felejtsd el bemásolni a fő txt-be!";

		var useNetDB = GM_getValue("useNetDB");
		if (useNetDB != undefined && useNetDB == 1) {
			if (!sendSuccess)
				msg += " Nem sikerült kérdéseket elküldeni szervernek. Ha gondolod utánanézhetsz.";
			else
				msg += "Az új kérdések elküldve.";
		}
		msg += "Az új kérdések elküldve.";
	} else // if there is 0 or less new question
	{
		msg = "A kérdőívben nincsen új kérdés. Ha mégis le akarod menteni klikk ide.";
		if (!data)
			msg += " Lehet azért, mert nincs kérdés betöltve.";
	}
	// showing a message wit the click event, and the generated page
	ShowMessage({
		m: msg,
		isSimple: true
	}, null, function() {
		var towrite = '<h3>TXT-ben nem szereplő kérdések: ' + addedQ + '/' + allQ + '</h3><br>' +
			output.replace(/\n/g, '<br>') + '<br><h3>Összes kérdés/válasz:</h3>' + allOutput.replace(
				/\n/g, '<br>');

		var useNetDB = GM_getValue("useNetDB");
		if (useNetDB != undefined && useNetDB == 1)
			towrite += "</p>Elküldött adatok:</p> " + sentData;
		document.write(towrite);
		document.close();
	});
}

function SearchSameQuestion(questionData, quiz, i) {
	var r = questionData.Search(quiz[i].Q);
	return r.length == 0 ? -1 : r.length;
}

// this should get the image url from a result page
// i is the index of the question
function GetImageFormResult(i) {
	var temp = null;
	try {
		var imgElements = GetResultImage(i); // trying to get image
		var imgURL = []; // image urls
		for (var j = 0; j < imgElements.length; j++) {
			if (!imgElements[j].src.includes("brokenfile")) // idk why brokenfile is in some urls, which are broken, so why tf are they there damn moodle
			{
				var filePart = imgElements[j].src.split("/"); // splits the link by "/"
				filePart = filePart[filePart.length - 1]; // the last one is the image name
				imgURL.push(decodeURI(ShortenString(filePart, 30)));
			}
		}
		if (imgURL.length > 0) {
			temp = JSON.stringify(imgURL);
			return temp;
		}
	} catch (e) {}
}

// saves the current quiz. questionData contains the active subjects questions
function SaveQuiz(quiz, questionData) {
	try {
		if (quiz.length == 0)
			throw {
				message: "quiz length is zero!",
				stack: "no stack."
			};
		var output = ""; // thefinal output
		var allOutput = ""; // thefinal output with all questions
		var allQ = 0;
		var addedQ = 0;
		var newQuestions = [];
		for (var i = 0; i < quiz.length; i++) // going though quiz
		{
			// searching for same questions in questionData
			var toAdd = ""; // this will be added to some variable depending on if its already in the database
			toAdd += "?" + RemoveUnnecesarySpaces(quiz[i].Q) + "\n"; // adding quiz question
			toAdd += "!" + RemoveUnnecesarySpaces(quiz[i].A) + "\n"; // adding quiz answer
			if (quiz[i].HasImage()) {
				toAdd += ">" + RemoveUnnecesarySpaces(quiz[i].I) + "\n"; // adding quiz image if there is any
			}
			if (SearchSameQuestion(questionData, quiz, i) == -1) // if there is no such item in the database (w/ same q and a)
			{
				output += toAdd; // adding to output
				newQuestions.push(quiz[i]);
				addedQ++;
			}
			allOutput += toAdd; // adding to all
			allQ++;
		}
		var sendSuccess = false;
		var sentData = {};
		try {
			try {
				sentData.subj = GetCurrentSubjectName();
			} catch (e) {
				sentData.subj = "NOSUBJ";
				Log("unable to get subject name :c");
			}
			var useNetDB = GM_getValue("useNetDB");
			if (useNetDB != undefined && useNetDB == 1) {
				sentData.allData = quiz;
				sentData.data = newQuestions;
				sentData.version = GM_info.script.version;
				SendXHRMessage("datatoadd=" + JSON.stringify(sentData));
				sendSuccess = true;
			}
		} catch (e) {
			Exception(e, "error at sending data to server.");
		}
		ShowSaveQuizDialog(addedQ, allQ, allOutput, output, sendSuccess, sentData);
	} catch (e) {
		Exception(e, "script error at saving quiz");
	}
}


// getting quiz from finish page
function GetQuiz() {
	try {
		var quiz = []; // final quiz stuff
		var results = GetFormResult(); // getting results element
		for (var i = 0; i < results.length - 2; i++) // going through results, except last two, idk why, dont remember, go check it man
		{
			var question = {}; // the current question
			// QUESTION --------------------------------------------------------------------------------------------------------------------
			var q = GetQuestionFromResult(i);
			if (q != undefined)
				question.q = SimplifyQuery(q);
			// RIGHTANSWER ---------------------------------------------------------------------------------------------------------------------
			var a = GetRightAnswerFromResultv2(i);
			if (a == undefined)
				a = GetRightAnswerFromResult(i);
			if (a != undefined)
				question.a = SimplifyQuery(a);
			// IMG ---------------------------------------------------------------------------------------------------------------------
			var img = GetImageFormResult(i);
			question.i = img;

			if (question.a != undefined) // adding only if has question
			{
				quiz.push(new Question(question.q, question.a, question.i)); // adding current question to quiz
			} else {
				Log("error getting queston, or its incorrect");
				Log(question);
			}
		}
		return quiz;
	} catch (e) {
		Exception(e, "script error at quiz parsing:");
	}
}


//: }}}

//: Helpers {{{

function SimplifyImages(imgs) {
	var questionImages = []; // the array for the image names in question
	for (var i = 0; i < imgs.length; i++) // going through all image
	{
		if (!imgs[i].src.includes("brokenfile")) // if its includes borken file its broken. Its another moodle crap. So i just wont check those
		{
			var filePart = imgs[i].src.split("/"); // splits the link by "/"
			filePart = filePart[filePart.length - 1]; // the last one is the image name
			questionImages.push(decodeURI(RemoveUnnecesarySpaces(ShortenString(filePart, 30)))); // decodes uri codes, and removes exess spaces, and shortening it
		}
	}
	return questionImages;
}

// adds image names to image nodes
function AddImageNamesToImages(imgs) {
	for (var i = 0; i < imgs.length; i++) // going through all image
	{
		if (!imgs[i].src.includes("brokenfile")) // if its includes borken file its broken. Its another moodle crap. So i just wont check those
		{
			var filePart = imgs[i].src.split("/"); // splits the link by "/"
			filePart = filePart[filePart.length - 1]; // the last one is the image name
			var appedtTo = imgs[i].parentNode; // it will be appended here
			var mainDiv = document.createElement("div");
			var fileName = ShortenString(decodeURI(filePart), 15); // shortening name, couse it can be long as fuck
			var textNode = document.createTextNode("(" + fileName + ")");
			mainDiv.appendChild(textNode);
			appedtTo.appendChild(mainDiv);
		}
	}
}

// this function adds basic hotkeys for video controll.
function AddVideoHotkeys(url) {
	var seekTime = 20;
	document.addEventListener("keydown", function(e) // keydown event listener
		{
			try {
				var video = GetVideo();
				var keyCode = e.keyCode; // getting keycode
				if (keyCode == 32) // if the keycode is 32 (space)
				{
					e.preventDefault(); // preventing default action (space scrolles down)
					if (video.paused && video.buffered.length > 0) {
						video.play();
					} else {
						video.pause();
					}
				}
				if (keyCode == 39) // rigth : 39
				{
					video.currentTime += seekTime;
				}
				if (keyCode == 37) // left : 37
				{
					video.currentTime -= seekTime;
				}
			} catch (err) {
				Log("Hotkey error.");
				Log(err.message);
			}
		});
	var toadd = GetVideoElement();
	var node = CreateNodeWithText(toadd,
		"Miután elindítottad: Play/pause: space. Seek: Bal/jobb nyíl.");
	node.style.margin = "5px 5px 5px 5px"; // fancy margin
}

function GetMatchPercent(currData, questionParts) {
	var currQuestion = SimplifyQuery(currData.q); // current question simplified
	var match = 0; // how many times the current question matches the current question in the database
	for (var i = 0; i < questionParts.length; i++) // going through the question parts
	{
		if (currQuestion.includes(questionParts[i])) // if the current question from questionData includes one of the question parts
		{
			match++;
		}
	}
	var percent = Math.round(((match / questionParts.length) * 100).toFixed(2)); // matched words percent
	var lengthDifference = RemoveMultipleItems(SimplifyQuery(currQuestion).split(" ")).length -
		questionParts.length;
	percent -= Math.abs(lengthDifference) * 2;
	return percent;
}

// simple sort.
function SortByPercent(results) {
	for (var i = 0; i < results.length; i++) {
		for (var j = results.length - 1; j > i; j--) {
			if (results[i].p < results[j].p) {
				var temp = results[i];
				results[i] = results[j];
				results[j] = temp;
			}
		}
	}
	return results;
}

// removes stuff like " a. q1" -> "q1"
function RemoveLetterMarking(inp) {
	return RemoveUnnecesarySpaces(inp.substr(inp.indexOf(".") + 1, inp.length));
}

// highlights the possible solutions to the current question
function HighLightAnswer(results, currQuestionNumber) {
	try {
		if (results.length > 0) // if there are items in the result
		{
			var answers = GetAllAnswer(currQuestionNumber); // getting all answers
			var toColor = []; // the numberth in the array will be colored, and .length items will be colored
			var type = ""; // type of the question. radio or ticbox or whatitscalled
			for (var i = 0; i < answers.length; i++) // going thtough answers
			{
				if (answers[i].tagName && answers[i].tagName.toLowerCase() == "div") // if its not null and is "div"
				{
					var correct = results[0].q.A.toLowerCase(); // getting current correct answer from data
					var answer = answers[i].innerText.replace(/\n/g, "").toLowerCase(); // getting current answer

					// removing stuff like "a."
					answer = RemoveLetterMarking(answer);

					if (NormalizeSpaces(RemoveUnnecesarySpaces(correct)).includes(answer)) // if the correct answer includes the current answer
					{
						toColor.push(i); // adding the index
						type = GetInputType(answers, i); // setting the type
					}
				}
			}
			if (results[0].match == 100) // if the result is 100% correct
				if (type !== "radio" || toColor.length == 1) // if the type is not radio or there is exactly one correct solution
					for (var i = 0; i < toColor.length; i++) // going through "toColor"
						answers[toColor[i]].style.backgroundColor = "#8cff66"; // and coloring the correct index
		}
	} catch (e) // catching errors. Sometimes there are random errors, wich i did not test, but they are rare, and does not break the main script.
	{
		Log("script error at highlightin answer: " + e.message);
	}
}

//: }}}

function Log(value) {
	if (log)
		console.log(value);
}

function Exception(e, msg){
		Log("------------------------------------------");
		Log(msg);
		Log(e.message);
		Log("------------------------------------------");
		Log(e.stack);
		Log("------------------------------------------");
}

//: }}}

//: Minor UI stuff {{{

// shows a message with "msg" text, "matchPercent" tip and transp, and "timeout" time
function ShowMessage(msgItem, timeout, funct) {
	// msgItem help:
	// [ [ {}{}{}{} ] [ {}{}{} ] ]
	// msgItem[] <- a questions stuff
	// msgItem[][] <- a questions relevant answers array
	// msgItem[][].p <- a questions precent
	// msgItem[][].m <- a questions message
	try {
		var defMargin = "0px 5px 0px 5px";
		var isSimpleMessage = false;
		var simpleMessageText = "";
		if (msgItem.isSimple) // parsing msgItem for easier use
		{
			simpleMessageText = msgItem.m;
			if (simpleMessageText == "") {
				return;
			}
			msgItem = [
				[{
					m: simpleMessageText
				}]
			];
			isSimpleMessage = true;
		}

		var appedtTo = document.body; // will be appended here
		var width = window.innerWidth - window.innerWidth / 6; // with of the box
		var startFromLeft = window.innerWidth / 2 - width / 2; // dont change this
		var startFromTop = 25; // top distance

		var mainDiv = document.createElement("div"); // the main divider, wich items will be attached to
		mainDiv.setAttribute("id", "messageMainDiv");
		if (funct) // if there is a function as parameter
		{
			addEventListener(mainDiv, 'click', funct); // adding it as click
		}
		// lotsa crap style
		mainDiv.style.position = "fixed";
		mainDiv.style.zIndex = 999999;
		mainDiv.style.textAlign = "center";
		mainDiv.style.width = width + 'px';
		//mainDiv.style.height = height + 'px';
		mainDiv.style.padding = "0px";
		mainDiv.style.background = "#222d32"; // background color
		mainDiv.style.color = "#ffffff"; // text color
		mainDiv.style.borderColor = "#035a8f"; // border color
		mainDiv.style.border = "solid";
		mainDiv.style.top = (startFromTop) + 'px';
		mainDiv.style.left = (window.innerWidth - width) / 2 + 'px';
		mainDiv.style.opacity = "0.9"; // setting starting opacity
		var matchPercent = msgItem[0][0].p;
		if (isSimpleMessage) {
			var simpleMessageParagrapg = document.createElement("p"); // new paragraph
			simpleMessageParagrapg.style.margin = defMargin; // fancy margin
			var splitText = simpleMessageText.split("\n");
			for (var i = 0; i < splitText.length; i++) {
				var mesageNode = CreateNodeWithText(simpleMessageParagrapg, splitText[i]);
				mesageNode.style.margin = defMargin; // fancy margin
			}
			mainDiv.appendChild(simpleMessageParagrapg); // adding text box to main div
		} else // if its a fucking complicated message
		{
			// TABLE SETUP ------------------------------------------------------------------------------------------------------------
			var table = document.createElement('table');
			table.style.width = "100%";
			// ROWS -----------------------------------------------------------------------------------------------------
			var rowOne = table.insertRow(); // previous suggestion, question text, and prev question
			var rowTwo = table.insertRow(); // next question button
			var rowThree = table.insertRow(); // next suggetsion button
			// CELLS -----------------------------------------------------------------------------------------------------
			// row one
			var numberTextCell = rowOne.insertCell();
			var questionCell = rowOne.insertCell(); // QUESTION CELL
			questionCell.setAttribute("id", "questionCell");
			questionCell.rowSpan = 3;
			questionCell.style.width = "90%";
			var prevQuestionCell = rowOne.insertCell();
			// row two
			var percentTextCell = rowTwo.insertCell();
			var nextQuestionCell = rowTwo.insertCell();
			// row three
			var prevSuggestionCell = rowThree.insertCell();
			var nextSuggestionCell = rowThree.insertCell();
			// adding finally
			mainDiv.appendChild(table);
			// PERCENT TEXT SETUP -----------------------------------------------------------------------------------------------------
			var percentTextBox = CreateNodeWithText(percentTextCell, "");
			percentTextBox.setAttribute("id", "percentTextBox");

			if (matchPercent) // if match percent param is not null
			{
				percentTextBox.innerText = matchPercent + "%";
			}
			// NUMBER SETUP -----------------------------------------------------------------------------------------------------
			var numberTextBox = CreateNodeWithText(numberTextCell, "1.");
			numberTextBox.setAttribute("id", "numberTextBox");

			// ANSWER NODE SETUP -------------------------------------------------------------------------------------------------------------
			var questionTextElement = CreateNodeWithText(questionCell, "ur question goes here, mister OwO");
			questionTextElement.setAttribute("id", "questionTextElement");

			// BUTTON SETUP -----------------------------------------------------------------------------------------------------------
			var currItem = 0;
			var currRelevantQuestion = 0;

			function GetRelevantQuestion() // returns the currItemth questions currRelevantQuestionth relevant question
			{
				return msgItem[currItem][currRelevantQuestion];
			}

			function ChangeCurrItemIndex(to) {
				currItem += to;
				if (currItem < 0) {
					currItem = 0;
				}
				if (currItem > msgItem.length - 1) {
					currItem = msgItem.length - 1;
				}
				currRelevantQuestion = 0;
			}

			function ChangeCurrRelevantQuestionIndex(to) {
				currRelevantQuestion += to;
				if (currRelevantQuestion < 0) {
					currRelevantQuestion = 0;
				}
				if (currRelevantQuestion > msgItem[currItem].length - 1) {
					currRelevantQuestion = msgItem[currItem].length - 1;
				}
			}

			function SetQuestionText() {
				var relevantQuestion = GetRelevantQuestion();
				questionTextElement.innerText = relevantQuestion.m;
				if (currItem == 0 && currRelevantQuestion == 0) {
					numberTextBox.innerText = (currRelevantQuestion + 1) + ".";
				} else {
					numberTextBox.innerText = (currItem + 1) + "./" + (currRelevantQuestion + 1) + ".";
				}
				percentTextBox.innerText = relevantQuestion.p + "%";
			}

			var buttonMargin = "2px 2px 2px 2px"; // uniform button margin
			if (msgItem[currItem].length > 1) {
				// PREV SUGG BUTTON ------------------------------------------------------------------------------------------------------------
				var prevSuggButton = CreateNodeWithText(prevSuggestionCell, "<", "button");
				prevSuggButton.style.margin = buttonMargin; // fancy margin

				prevSuggButton.addEventListener("click", function() {
					ChangeCurrRelevantQuestionIndex(-1);
					SetQuestionText();
				});
				// NEXT SUGG BUTTON ------------------------------------------------------------------------------------------------------------
				var nextSuggButton = CreateNodeWithText(nextSuggestionCell, ">", "button");
				nextSuggButton.style.margin = buttonMargin; // fancy margin

				nextSuggButton.addEventListener("click", function() {
					ChangeCurrRelevantQuestionIndex(1);
					SetQuestionText();
				});
			}
			// deciding if has multiple questions ------------------------------------------------------------------------------------------------
			if (msgItem.length == 1) {
				SetQuestionText();
			} else // if there are multiple items to display
			{
				// PREV QUESTION BUTTON ------------------------------------------------------------------------------------------------------------
				var prevButton = CreateNodeWithText(prevQuestionCell, "^", "button");
				prevButton.style.margin = buttonMargin; // fancy margin

				//event listener
				prevButton.addEventListener("click", function() {
					ChangeCurrItemIndex(-1);
					SetQuestionText();
				});
				//NEXT QUESTION BUTTON ------------------------------------------------------------------------------------------------------------
				var nextButton = CreateNodeWithText(nextQuestionCell, "ˇ", "button");
				nextButton.style.margin = buttonMargin; // fancy margin

				//event listener
				nextButton.addEventListener("click", function() {
					ChangeCurrItemIndex(1);
					SetQuestionText();
				});
				SetQuestionText();
			}
		}
		appedtTo.appendChild(mainDiv); // THE FINAL APPEND

		//setting some events
		//addEventListener(window, 'scroll', function () {
		//	mainDiv.style.top = (pageYOffset + startFromTop) + 'px';
		//})
		addEventListener(window, 'resize', function() {
			mainDiv.style.left = (window.innerWidth - width) / 2 + 'px';
		});
		var timeOut;
		if (timeout && timeout > 0) // setting timeout if not zero or null
		{
			timeOut = setTimeout(function() {
				mainDiv.parentNode.removeChild(mainDiv);
			}, timeout * 1000);
		}
		// middle click close event listener
		addEventListener(mainDiv, 'mousedown', function(e) {
			if (e.which == 2) {
				mainDiv.parentNode.removeChild(mainDiv);
				if (timeOut) {
					clearTimeout(timeOut);
				}
			}
		});
	} catch (e) {
		Exception(e, "script error at showing message:");
	}
}

// shows a fancy menu
function ShowMenu() {
	try {
		var buttonWidth = 75; // button size ;)
		var buttonHeight = 55;
		var appedtTo = document.body; // will be appended here

		// mainDiv.style.left = (window.innerWidth - width) / 2 + 'px';

		var menuButtonDiv = document.createElement("div");
		menuButtonDiv.setAttribute("id", "HelperMenuButton");
		menuButtonDiv.style.width = buttonWidth + 'px';
		menuButtonDiv.style.height = buttonHeight + 'px';
		menuButtonDiv.style.top = (window.innerHeight - buttonHeight * 1.5) + 'px';
		menuButtonDiv.style.left = window.innerWidth - buttonWidth * 1.5 + 'px';
		menuButtonDiv.style.zIndex = 999999; // TO THE MAX
		menuButtonDiv.style.position = "fixed";

		// design
		menuButtonDiv.style.textAlign = "center";
		menuButtonDiv.style.padding = "0px";
		menuButtonDiv.style.background = "#222d32"; // background color
		menuButtonDiv.style.color = "#ffffff"; // text color
		menuButtonDiv.style.borderColor = "#035a8f"; // border color
		menuButtonDiv.style.border = "solid";
		menuButtonDiv.style.opacity = "0.9"; // setting starting opacity

		// menu text
		var menuTextBox = CreateNodeWithText(menuButtonDiv, "Kérdések\nMenü");

		menuButtonDiv.addEventListener("click", function() {
			if (document.getElementById("HelperMenu") == null) {
				ShowMenuList();
			} else {
				document.getElementById("HelperMenu").parentNode.removeChild(document.getElementById(
					"HelperMenu"));
			}
		}); // adding click

		//addEventListener(window, 'scroll', function () {
		//	menuButtonDiv.style.top = (pageYOffset + window.innerHeight - buttonHeight * 2) + 'px';
		//})

		addEventListener(window, 'resize', function() {
			menuButtonDiv.style.left = window.innerWidth - buttonWidth * 2 + 'px';
		});

		appedtTo.appendChild(menuButtonDiv);
	} catch (e) {
		Exception(e, "script error at showing menu:");
	}
}

// shows a fancy menu list with the subjects
function ShowMenuList() {
	try {
		var appedtTo = document.body; // will be appended here

		var menuDiv = document.createElement("div");
		menuDiv.setAttribute("id", "HelperMenu");
		menuDiv.style.width = (window.innerWidth / 2) + 'px';
		menuDiv.style.top = (window.innerHeight / 10) + 'px';
		menuDiv.style.left = window.innerWidth / 2 - (window.innerWidth / 2) / 2 + 'px';
		menuDiv.style.zIndex = 999999;
		menuDiv.style.position = "fixed";

		//design
		menuDiv.style.textAlign = "center";
		menuDiv.style.padding = "0px";
		menuDiv.style.background = "#222d32"; // background color
		menuDiv.style.color = "#ffffff"; // text color
		menuDiv.style.borderColor = "#035a8f"; // border color
		menuDiv.style.border = "solid";
		menuDiv.style.opacity = "1"; // setting starting opacity

		var fiveMargin = "5px 5px 5px 5px";
		var tbl = document.createElement('table');
		tbl.style.margin = fiveMargin;
		tbl.style.textAlign = "left";
		tbl.style.width = "98%";

		// adding headers ---------------------------------------------------------------------------------------------------------------
		var tr = tbl.insertRow();
		var header1 = tr.insertCell();

		var headerSubjInfoParagraph = CreateNodeWithText(header1, "Tárgynév [darab kérdés]", "center");
		headerSubjInfoParagraph.style.margin = fiveMargin; // fancy margin

		var header2 = tr.insertCell();
		var headerSubjInfoParagraph = CreateNodeWithText(header2, "Aktív");
		headerSubjInfoParagraph.style.margin = fiveMargin; // fancy margin

		if (data && data.length > 0) {
			for (let i = 0; i < data.length; i++) {
				var subjRow = tbl.insertRow();
				subjRow.style.border = "1px solid #131319";

				var td = subjRow.insertCell();
				var text = data.Subjects[i].Name;
				if (data.Subjects[i].length != 0)
					text += " [ " + data.Subjects[i].length + "db ]";

				var textBox = CreateNodeWithText(td, text);

				textBox.style.margin = fiveMargin; // fancy margin

				td = subjRow.insertCell();
				var checkbox = document.createElement("input"); // new paragraph
				checkbox.type = "checkbox";
				checkbox.style.background = "white";
				checkbox.style.margin =
					"5px 5px 5px 5px"; // fancy margin
				td.appendChild(checkbox); // adding text box to main td

				var active = data.GetIfActive(i);
				checkbox.checked = active;

				checkbox.setAttribute("id", "HelperTextNode" + i);

				checkbox.addEventListener("click", function() {
					var checked = document.getElementById("HelperTextNode" + i).checked;
					data.ChangeActive(i, checked);
				}); // adding click
			}
		} else // if no data
		{
			var noDataRow = tbl.insertRow();
			var noDataRowCell = noDataRow.insertCell();

			var textBox = CreateNodeWithText(noDataRowCell, "A kérdések filet nem lehetett beolvasni.");
			textBox.style.margin = fiveMargin; // fancy margin

		}

		// show splash tickbox -----------------------------------------------------------------------------------------------------------------------------
		var splasTickboxRow = tbl.insertRow();
		var splashTickboxCell = splasTickboxRow.insertCell();

		var splashTickBox = document.createElement("input");
		splashTickBox.type = "checkbox";
		splashTickBox.checked = GM_getValue("showSplash") || false;
		splashTickBox.style.position = "";
		//splashTickBox.style.background = "white";
		splashTickBox.style.left = 10 + 'px';
		splashTickBox.style.margin = "5px 5px 5px 5px"; // fancy margin
		splashTickBox.style.top = menuDiv.offsetHeight + 'px';
		splashTickboxCell.appendChild(splashTickBox); // adding to main div

		splashTickBox.addEventListener("click", function() {
			GM_setValue("showSplash", splashTickBox.checked);
		}); // adding clicktextNode

		var splashTickBoxTextSpan = CreateNodeWithText(splashTickboxCell,
			"Üdvözlő üzenet mutatása minden oldalon", "span");

		// show questons tickbox -----------------------------------------------------------------------------------------------------------------------------
		var questionTickboxRow = tbl.insertRow();
		var questionTickboxCell = questionTickboxRow.insertCell();

		var questionsTickBox = document.createElement("input");
		questionsTickBox.type = "checkbox";
		questionsTickBox.checked = GM_getValue("showQuestions");
		questionsTickBox.style.position = "";
		//questionsTickBox.style.background = "white";
		questionsTickBox.style.left = 10 + 'px';
		questionsTickBox.style.margin = "5px 5px 5px 5px"; // fancy margin
		questionsTickBox.style.top = menuDiv.offsetHeight + 'px';
		questionTickboxCell.appendChild(questionsTickBox); // adding to main div

		questionsTickBox.addEventListener("click", function() {
			GM_setValue("showQuestions", questionsTickBox.checked);
			if (!questionsTickBox.checked) {
				ShowMessage({
					m: "Szinte mindég jó az talált válasz a kérdésre, de attól még könnyen előfordulhat, hogy rosz kérdésre írja ki a választ! Ez a opció nélkül ezt az ellenőrzési lehetőséget nem tudod kihasználni",
					isSimple: true
				}, 7);
			}
		}); // adding clicktextNode
		var questionsTickBoxTextSpan = CreateNodeWithText(questionTickboxCell,
			"Kérdések mutatása válaszhoz", "span");

		// database mode listbox -----------------------------------------------------------------------------------------------------------------------------
		var databasemodeListboxRow = tbl.insertRow();
		var databasemodeListboxCell = databasemodeListboxRow.insertCell();

		var databasemodeListbox = document.createElement("select");
		databasemodeListbox.type = "checkbox";
		//databasemodeListbox.checked = GM_getValue("showSplash") || false;
		databasemodeListbox.style.position = "";
		//databasemodeListbox.style.background = "white";
		databasemodeListbox.style.left = 10 + 'px';
		databasemodeListbox.style.margin = "5px 5px 5px 5px"; // fancy margin
		databasemodeListbox.style.top = menuDiv.offsetHeight + 'px';

		var databasemodeListboxText = CreateNodeWithText(questionTickboxCell,
			"Kérdések beszerzése:", "span");
		databasemodeListboxCell.appendChild(databasemodeListboxText);

		databasemodeListboxCell.appendChild(databasemodeListbox); // adding to main div

		databasemodeListbox.addEventListener("change", function(e) {
			// sorry for using selectedindex :c
			GM_setValue("useNetDB", databasemodeListbox.selectedIndex);
		});

		var uselocal = document.createElement('option');
		uselocal.text = "Helyi fájlból (old school)";
		uselocal.value = 2;
		databasemodeListbox.add(uselocal, 0);

		var usenetsafe = document.createElement('option');
		usenetsafe.text = "Netről";
		usenetsafe.value = 0;
		databasemodeListbox.add(usenetsafe, 1);

		var selected = GM_getValue("useNetDB");
		if (selected != undefined)
			databasemodeListbox.selectedIndex = selected;

		var databasemodeListboxElement = document.createElement("span"); // new paragraph
		databasemodeListboxCell.appendChild(databasemodeListboxElement);

		// setting up buttons
		var buttonRow = tbl.insertRow();
		var buttonCell = buttonRow.insertCell();
		buttonCell.style.textAlign = 'center';
		// x button ------------------------------------------------------------------------------------------------------------------------------
		var xButton = CreateNodeWithText(buttonCell, "Bezárás", "button");

		xButton.style.position = "";
		xButton.style.background = "white";
		xButton.style.left = 10 + 'px';
		xButton.style.margin = "5px 5px 5px 5px"; // fancy margin
		xButton.style.top = menuDiv.offsetHeight + 'px';

		xButton.addEventListener("click", function() {
			document.getElementById("HelperMenu").parentNode.removeChild(document.getElementById(
				"HelperMenu"));
		}); // adding clicktextNode
		// help button ----------------------------------------------------------------------------------------------------------------
		var helpButton = CreateNodeWithText(buttonCell, "Help", "button");

		helpButton.style.position = "";
		helpButton.style.background = "white";
		helpButton.style.left = 10 + 'px';
		helpButton.style.margin = "5px 5px 5px 5px"; // fancy margin
		helpButton.style.top = menuDiv.offsetHeight + 'px';

		helpButton.addEventListener("click", function() {
			ShowHelp();
		}); // adding clicktextNode


		// site link ----------------------------------------------------------------------------------------------------------------

		var siteLink = CreateNodeWithText(buttonCell, "Help", "button");
		siteLink.innerText = "Weboldal";

		siteLink.addEventListener("click", function() {
			location.href = serverAdress + "menuClick";
		});

		//addEventListener(window, 'scroll', function () {
		//	menuDiv.style.top = (pageYOffset + window.innerHeight / 3) + 'px';
		//})
		addEventListener(window, 'resize', function() {
			menuDiv.style.left = window.innerWidth / 2 + 'px';
		});

		menuDiv.appendChild(tbl); // adding table box to main div
		appedtTo.appendChild(menuDiv);
	} catch (e) {
		Exception(e, "script error at showing menu list:");
	}
}

//: }}}

//: Generic utils {{{

function RemoveMultipleItems(array) {
	var newArray = [];
	for (var i = 0; i < array.length; i++) {
		var j = 0;
		while (j < newArray.length && newArray[j] !== array[i]) {
			j++;
		}
		if (j >= newArray.length) {
			newArray.push(array[i]);
		}
	}
	return newArray;
}

// removes some crap from "q"
function SimplifyQuery(q) {
	var result = q.replace(/\n/g, "").replace(/\s/g, ' '); // WHY TF ARE THERE TWO KINDA SPACES??? (charcode 160 n 32)
	return RemoveUnnecesarySpaces(result);
}

function ShortenString(toShorten, ammount) {
	var result = "";
	var i = 0;
	while (i < toShorten.length && i < ammount) {
		result += toShorten[i];
		i++;
	}
	return result;
}

function CreateNodeWithText(to, text, type) {
	var paragraphElement = document.createElement(type || "p"); // new paragraph
	var textNode = document.createTextNode(text);
	paragraphElement.appendChild(textNode);
	to.appendChild(paragraphElement);
	return paragraphElement;
}

// removes whitespace from begining and and, and replaces multiple spaces with one space
function RemoveUnnecesarySpaces(toremove) {
	toremove = NormalizeSpaces(toremove);
	while (toremove.includes("  ")) // while the text includes double spaces replaces all of them with a single one
	{
		toremove = toremove.replace(/  /g, " ");
	}
	return toremove.trim();
}

// simplifies a string for easier comparison
function SimplifyStringForComparison(value) {
	value = RemoveUnnecesarySpaces(value).toLowerCase();
	var removableChars = [",", ".", ":", "!"];
	for (var i = 0; i < removableChars.length; i++) {
		var regex = new RegExp(removableChars[i], "g");
		value.replace(regex, "");
	}
	return value;
}

// if the value is empty, or whitespace
function EmptyOrWhiteSpace(value) {
	// replaces /n-s with "". then replaces spaces with "". if it equals "", then its empty, or only consists of white space
	if (value === undefined)
		return true;
	return (value.replace(/\n/g, "").replace(/ /g, "").replace(/\s/g, ' ') === "");
}

// damn nonbreaking space
function NormalizeSpaces(input) {
	return input.replace(/\s/g, ' ');
}

function SendXHRMessage(message) {
	var url = serverAdress + "isAdding";
	GM_xmlhttpRequest({
		method: "POST",
		url: url,
		data: message,
		headers: {
			"Content-Type": "application/x-www-form-urlencoded"
		},
		onerror: function(response) {
			Log("XMLHTTP request POST error");
		}
	});
}

//: }}}

//: Help {{{

// shows some neat help
function ShowHelp() {
	GM_openInTab(serverAdress + 'manual', {
		active: true
	});
}

//: }}}

// I am not too proud to cry that He and he
// Will never never go out of my mind.
// All his bones crying, and poor in all but pain,

// Being innocent, he dreaded that he died
// Hating his God, but what he was was plain:
// An old kind man brave in his burning pride.

// The sticks of the house were his; his books he owned.
// Even as a baby he had never cried;
// Nor did he now, save to his secret wound.

// Out of his eyes I saw the last light glide.
// Here among the liught of the lording sky
// An old man is with me where I go

// Walking in the meadows of his son's eye
// Too proud to cry, too frail to check the tears,
// And caught between two nights, blindness and death.

// O deepest wound of all that he should die
// On that darkest day.