"use strict";
const fs = require("graceful-fs");
const path = require("path");

const check = (expected, actual) => {
	if (expected instanceof RegExp) {
		expected = { message: expected };
	}
	if (Array.isArray(expected)) {
		return expected.every(e => check(e, actual));
	}
	return Object.keys(expected).every(key => {
		let value = actual[key];
		if (typeof value === "object") {
			value = JSON.stringify(value);
		}
		return expected[key].test(value);
	});
};

const explain = object => {
	if (object instanceof RegExp) {
		object = { message: object };
	}
	return Object.keys(object)
		.map(key => {
			let value = object[key];
			if (typeof value === "object" && !(value instanceof RegExp)) {
				value = JSON.stringify(value);
			}
			let msg = `${key} = ${value}`;
			if (key !== "stack" && key !== "details" && msg.length > 100)
				msg = msg.slice(0, 97) + "...";
			return msg;
		})
		.join("; ");
};

const diffItems = (actual, expected, kind) => {
	const tooMuch = actual.slice();
	const missing = expected.slice();
	for (let i = 0; i < missing.length; i++) {
		const current = missing[i];
		for (let j = 0; j < tooMuch.length; j++) {
			if (check(current, tooMuch[j])) {
				tooMuch.splice(j, 1);
				missing.splice(i, 1);
				i--;
				break;
			}
		}
	}
	const diff = [];
	if (missing.length > 0) {
		diff.push(`The following expected ${kind}s are missing:
${missing.map(item => `${explain(item)}`).join("\n\n")}`);
	}
	if (tooMuch.length > 0) {
		diff.push(`The following ${kind}s are unexpected:
${tooMuch.map(item => `${explain(item)}`).join("\n\n")}`);
	}
	return diff.join("\n\n");
};

module.exports = function checkArrayExpectation(
	testDirectory,
	object,
	kind,
	filename,
	upperCaseKind,
	done
) {
	if (!done) {
		done = upperCaseKind;
		upperCaseKind = filename;
		filename = `${kind}s`;
	}
	let array = object[`${kind}s`];
	if (Array.isArray(array)) {
		if (kind === "warning") {
			array = array.filter(item => !/from Terser/.test(item));
		}
	}
	if (fs.existsSync(path.join(testDirectory, `${filename}.js`))) {
		// CHANGE: added file for sorting messages in multi-thread environment
		if (fs.existsSync(path.join(testDirectory, `${filename}-sort.js`))) {
			const sorter = require(path.join(testDirectory, `${filename}-sort.js`))
			array = sorter(array)
		}
		const expectedFilename = path.join(testDirectory, `${filename}.js`);
		const expected = require(expectedFilename);
		const diff = diffItems(array, expected, kind);
		if (expected.length < array.length) {
			return (
				done(
					new Error(
						`More ${kind}s (${array.length} instead of ${expected.length}) while compiling than expected:\n\n${diff}\n\nCheck expected ${kind}s: ${expectedFilename}`
					)
				),
				true
			);
		} else if (expected.length > array.length) {
			return (
				done(
					new Error(
						`Less ${kind}s (${array.length} instead of ${expected.length}) while compiling than expected:\n\n${diff}\n\nCheck expected ${kind}s: ${expectedFilename}`
					)
				),
				true
			);
		}
		for (let i = 0; i < array.length; i++) {
			if (Array.isArray(expected[i])) {
				for (let j = 0; j < expected[i].length; j++) {
					if (!check(expected[i][j], array[i])) {
						return (
							done(
								new Error(
									`${upperCaseKind} ${i}: ${explain(
										array[i]
									)} doesn't match ${explain(expected[i][j])}`
								)
							),
							true
						);
					}
				}
			} else if (!check(expected[i], array[i]))
				return (
					done(
						new Error(
							`${upperCaseKind} ${i}: ${explain(
								array[i]
							)} doesn't match ${explain(expected[i])}`
						)
					),
					true
				);
		}
	} else if (array.length > 0) {
		return (
			done(
				new Error(
					`${upperCaseKind}s while compiling:\n\n${array
						.map(explain)
						.join("\n\n")}`
				)
			),
			true
		);
	}
};
