// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2021-2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END

const HTTPS = require("https");
const PATH = require("path");
const FS = require("fs/promises");
const CHILD_PROCESS = require("child_process");

function query_git() {
	// git shortlog -sn --all | sed -E "s/^\W+[0-9]+\W+//"
	return (async () => {
		let users = new Map();

		let git = new Promise((resolve, reject) => {
			try {
				const child = CHILD_PROCESS.spawn('git', ['shortlog', '-sn', '--all']);

				let sout = "";
				child.stdout.setEncoding('utf8');
				child.stdout.on('data', (data) => {
					sout += data;
				});

				let serr = "";
				child.stderr.setEncoding('utf8');
				child.stderr.on('data', (data) => {
					serr += data;
				});

				child.on('close', (code) => {
					if (code != 0) {
						reject([code, serr]);
					} else {
						resolve(sout);
					}
				});
			} catch (e) {
				reject(e);
			}
		});

		let result = await git;
		let lines = result.matchAll(/^[\s]+([0-9]+)[\s]+(.+)$/gim);
		for (let line of lines) {
			let name = line[2];
			if (!users.has(name)) {
				users.set(name, `https://github.com/${process.env.GITHUB_REPOSITORY}/graphs/contributors`)
			}
		}

		return users;
	})();
}

function query_crowdin(project_id, project_auth_key) {
	const limit = 100;
	function _query_page(page) {
		return new Promise((resolve, reject) => {
			try {
				let query = {
					protocol: "https:",
					host: "crowdin.com",
					path: `/api/v2/projects/${project_id}/members?limit=${limit}&offset=${page * limit}`,
					method: "GET",
					headers: {
						"accept": "application/json",
						"authorization": `Bearer ${project_auth_key}`
					}
				};
				let req = HTTPS.request(query, (res) => {
					let data = "";
					res.setEncoding('utf8');
					res.on('data', (chunk) => {
						data += chunk;
					});
					res.on('end', () => {
						res.data = data;
						resolve(res);
					});
				});
				req.end();
			} catch (e) {
				reject(e);
			}
		});
	}

	return (async () => {
		let page_is_last = false;
		let page = 0;

		let users = new Map();

		while (!page_is_last) {
			let res = await _query_page(page++);
			if (res.statusCode == 200) {
				let json = JSON.parse(res.data);
				let count = json.data.length;
				for (let user of json.data) {
					// Don't credit blocked users for work not done.
					if (user.data.role == 'blocked') {
						continue;
					}

					let key = (user.data.fullName !== null) && (user.data.fullName !== undefined) && (user.data.fullName.length > 0) ? user.data.fullName : user.data.username;

					users.set(key, `https://crowdin.com/profile/${user.data.username}`);
				}

				page_is_last = (count < limit);
			} else {
				throw new Error(JSON.parse(res.data));
			}
		}

		return users;
	})();
}

function query_github_sponsors(auth_token) {
	const HTTPS = require("https");

	let build_query = function (cursor) {
		if (cursor === undefined) {
			return `query {
	viewer {
		sponsors(after: null, first: 0) {
			totalCount
		}
	}
}`;
		} else {
			return `query {
	viewer {
		sponsors(after: ${typeof (cursor) == "string" ? `"${cursor}"` : "null"}, first: 100) {
			nodes {
				__typename
				... on User {
					resourcePath
					login
					name
				}
				... on Organization {
					resourcePath
					login
					name
				}
			}
			pageInfo {
			  endCursor
			  startCursor
			}
		}
	}
}`;
		}
	}

	let do_query = function (schema) {
		return new Promise((resolve, reject) => {
			let text_query = JSON.stringify({ query: schema });

			let request = HTTPS.request(
				"https://api.github.com/graphql",
				{
					method: 'POST',
					headers: {
						'Content-Type': 'application/json',
						'Accept': 'application/json',
						'Authorization': `bearer ${auth_token}`,
						'User-Agent': 'Node.JS'
					},
				}, (res) => {
					let data = "";
					res.setEncoding('utf8');
					res.on('data', (chunk) => {
						data += chunk;
					});
					res.on('error', () => {
						reject();
					});
					res.on('end', () => {
						res.data = JSON.parse(data);
						resolve(res);
					});
				});
			request.write(text_query);
			request.end();
		})
	}

	return (async () => {
		let users = new Map();
		let total = 0;

		// Find the total amount.
		{
			let query = build_query();
			let response = await do_query(query);
			let data = response.data.data;
			total = data.viewer.sponsors.totalCount;
		}

		// Index by page, in 100 user increments.
		let cursor = null;
		for (let idx = 0, edx = total; idx < edx; idx += 100) {
			let query = build_query(cursor);
			let response = await do_query(query);
			let data = response.data.data;

			// Insert data info Map.
			for (let entry of data.viewer.sponsors.nodes) {
				let name = entry.login;
				if (typeof (entry.name) === "string") {
					name = entry.name;
				}

				users.set(name, `https://github.com${entry.resourcePath}`);
			}

			// Update cursor for further queries.
			cursor = data.viewer.sponsors.pageInfo.endCursor;
		}

		return users;
	})();
}

