Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
"use strict";
// libavtools /REQUIRES/ a modern browser. IE won't cut it.
// It may be possible to remove references to caching code in the future, but the cache provides a massive load-time save.

// Replace all for .. in .. with
// for (let [key, value] of Object.entries(...))

if (window.avtools === undefined) { 

let avt = {};
window.avtools = avt;


avt.Edit = class {
	constructor(editor, target_page, net_change, revision_id, timestamp, prev_revision_id, diff) {
		this.editor = editor; /* String */
		this.target_page = target_page; /* String */
		this.net_change = net_change; /* Integer */
		this.revision_id = revision_id; /* Integer */
		this.timestamp = timestamp; 
		this.prev_revision_id = prev_revision_id; /* Integer */
		this.diff = diff /* ?????? TODO: figure out diff format to use. */
	}
};

avt.programmatic_filters = {};

avt.programmatic_filters.is_mostly_caps = function (_, target) {
	const len = target.match(/[A-Z]/gu);
	return { "is_match": ((len ? len.length : 0)  > (target.length/4)), "progfilt_results": {} }; // if over half of target is caps, it's a match.
}

avt.MatchFilter = class {
	constructor(name, config) {
		if (config.regex && config.programmatic_check) {
			throw new Error("Cannot match " + name + "using both regex and programmatic check!");
		}
		
		if (config.regex) {
			this.regex = new RegExp(config.regex, config.flags ? config.flags : "gui");
		} else if (config.programmatic_check) {
			this.check_func = avt.programmatic_filters[config.programmatic_check];
			
			if (!this.check_func) {
				throw new Error(name + "specifies an invaild programmatic check!");
			}
		} else {
			/* ignore, likely a placeholder. */
		}
		
		this.show_matches = config.show_matches;
		this.severity = config.severity;
	}
	
	match(target) {
		let match = {};
		if (this.regex) {
			let nreg = new RegExp(this.regex); // duplicate..
			let m;
			match.regex_results = [];
			
			while ((m = nreg.exec(target)) !== null) {
				match.regex_results.push({res: m, index: m.index, lastIndex: nreg.lastIndex});
			}
			
			
			if (match.regex_results[0] !== undefined) {
				match.is_match = true;
			} else {
				match.is_match = false;
			}
			
			if (this.show_matches === false) {
				match.regex_results = undefined;
			}
			
		} else {
			match = this.check_func(this.flags, target);
		}
		return match;
	}
}

avt.avtools_central_config = "User:Moonythedwarf/libavtools/global_config.json";
avt.avtools_user_config = "Special:MyPage/libavtools/config.json";

avt.load_json_file = async function (wikilink) {
	let req = new Request(`${mw.util.wikiScript("index")}?title=${wikilink}&action=raw&ctype=text/json`);
	let res = await avt.__cache.match(req);
	// JS error handling much?
	if (res === undefined ) {
		let resp = await fetch(req);
		await avt.__cache.put(req, resp);
		return (await avt.__cache.match(req)).text();
	}
	return res.text()
}

avt.load_multiple_json_files = async function (links) {
	let loaded_files = [];
	for (const i in links) {
		loaded_files.push(avt.load_json_file(links[i]));
	}
	return (Promise.all(loaded_files)).then(a => a.filter(val => val !== "" ? true : false));
}

avt.load_avtools_config = async function () {
	if (avt.config !== undefined) {
		return;
	}
	const global_config = JSON.parse(await avt.load_json_file(avt.avtools_central_config));
	const lc_data = await avt.load_json_file(avt.avtools_user_config);
	const local_config = JSON.parse(lc_data? lc_data : "{}");
	let final_config = {};
	
	//FIX: Crude. Will need proper precedence rules later.
	for (const i in global_config) {
		if (local_config[i] !== undefined) {
			final_config[i] = local_config[i];
		} else {
			final_config[i] = global_config[i];
		}
	}
	avt.config = final_config;
}

avt.load_filters = function (list) {
	let filters = {};
	for (let fname in list) {
		for (let i in list[fname]) {
			filters[i] = new avt.MatchFilter(fname, list[fname][i]);
		}
	}
	return filters;
}

avt.match_against_filters = function (filters, target) {
	let matches = {};
	let severity = 0;
	for (const filtname in filters) {
		const filtmatches = filters[filtname].match(target);
		if (filtmatches.is_match) {
			matches[filtname] = filtmatches;
			
			severity += filters[filtname].severity;
		}
	}
	return { matches, severity };
}

avt.load_username_filters = async function () {
	let loaded_files = (await avt.load_multiple_json_files(avt.config.username_filters)).map(i => JSON.parse(i));
	avt.username_filters = avt.load_filters(loaded_files);
}

avt.load_content_filters = async function () {
	let loaded_files = (await avt.load_multiple_json_files(avt.config.content_filters)).map(i => JSON.parse(i));
	avt.content_filters = avt.load_filters(loaded_files);
}

avt.load_summary_filters = async function () {
	let loaded_files = (await avt.load_multiple_json_files(avt.config.summary_filters)).map(i => JSON.parse(i));
	avt.summary_filters = avt.load_filters(loaded_files);
}

avt.load_all_filters = async function (config) {
	return Promise.all([
		avt.load_summary_filters(),
		avt.load_content_filters(),
		avt.load_username_filters(),
	]);
}

avt.wipe_cache = async function () {
	for (let v of await avt.__cache.keys()) {
		await avt.__cache.delete(v); //snip!
	}
}

avt.load_libavtools_core = async function () {
	avt.__cache = await caches.open("avtcache");
	await avt.load_avtools_config();
};

avt.load_libavtools_filters = async function () {
	if (avt.config === undefined) {
		throw new Error("Attempted to load libavtools filters before core!");
	}
	
	await avt.load_all_filters();
}

$(async () => {
	try {
		await avt.load_libavtools_core();
	} catch (e) {
		mw.notify(e)
	}
})

}