/**
 * Try to color the cells of comparison tables based on their contents.
 *
 * @title Compare cells
 */
(function comparecells() {
	/* Create a new IFRAME to get a "clean" Window object, so we can use its
	 * console. Sometimes sites (e.g. Twitter) override console.log and even
	 * the entire console object. "delete console.log" or "delete console"
	 * does not always work, and messing with the prototype seemed more
	 * brittle than this. */
	let console = (function () {
		let iframe = document.getElementById('xxxJanConsole');
		if (!iframe) {
			iframe = document.createElementNS('http://www.w3.org/1999/xhtml', 'iframe');
			iframe.id = 'xxxJanConsole';
			iframe.style.display = 'none';

			(document.body || document.documentElement).appendChild(iframe);
		}

		return iframe && iframe.contentWindow && iframe.contentWindow.console || {
			log: function () {}
		};
	})();

	/**
	 * Get the text content for the given element.
	 */
	function getTextFromElement(element) {
		/* TODO: take IMG@alt, BUTTON@value etc. into account */
		return element.textContent.trim().toLowerCase();
	}

	/**
	 * Get a Uint8Array of the SHA-256 bytes for the given string.
	 */
	async function getSha256Bytes(string) {
		try {
			if (
				typeof crypto === 'object' && typeof crypto.subtle === 'object' && typeof crypto.subtle.digest === 'function'
				&& typeof Uint8Array === 'function'
				&& typeof TextEncoder === 'function'
			) {
				return new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder('utf-8').encode(string)));
			}
		} catch (e) {
			return null;
		}
	}

	async function getColorsForValue(value) {
		/* Cache the calculated values. */
		getColorsForValue.cellValuesToRgb = getColorsForValue.cellValuesToRgb || {};

		if (!getColorsForValue.cellValuesToRgb[value]) {
			let normalizedValue = value.trim().toLowerCase();
			let hash;

			let yesValues = [
				'✔',
				'yes',
				'ja',
				'oui',
				'si',
				'sí'
			];

			let noValues = [
				'x',
				'no',
				'nee',
				'neen',
				'nein',
				'non',
				'no'
			];

			if (yesValues.indexOf(normalizedValue) > -1) {
				/* Make "Yes" cells green. */
				getColorsForValue.cellValuesToRgb[value] = [ 150, 255, 32 ];
			} else if (noValues.indexOf(normalizedValue) > -1) {
				/* Make "No" cells green. */
				getColorsForValue.cellValuesToRgb[value] = [ 238, 32, 32 ];
			} else if ((shaBytes = await getSha256Bytes(normalizedValue))) {
				/* Give other cells a color based on their content’s SHA
				 * hash to ensure “consistent random colors” every time. */
				getColorsForValue.cellValuesToRgb[value] = [
					shaBytes[0],
					shaBytes[1],
					shaBytes[2]
				];
			} else {
				/* If the SHA hash could not be calculated, just use random
				 * values. These will change on every execution. */
				getColorsForValue.cellValuesToRgb[value] = [
					Math.random() * 255,
					Math.random() * 255,
					Math.random() * 255
				];
			}
		}

		/* Calculate/approximate the lightness (tweaked from “RGB to HSL”) to
		 * determine whether black or white text is best suited. */
		let isLight = 150 < (
			getColorsForValue.cellValuesToRgb[value][0] * 0.299
			+ getColorsForValue.cellValuesToRgb[value][1] * 0.587
			+ getColorsForValue.cellValuesToRgb[value][2] * 0.114
		);

		return {
			backgroundColor: 'rgb(' + getColorsForValue.cellValuesToRgb[value].join(', ') + ')',
			color: isLight
				? 'black'
				: 'white',
			textShadow: isLight
				? '1px 1px 3px white'
				: '1px 1px 3px black'
		};
	}

	/* The main function. */
	(function execute(document) {
		Array.from(document.querySelectorAll('table')).forEach(table => {
			Array.from(table.tBodies).forEach(tBody => {
				if (tBody.rows.length < 3) {
					console.log('Compare cells: skipping table body ', tBody, ' because it only has ', tBody.rows.length, ' rows');
					return;
				}

				Array.from(tBody.rows).forEach(tr => {
					/* Determine the values. */
					let cellValues = [];
					let uniqueCellValues = new Set();

					Array.from(tr.cells).forEach((cell, i) => {
						/* Don't take the header cells into account. */
						if (cell.tagName.toUpperCase() === 'TH') {
							return;
						}

						/* Assume the first cell is a header cell, even if it is not a TH. */
						if (i === 0) {
							return;
						}

						cellValues[i] = getTextFromElement(cell);
						uniqueCellValues.add(cellValues[i]);
					});

					/* Color (or not) the cells based on the values. */
					let isFirstValue = true;
					let firstValue;
					cellValues.forEach(async function(cellValue, i) {
						let hasTwoUniqueValues = uniqueCellValues.size == 2;
						if (isFirstValue) {
							firstValue = cellValue;
							isFirstValue = false;
						}

						let backgroundColor;
						let color;
						let textShadow;

						if (
							uniqueCellValues.size == 1 ||
							(hasTwoUniqueValues && cellValue === firstValue) ||
							cellValue.trim() === ''
						) {
							backgroundColor = 'inherit';
							color = 'inherit';
							textShadow = 'inherit';
						} else {
							backgroundColor = (await getColorsForValue(cellValue)).backgroundColor;
							color = (await getColorsForValue(cellValue)).color;
							textShadow = (await getColorsForValue(cellValue)).textShadow;
						}

						tr.cells[i].style.setProperty('background-color', backgroundColor, 'important');
						tr.cells[i].style.setProperty('color', color, 'important');
						tr.cells[i].style.setProperty('text-shadow', textShadow, 'important');
					});
				});
			});
		});

		/* Recurse for frames and iframes. */
		try {
			Array.from(document.querySelectorAll('frame, iframe, object[type^="text/html"], object[type^="application/xhtml+xml"]')).forEach(function (elem) {
				execute(elem.contentDocument);
			});
		} catch (e) {
			/* Catch exceptions for out-of-domain access, but do not do anything with them. */
		}
	})(document);
})();
