/**
sleuth.js
@author Ross Bruniges
@version 0.9

A light-weight JavaScript based HTML/CSS/JS confirmation tool (NOT a validator)
Write tests in JSON (example found at - ????)
Call in page via sleuth.test_page.init(json_objs)
*/
var sleuth = window.sleuth || {};

sleuth.test_page={
	// variable set for the main container - used for appending to later
	$logger:"",
	// number of tests run (in total if using multiple JSON)
	num_passed:0,
	// total number of failures
	num_failed:0,
	// CSS class applied to passing and failing tests
	test_fail_class:"fail",
	test_pass_class:"win",
	// message for when tests pass
	test_pass_message:"Tests run and passed :)",
	// location of CSS for console - CSS is loaded in via JS
	console_css_location:"js/tests/test_suite.css",
	// version of jQuery to use if it's not already in the project
	/**
	Setting up the test suite:
		- Loads jQuery if required
		- appends the console element to the body
		- loads in CSS for console
		- applies click event to show/hide console
		
	@param json_url	the json objects used in this particular page
					(can be either a single string or an array of strings)
	*/
	init:function(json_url) {
		$('body').append('<div id="test_console"><div class="controls"><p class="title">sleuth.js</p><a href="#">Hide results</a></div><div class="bd"></div></div>');
		sleuth.test_page.$logger = $('#test_console .bd');
		// load in CSS for the logger
		var style = document.createElement('link');
		style.type = "text/css";
		style.rel = "stylesheet";
		style.href = sleuth.test_page.console_css_location;
		document.getElementsByTagName('head')[0].appendChild(style);
		// assign functionality for the show/hide link
		$('#test_console a').click(function() {
			sleuth.test_page.toggle_results($(this));
			return false;
		});	
		// getting the JSON
		sleuth.test_page.get_JSON(json_url);
	},
	/**
	Helper function to show/hide the test results on click

	@param elm	the HTML element that the click event is to be attached to
	*/
	toggle_results:function(elm) {
		if (sleuth.test_page.$logger.css('display') === "none") {
			sleuth.test_page.$logger.show("slow");
			elm.text("Hide results");
			elm.removeClass('show');
			elm.parent().fadeTo("slow",1);
		} else {
			sleuth.test_page.$logger.hide("slow");
			elm.text("Show results");
			elm.addClass('show');
			elm.parent().fadeTo("slow", 0.3);
		}
	},
	/**
	Loads the JSON objects and on success calls parse_JSON to start the tests
	
	@param json	Either a string containing the path to a single JSON object
				or an array containing multiple paths (as strings)
	*/
	get_JSON:function(json) {
		$.getJSON(json, function(json) {
			sleuth.test_page.parse_JSON(json);
		});
	},
	/**
	Parses through the tests contained in the JSON and calls init_tests for each,
	appends a title to the console, if detects the last iteration
	calls create_summary
	
	@param data	loaded JSON data sent from get_JSON
	*/
	parse_JSON:function(data) {
		sleuth.test_page.$logger.append("<p class='title'>Results for " + data.title + "</p>");
		$.each(data.tests, function(key, value) {
			sleuth.test_page.init_tests(key, value);
		});
		sleuth.test_page.create_summary();
	},
	/**
	Function containing the initial check run on each test:
		- type check to see if we need run this test at all (used when rechecking)
		- grabs selector defined in test.selector and checks for existance
		- appends div to console for display of message
		
	@param i	current index of iteration through test (used for appending)
	@param obj	the current test we're running though (JSON segment)
	@return true is test pass (and no further tests required)
	@return false is test fails at the first hurdle
	*/
	init_tests:function(i,obj) {
		// grabbing the selector we're looking for
		var $master_elm = $(obj.selector);
		// assigning this to a var as we'll use it in all places where we traverse through child elements
		sleuth.test_page.$logger.append("<div id='test_" + i + "' class='message'><p><em>" + obj.name + "</em></p></div>");
		// assing this to a varible here - we'll need it for both cases
		var $container = $('#test_' + i);
		// OK - so if we don't find the initial selector we have big fail, so lets check for this first
		if (!$master_elm.length) {
			var err_sum = obj.message.replace(/VAR/gi, obj.selector);
			sleuth.test_page.haz_failed(err_sum, $container);
			return;
		}
		/*
			now the first hurdle has been cleared we perform the additional checks if required, 
			if not then woohoo we've passed!
		*/
		if (obj.check_for) {
			$.each(obj.check_for,function(key, value){
				sleuth.test_page.assign_checks($master_elm, $container, key, value);
			});
		} else {
			sleuth.test_page.tests_passed($container);
			return;
		}
	},
	/**
	Method which define the test to be run on each JSON segment
	If sent an invalid test it fails with a generic error message
	
	@param target_selector	HTML object to apply test to
	@param error_elm		the div to append message to
	@param check_name		name of the test to apply (used in switch statment)
	@param obj				JSON segment containing values to be used in test
	*/
	assign_checks:function(target_selector, error_elm, check_name, obj) {
		// ok - so we know the checks we have and will use switch to pass over them and assign checks as appropriate
		switch(check_name) {
			case "contains":
				sleuth.test_page.confirm_html(target_selector, error_elm, obj);
				break;
			default:
				sleuth.test_page.haz_failed("This will fail as no code has been written to perform the check against " + check_name, error_elm);
				break;
		}
	},
	/**
	Method called when tests fail:
		- adds sleuth.test_page.test_fail_class to message container for styling
		- increments sleuth.test_page.num_failed
		- appends message to message container
		
	@param msg	the message we're providing to the user
	@param elm	the div element for this test to apply msg to
	*/
	haz_failed:function(msg,elm) {
		elm.addClass(sleuth.test_page.test_fail_class);
		sleuth.test_page.num_failed++;
		elm.append("<p>" + msg + "</p>");
	},
	/**
	Method called when tests pass:
		- increments sleuth.test_page.num_passed
		- appends message to message container
		
	@param msg	the message we're providing to the user
	@param elm	the div element for this test to apply msg to
	*/
	tests_passed:function(elm) {
		elm.addClass(sleuth.test_page.test_pass_class);
		sleuth.test_page.num_passed++;
		elm.append("<p>" + sleuth.test_page.test_pass_message + "</p>");
	},
	/**
	Method to create a summary of all tests to the user on tests completion
	Creates and assigns click events to a link allowing user to recheck page 
		(which will also call AJAX only tests)	
	*/
	create_summary:function() {
		var num_tests = sleuth.test_page.num_passed + sleuth.test_page.num_failed;
		// creating html string to append when finished
		var summary_html = "<div class='test_summary'><p><strong>Summary</strong></p><ul>";
		summary_html += "<li>Tests run: " + num_tests + "</li>";
		summary_html += "<li>Tests passed: " + sleuth.test_page.num_passed + "</li>";
		summary_html += "<li>Tests failed: " + sleuth.test_page.num_failed + "</li>";
		summary_html += "</ul>";
		summary_html += "</div>";
		sleuth.test_page.$logger.prepend(summary_html);
	},
	/**
	Method to parse page and look for specific HTML selectors:
		- creates empty arrays for all missing elements 
		- checks for position length of jQuery object gained from using HTML selector
		- pushes all un-matched files into array of missing files
		- if unique object detected in JSON checks for length of jQuery object of greater than 1
		
	@param target_selector	jQuery object containing the element we're testing against
	@param error_elm		the div to apply message to on completion
	@param obj				JSON segment containing test data and nested 
							tests for asserting only one selector on the page (optional)
	*/
	confirm_html:function(target_selector, error_elm, obj) {
		var missing_elms = [];
		$.each(obj.elements, function(i, val) {
			if (!target_selector.find(val).length) {
				missing_elms.push(val);
			}
		});
		if (missing_elms.length) {
			var file_list = missing_elms.join('</li><li>');
			var err_sum = obj.message.replace(/VAR/gi, file_list);
			sleuth.test_page.haz_failed(err_sum, error_elm);
			return;
		}
		// we've made it through all the checks unscathed - so let's pass the test
		sleuth.test_page.tests_passed(error_elm);
		return;
	}
};
