
/**
 * Checks the strength of a password
 * based upon it's length and combination of characters
 * inspired by http://forums.devnetwork.net/viewtopic.php?t=51588
 * thanks to d11wtq -> http://www.w3style.co.uk/~d11wtq/password_checker.html
 * Possibility for german/english added by Petra Sütterlin http://www.philognosie.net
 * Made it work in IE by Petra Sütterlin http://www.philognosie.net
 */
function strengthChecker(outputId)
{
	/**
	 * Id of the node we want to show output in
	 * @var string ID
	 * @private
	 */
	var elementId = outputId;
	/**
	 * An internal timeout.  Gets used in loading
	 * ... possibly pointless.
	 * @var timeout
	 * @private
	 */
	var tm = 0;
	/**
	 * Passwords are given a strength score
	 * @var float score
	 * @private
	 */
	var score = false;
	/**
	 * The current password
	 * @var string password
	 * @private
	 */
	var password = '';
	/**
	 * The actual DOM node for the guage we append
	 * to elementId
	 * @var node gauge
	 * @private
	 */
	var gaugeNode;
	/**
	 * The DOM node for the helper
	 * @var node helper
	 * @private
	 */
	var helperNode;
	/**
	 * The weight given for the various strength enhancers
	 * @var array (float) points
	 * @private
	 */
	var criteriaPoints = new Array();
	criteriaPoints['CaseChange'] = 3;
	criteriaPoints['Length'] = 1;
	criteriaPoints['SpecialChars'] = 10;
	criteriaPoints['AlphaNum'] = 2.5;
	criteriaPoints['NoNumbers'] = -2;
	criteriaPoints['NoSpecialChars'] = -0.4;
	criteriaPoints['NoLetters'] = -1;
	criteriaPoints['NoCaseChange'] = -0.5;
	criteriaPoints['Duplicates'] = -3;
	criteriaPoints['MinLength'] = -3;
	/**
	 * Passwords should satisfy a minimum length
	 * or they will be penalized
	 * @var int min length
	 * @private
	 */
	var minLength = 8;
	var helperId = false;

	/**
	 * Does the initial loading of the password gauge
	 * upon instantiation if the page is idle
	 * @return void
	 * @private
	 */
	var load = function()
	{
		//The DOM tree may not have finished building yet
		if (document.getElementById(elementId))
		{
			if (score === false) updateOutput();
			if (tm) window.clearTimeout(tm);
		}
		else //If the element doesn't exist, look again in 200ms
		{
			tm = window.setTimeout(function() { load(); }, 200);
		}
	}
	/**
	 * Update what is displayed for the gauge at elementId
	 * @return void
	 * @private
	 */
	var updateOutput = function(language)
	{
		var color = '#ffffff';
		var txt = '';

		if (score < -1 || password.length == 0)
		{
			if( language == 'de' ) txt = 'Sehr schwach';
			else txt = 'Very Weak';
			color = '#a22a2a';

			if( password.length == 0 )
			{
				txt = '';
				color = '#808080';
			}

		}
		else if (score >= -1 && score < 2.5)
		{
			if( language == 'de' ) txt = 'Schwach';
			else txt = 'Weak';
			color = '#b86121';
		}
		else if (score >= 2.5 && score < 4)
		{
			if( language == 'de' ) txt = 'Gut';
			else txt = 'Good';
			color = '#a1b821';
		}
		else if (score >= 4 && score < 6.75)
		{
			if( language == 'de' ) txt = 'Stark';
			else txt = 'Strong';
			color = '#64c451';
		}
		else if (score > 6.75)
		{
			if( language == 'de' ) txt = 'Sehr stark';
			else txt = 'Very Strong';
			color = '#06ab02';
		}


		try {
			document.getElementById(elementId).removeChild(gaugeNode);
		} catch (e) {
			//
		}

		gaugeNode = document.createElement('div');
		gaugeNode.style.width = '100';
		gaugeNode.style.padding= '3px';
		gaugeNode.style.backgroundColor = color;
		gaugeNode.style.color = '#000000';
		gaugeNode.style.border = '1px solid #777777';
		gaugeNode.style.fontSize = '12px';
		gaugeNode.style.fontWeight = 'bold';
		gaugeNode.style.textAlign = 'center';
		gaugeNode.innerHTML = txt;
		document.getElementById(elementId).appendChild(gaugeNode);
	}
	/**
	 * Reads a new password and then gives it a score
	 * The password gauge is then updated
	 * @param string password value
	 * @return float score
	 */
	this.check = function(v,lang)
	{
		password = v;
		language = lang;
		score = 0;
		//Score based upon length
		var lengthPoints = criteriaPoints['Length'];
		var multiplier = lengthPoints;
		for (i = 0; i < v.length; i++) //Non-linear
		{
			score += lengthPoints;
			if (i < minLength) lengthPoints *= 0.8;
			multiplier *= 0.8;
		}
		//Use this as a factor in subsequent point additions
		var multiplier = lengthPoints;

		var collected = new Array();
		var lower = 0;
		var upper = 0;
		var numbers = 0;
		var specialChars = 0;
		var duplicates = 0;
		var lettersOnly = '';
		var numbersOnly = '';
		var charsOnly = '';
		for (var i = 0; i < v.length; i++)
		{
			var letter = v.substr(i, 1);
			if (collected.hasValue(letter)) duplicates++;

			collected.push(letter);
			if (letter.match(/[a-z]/))
			{
				lettersOnly += letter;
				lower++;
			}
			else if (letter.match(/[A-Z]/))
			{
				lettersOnly += letter;
				upper++;
			}
			else if (letter.match(/\d/))
			{
				numbersOnly += letter;
				numbers++;
			}
			else if (letter.match(/\W/))
			{
				charsOnly += letter;
				specialChars++;
			}
		}
		//Points based upon case change
		var caseDiff = Math.abs(upper - lower);
		score += parseFloat((lettersOnly.length - caseDiff) * criteriaPoints['CaseChange'] * multiplier);
		//Alpha Numeric Points
		var alphaNumDiff = Math.abs(upper+lower - numbers);
		score += parseFloat(((lettersOnly.length + numbersOnly.length) - alphaNumDiff) * criteriaPoints['AlphaNum'] * multiplier);
		//Special Character Points
		score += parseFloat(specialChars * criteriaPoints['SpecialChars'] * multiplier);
		//Penalise for lack of numbers
		if (!numbers)
		{
			score += parseFloat(v.length * criteriaPoints['NoNumbers'] * multiplier);
		}
		//Penalise for lack of letters
		if (!lower && !upper)
		{
			score += parseFloat(v.length * criteriaPoints['NoLetters'] * multiplier);
		}
		//Penalise for lack of special chars
		if (!specialChars)
		{
			score += parseFloat(v.length * criteriaPoints['NoSpecialChars'] * multiplier);
		}
		//Penalise for lack of changing case
		if ((upper || lower) && (!upper || !lower))
		{
			score += parseFloat(v.length * criteriaPoints['NoCaseChange'] * multiplier);
		}
		//Penalise for duplicate chars
		score += parseFloat(duplicates * criteriaPoints['Duplicates'] * multiplier);
		//Penalise for PW being too short
		score += parseFloat((minLength - v.length) * multiplier * criteriaPoints['MinLength']);

		//Now update the gauge
		updateOutput(language);
		if (helperId) showHelper((v.length >= minLength), numbers, (lower + upper), specialChars, !((upper || lower) && (!upper || !lower)), !duplicates, language);
		return score;
	}
	/**
	 * Show a helper check-list for the user
	 * @param bool length OK
	 * @param bool contains numbers
	 * @param bool contains letters
	 * @param bool contain special chars
	 * @param bool mixed case
	 * @param bool contains duplicates
	 * @return void
	 */
	var showHelper = function(len, numbers, letters, special, caseChange, duplicates, language)
	{
		try {
			document.getElementById(helperId).removeChild(helperNode);
		} catch(e) {
			//
		}

		helperNode = document.createElement('p');
		if( language == 'de' ) var questiontext = document.createTextNode('Schützen Sie Ihre sensiblen Daten und machen Sie Ihr Passwort sicherer, indem Sie:');
		else var questiontext = document.createTextNode('Make your password stronger:');
		var b=document.createElement('b');
		b.appendChild(questiontext);
		helperNode.appendChild(b);

		ul = document.createElement('ul');
		helperNode.appendChild(ul);

		//helperNode = document.createElement('ul');
		
		li = document.createElement('li');
		if( language == 'de' ) li.innerHTML = 'niemals Wörter wie "laufen" oder "marathon" verwenden';
		else li.innerHTML = 'do not use words like "running" or "marathon"';
		ul.appendChild(li);
		
		li = document.createElement('li');
		if( language == 'de' ) li.innerHTML = 'niemals Ihren Vereins- oder Veranstaltungsnamen verwenden';
		else li.innerHTML = 'never use the event or company name';
		ul.appendChild(li);

		var rows = new Array();
		if (!len)
		{
			li = document.createElement('li');
			if( language == 'de' ) li.innerHTML = 'mindestens ' + minLength +' Zeichen verwenden';
			else li.innerHTML = 'do not use less than ' + minLength +' characters';
			ul.appendChild(li);
		}
		if (!numbers)
		{
			li = document.createElement('li');
			if( language == 'de' ) li.innerHTML = 'Zahlen einfügen';
			else li.innerHTML = 'use numbers';
			ul.appendChild(li);
		}
		if (!letters)
		{
			li = document.createElement('li');
			if( language == 'de' ) li.innerHTML = 'Buchstaben einfügen';
			else li.innerHTML = 'use letters';
			ul.appendChild(li);
		}
		if (!special)
		{
			li = document.createElement('li');
			if( language == 'de' ) li.innerHTML = 'Sonderzeichen einfügen';
			else li.innerHTML = 'use special characters';
			ul.appendChild(li);
		}
		if (!caseChange)
		{
			li = document.createElement('li');
			if( language == 'de' ) li.innerHTML = 'Groß- und Kleinbuchstaben mischen';
			else li.innerHTML = 'chose different cases';
			ul.appendChild(li);
		}
		if (!duplicates)
		{
			li = document.createElement('li');
			if( language == 'de' ) li.innerHTML = 'jedes Zeichen nur einmal verwenden';
			else li.innerHTML = 'use characters once only';
			ul.appendChild(li);
		}

		document.getElementById(helperId).appendChild(helperNode);
	}
	/**
	 * Allow the user to change the points weightings
	 * @param string criteria (See criteriaPoints)
	 * @param float points
	 * @return bool successful
	 */
	this.setPoints = function(criteria, pnts)
	{
		if (criteriaPoints[criteria])
		{
			criteriaPoints[criteria] = parseFloat(pnts);
			return true;
		}
		else return false;
	}
	/**
	 * Specify the minimum length of a "strong" password
	 * defaults to 8
	 * @param int length
	 * @return void
	 */
	this.setMinLength = function(len)
	{
		minLength = parseInt(len);
	}
	/**
	 * Display a helper dialog
	 * @param string helperNode id
	 * @return void
	 */
	this.setHelperId = function(helper)
	{
		helperId = helper;
	}

	//At end of instantiation load the gauge
	load();

	//Just comes in useful
	if (!Array.hasValue)
	{
		Array.prototype.hasValue = function(v)
		{
			for (var i in this)
			{
				if (this[i] == v) return true;
			}
			return false;
		}
	}
}

var pw = new strengthChecker('pw_gauge');
pw.setHelperId('helper');

