/**
 * Helper for JSHint-tests.
 * Export itself as function in setup.testhelper to
 * prevent expresso to use it as test...
 *
 * Use it with method chaining, so you get something like
 *
 *		var TestRun = require("./testhelper").setup.testRun;
 *
 *		TestRun(test, name)
 *			.addError(line, errorMessage)
 *			.test(source, options);
 *
 * TestRun(test, name)
 *	   test:		   nodeunit test object
 *	   name:		   optional. name of the test run
 *					   with a name, it's easier to identify a test run
 *
 * .addError(line, errorMessage)
 *	   line:		   line of the error message
 *	   errorMessage:   the message of the reported error
 *
 * .test(source, options)
 *					   starts the test run
 *	   source:		   source of the test file
 *	   options:		   optional. the options for jshint
 */

/*jshint node: true, eqnull: true*/

var JSHINT = require('../../../src/stable/jshint.js').JSHINT;

if (exports.setup === undefined || exports.setup === null) {
	exports.setup = {};
}

exports.setup.testRun = function (test, name) {
	var initCounter = 0, runCounter = 0, seq = 0, checked = [], definedErrors = [];

	var helperObj = {
		addError: function (line, message, extras) {
			definedErrors.push({
				line: line,
				message: message,
				extras: extras
			});

			return helperObj;
		},

		test: function (source, options, globals) {
			var ret = !!JSHINT(source, options, globals);
			var errors = JSHINT.errors.filter(function (er) {
				return er && er.id === "(error)";
			});

			if (errors.length === 0 && definedErrors.length === 0) {
				return;
			}

			// filter all thrown errors
			var undefinedErrors = errors.filter(function (er) {
				return !definedErrors.some(function (def) {
					var result = def.line === er.line &&
						def.message === er.reason;

					if (!result) {
						return result;
					}

					if (def.extras) {
						for (var extra in def.extras) {
							if (def.extras.hasOwnProperty(extra) &&
									er.hasOwnProperty(extra)) {
								result = (def.extras[extra] === er[extra]);
								if (!result) {
									return result;
								}
							}
						}
					}
					return result;
				});
			});

			// filter all defined errors
			var unthrownErrors = definedErrors.filter(function (def) {
				return !errors.some(function (er) {
					return def.line === er.line &&
						def.message === er.reason;
				});
			});

			// elements that only differs in line number
			var wrongLineNumbers = undefinedErrors.map(function (er) {
				var lines = unthrownErrors.filter(function (def) {
					return def.line !== er.line &&
						def.message === er.reason;
				}).map(function (def) {
					return def.line;
				});

				if (lines.length) {
					return {
						line: er.line,
						message: er.reason,
						definedIn: lines
					};
				}
				return null;
			}).filter(function (er) {
				return !!er;
			});

			// remove undefined errors, if there is a definition with wrong line number
			undefinedErrors = undefinedErrors.filter(function (er) {
				return !wrongLineNumbers.some(function (def) {
					return def.message === er.reason;
				});
			});
			unthrownErrors = unthrownErrors.filter(function (er) {
				return !wrongLineNumbers.some(function (def) {
					return def.message === er.message;
				});
			});

			test.ok(
				undefinedErrors.length === 0 && unthrownErrors.length === 0 && wrongLineNumbers.length === 0,

				(name == null ? "" : "\n  TestRun: [bold]{" + name + "}") +
				unthrownErrors.map(function (el, idx) {
					return (idx === 0 ? "\n	[yellow]{Errors defined, but not thrown by JSHINT}\n" : "") +
						" [bold]{Line " + el.line + ", Char " + el.character + "} " + el.message;
				}).join("\n") +
				undefinedErrors.map(function (el, idx) {
					return (idx === 0 ? "\n	[yellow]{Errors thrown by JSHINT, but not defined in test run}\n" : "") +
						"	[bold]{Line " + el.line + ", Char " + el.character + "} " + el.reason;
				}).join("\n") +
				wrongLineNumbers.map(function (el, idx) {
					return (idx === 0 ? "\n	[yellow]{Errors with wrong line number}\n" : "") +
						"	[bold]{Line " + el.line + "} " + el.message + " [red]{not in line(s)} [bold]{" + el.definedIn.join(", ") + "}";
				}).join("\n") + "\n"
			);
		}
	};

	return helperObj;
};
