/**
 * Checks the strength of a password
 * based upon it's length and combination of characters
 *
 * Original source: http://w3style.co.uk/~d11wtq/password_checker.html>
 * Modified by Jérôme Gamez <http://jerome-gamez.de>
 */
var strengthChecker = function(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;
	var barNode;
	var barNode2;
	
	
	/**
	 * 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'] = 2; //  ursprünlich 3
	criteriaPoints['Length'] = 1;
	criteriaPoints['SpecialChars'] = 3;	//          Ursprünglich: 10
	criteriaPoints['AlphaNum'] = 0;
	criteriaPoints['NoNumbers'] = -2;
	criteriaPoints['NoSpecialChars'] = -0.4;
	criteriaPoints['NoLetters'] = -1;
	criteriaPoints['NoCaseChange'] = -0.5;
	criteriaPoints['Duplicates'] = -3; //          ursprünglich: -3
	criteriaPoints['MinLength'] = -3;
	/**
	 * Passwords should satisfy a minimum length
	 * or they will be penalized
	 * @var int min length
	 * @private
	 */
	var minLength = 6; // ursprünglich: 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()
	{
		var color = '#ffffff';
		var txt = '';
		var maxwidth = 150; /* if you change this you also have to change the width´s below */
		var width = 30;  
		
		if (password.length == 0)
		{
			txt = '&nbsp;';
			color = '#cccccc';
			width = 0;
		}
		else if (score < -1)
		{
			txt = 'Sehr schwach';
			color = '#a22a2a';
			width = 30; 
		}
		else if (score >= -1 && score < 2.5)
		{
			txt = 'Schwach';
			color = '#b86121';
			width = 60; 
		}
		else if (score >= 2.5 && score < 4)
		{
			txt = 'Gut';
			color = '#a1b821';
			width = 90; 
		}
		else if (score >= 4 && score < 6.75)
		{
			txt = 'Stark';
			color = '#64c451';
			width = 120; 
			
		}
		else if (score > 6.75)
		{
			txt = 'Sehr stark';
			color = '#06ab02';
			width = 150; 
		}
		
		try {
			document.getElementById(elementId).removeChild(gaugeNode);
			document.getElementById(elementId).removeChild(barNode);
			document.getElementById(elementId).removeChild(barNode2);
		} catch (e) {
			//
		}
		
		gaugeNode = document.createElement('div');
		gaugeNode.style.width = maxwidth + 'px';
		gaugeNode.style.padding= '1px';
		gaugeNode.style.margin = '0px';
		gaugeNode.style.overflow = 'visible';
		gaugeNode.style.color = '#000000';
		// gaugeNode.style.fontSize = '12px';
		gaugeNode.style.fontWeight = 'bold';
		gaugeNode.style.textAlign = 'center';
		gaugeNode.style.whiteSpace = 'nowrap';
		gaugeNode.innerHTML = txt;
		document.getElementById(elementId).appendChild(gaugeNode);
		
		barNode = document.createElement('div');
		barNode.style.width = width + 'px';
		barNode.style.padding= '1px';
		barNode.style.margin = '0px';
		barNode.style.borderBottomWidth= '5px';
		barNode.style.borderBottomColor= color;
		barNode.style.borderBottomStyle = 'solid';
		barNode.style.overflow = 'visible';
		barNode.style.color = '#000000';
		barNode.style.fontWeight = 'bold';
		barNode.style.textAlign = 'center';
		barNode.style.whiteSpace = 'nowrap';
		barNode.style.cssFloat = 'left';
		barNode.style.styleFloat = 'left';
		document.getElementById(elementId).appendChild(barNode);
		
		if (width != maxwidth)
		{
			barNode2 = document.createElement('div');
			barNode2.style.width = (maxwidth - width - 4) + 'px';
			barNode2.style.padding= '1px';
			barNode2.style.margin = '0px';
			barNode2.style.borderBottomWidth= '5px';
			barNode2.style.borderBottomColor= '#ccc';
			barNode2.style.borderBottomStyle = 'solid';
			barNode2.style.overflow = 'visible';
			barNode2.style.color = '#000000';
			barNode2.style.fontWeight = 'bold';
			barNode2.style.textAlign = 'center';
			barNode2.style.whiteSpace = 'nowrap';
			barNode2.style.cssFloat = 'left';
			barNode2.style.styleFloat = 'left';
			document.getElementById(elementId).appendChild(barNode2);
		}

		
	}
	/**
	 * 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)
	{
		password = v;
		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;
		}
		
		// console.log ("Score = lengthPoints: " + score);
		
		//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);
		// console.log ("Score nach CaseChange: " + score + " (" + v.length + " * " + criteriaPoints['CaseChange'] + " * " + multiplier + ")" );
		//Alpha Numeric Points
		var alphaNumDiff = Math.abs(upper+lower - numbers);
		score += parseFloat(((lettersOnly.length + numbersOnly.length) - alphaNumDiff) * criteriaPoints['AlphaNum'] * multiplier);
		// console.log ("Score nach AlphaNum: " + score + " (" + v.length + " * " + criteriaPoints['AlphaNum'] + " * " + multiplier + ")");
		//Special Character Points
		score += parseFloat(specialChars * criteriaPoints['SpecialChars'] * multiplier);
		// console.log ("Score nach SpecialChars: " + score + " (" + v.length + " * " + criteriaPoints['SpecialChars'] + " * " + multiplier + ")");
		//Penalise for lack of numbers
		if (!numbers)
		{
			score += parseFloat(v.length * criteriaPoints['NoNumbers'] * multiplier);
		}
		// console.log ("Score nach NoNumbers: " + score + " (" + v.length + " * " + criteriaPoints['NoNumbers'] + " * " + multiplier + ")");
		//Penalise for lack of letters
		if (!lower && !upper)
		{
			score += parseFloat(v.length * criteriaPoints['NoLetters'] * multiplier);
		}
		// console.log ("Score nach NoLetters: " + score + " (" + v.length + " * " + criteriaPoints['NoLetters'] + " * " + multiplier + ")");
		//Penalise for lack of special chars
		if (!specialChars)
		{
			score += parseFloat(v.length * criteriaPoints['NoSpecialChars'] * multiplier);
		}
		// console.log ("Score nach NoSpecialChars: " + score);
		//Penalise for lack of changing case
		if ((upper || lower) && (!upper || !lower))
		{
			score += parseFloat(v.length * criteriaPoints['NoCaseChange'] * multiplier);
		}
		// console.log ("Score nach NoCaseChange: " + score + " (" + v.length + " * " + criteriaPoints['NoCaseChange'] + " * " + multiplier + ")");
		//Penalise for duplicate chars
		score += parseFloat(duplicates * criteriaPoints['Duplicates'] * multiplier);
		// console.log ("Score nach Duplicates: " + score + " (" + v.length + " * " + criteriaPoints['Duplicates'] + " * " + multiplier + ")");
		//Penalise for PW being too short
		score += parseFloat((minLength - v.length) * multiplier * criteriaPoints['MinLength']);
		// console.log ("Score nach MinLength: " + score + " (" + parseFloat(minLength - v.length) + " * " + criteriaPoints['MinLength'] + " * " + multiplier + ")");
		// console.log ("-------------");
		//Now update the gauge
		updateOutput();
		if (helperId) showHelper((v.length >= minLength), numbers, (lower + upper), specialChars, !((upper || lower) && (!upper || !lower)), !duplicates);
		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)
	{
		try {
			document.getElementById(helperId).removeChild(helperNode);
		} catch(e) {
			//
		}
		
		helperNode = document.createElement('ul');
		var rows = new Array();
		if (!len)
		{
			li = document.createElement('li');
			li.innerHTML = 'Password is short (' + minLength +' recommended)';
			helperNode.appendChild(li);
		}
		if (!numbers)
		{
			li = document.createElement('li');
			li.innerHTML = 'Password contains no numbers';
			helperNode.appendChild(li);
		}
		if (!letters)
		{
			li = document.createElement('li');
			li.innerHTML = 'Password contains no letters';
			helperNode.appendChild(li);
		}
		if (!special)
		{
			li = document.createElement('li');
			li.innerHTML = 'Password contains no special characters';
			helperNode.appendChild(li);
		}
		if (!caseChange)
		{
			li = document.createElement('li');
			li.innerHTML = 'Password is all the same case';
			helperNode.appendChild(li);
		}
		if (!duplicates)
		{
			li = document.createElement('li');
			li.innerHTML = 'Password contains duplicate characters';
			helperNode.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');