(async () => {
	let json = {};
	let markdown = `# StreamFX Contributors & Supporters

## Contributors

`;

	// Contributors
	{// - Git
		markdown += `### Code, Media
Thanks go to the following people, who have either wrangled with code or wrangled with image editors while saving often in the hopes of not losing any changes:

`;
		json.contributor = {};
		let info = await query_git();
		let extra = JSON.parse(await FS.readFile(PATH.join(__dirname, "patch-contributors-git.json")));
		for (let key in extra) {
			info.set(key, extra[key]);
		}
		for (let key of Array.from(info.keys()).sort((a, b) => a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'}))) {
			let value = info.get(key);
			json.contributor[key] = value;
			markdown += `* [${key}](${value})\n`;
		}
		markdown += "\n";
	}

	{// - Crowdin
		markdown += `### Translators
[StreamFX relies on crowd-sourced translations to be available in your language.](https://github.com/Xaymar/obs-StreamFX/issues/36) Much thanks go out to all volunteer translators who have taken some time to submit translations on Crowdin.

`;
		json.translator = {};
		let info = await query_crowdin(process.env.CROWDIN_PROJECTID, process.env.CROWDIN_TOKEN);
		let extra = JSON.parse(await FS.readFile(PATH.join(__dirname, "patch-contributors-git.json")));
		for (let key in extra) {
			info.set(key, extra[key]);
		}
		for (let key of Array.from(info.keys()).sort((a, b) => a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'}))) {
			let value = info.get(key);
			json.translator[key] = value;
			markdown += `* [${key}](${value})\n`;
		}
		markdown += "\n";
	}

	// Supporters
	markdown += `## Supporters
The StreamFX project relies on generous donations from you through [Patreon](https://patreon.com/xaymar), [GitHub](https://github.com/sponsors/xaymar) or [PayPal](https://paypal.me/xaymar). Huge thanks go out to the following people for supporting StreamFX:

`;
	json.supporter = {}
	{// - GitHub
		markdown += `### GitHub Sponsors
`;
		json.supporter.github = {};
		let info = await query_github_sponsors(process.env.GITHUB_TOKEN);
		let extra = JSON.parse(await FS.readFile(PATH.join(__dirname, "patch-supporters-github.json")));
		for (let key in extra) {
			info.set(key, extra[key]);
		}
		for (let key of Array.from(info.keys()).sort((a, b) => a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'}))) {
			let value = info.get(key);
			json.supporter.github[key] = value;
			markdown += `* [${key}](${value})\n`;
		}
		markdown += "\n";
	}

	{// - Patreon
		// TODO: How do we OAuth without OAuth?!
		markdown += `### Patreon Patrons
`;
		json.supporter.patreon = {};
		let info = new Map();
		let extra = JSON.parse(await FS.readFile(PATH.join(__dirname, "patch-supporters-patreon.json")));
		for (let key in extra) {
			info.set(key, extra[key]);
		}
		for (let key of Array.from(info.keys()).sort((a, b) => a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'}))) {
			let value = info.get(key);
			json.supporter.patreon[key] = value;
			markdown += `* [${key}](${value})\n`;
		}
		markdown += "\n";
	}

	// Write Markdown file
	FS.writeFile(process.argv[2], 
		markdown, 
		{
			encoding: 'utf8'
		}
	)

	// Write JSON file
	FS.writeFile(process.argv[3], 
		JSON.stringify(json, undefined, '\t'),
		{
			encoding: 'utf8'
		}
	)
})();
