// ---------------------------------------------------------------------------------------------
// Password Strength Checking
// ---------------------------------------------------------------------------------------------

function PasswordChecker(el) {
	this.container = el;
	this.input = $('jsPasswordInput') ? $('jsPasswordInput') : $('password');
	this.meterContainer = this.container.down('.jsPasswordStrength')
	// Used for checking sequences
	this.alphabet = 'abcdefghijklmnopqrstuvwxyz';
	this.numbers = '0123456789';
	this.keyboard = ['qwert','yuiop','asdfg','hjkl','zxcvb','nm', '123456','789','7890'];

	if (this.input) {
		this.input.observe('keyup', this.checkPassword.bind(this));
		this.createPasswordMeter();
	}
}

PasswordChecker.prototype.createPasswordMeter = function() {
	if (!this.meterContainer) return;

	this.meter = new Element('div');
	this.color = new Element('div');
	this.label = new Element('div');

	this.label.insert({top: new Element('span', {id: 'passwordStrongLabel'}).update('Strong')});
	this.label.insert({top: new Element('span', {id: 'passwordWeakLabel'}).update('Weak')});
	this.label.insert(new Element('span', {id: 'passwordLabel'}).update('Password Strength'));
	

	this.meter.insert(this.color);
	this.meterContainer.insert(this.meter);
	this.meterContainer.insert(this.label);

	// Add a "How do I?" link
	var link = new Element('a', {'href': '#'});
	this.label.insert({'after': link});
	link.insert({'after': ' <img class="vm" width="19" height="20" src="/assets/img/icons/query.gif" alt=""/>'});
	//this.label.insert({'after': new Element('br')});
	link.update("How do I choose a 'safe' password");
	link.observe('click', function(e) {
		Event.stop(e);
		new Facebox(function() {
			var fbox = this;
			fbox.reveal($('passwordTipOverlay').cloneNode(true));
		});
	});


	this.color.setStyle({height:'10px', background:'#f00', width:'0%' });
	this.meter.setStyle({height:'10px', background:'#ccc', width:'160px', background:'url(/assets/img/shared/password_strength_metre.gif) top left no-repeat #ccc'});
}

PasswordChecker.prototype.setPasswordMeter = function(percentage) {
	var width = percentage;
	var color = [0,0,0];
	var ratio = percentage / 100;

	// First attempt, evenly transition from red to green
	// color[0] = (ratio <= 0.5) ? 255 : 255 - Math.round(255 * ((ratio-0.5)*2));
	// color[1] = (ratio <= 0.6) ? 63 + Math.round(192 * (ratio*(5/3))) : 255;
	// color[2] = (ratio <= 0.5) ? 0 : Math.round(64 * ((ratio-0.5)*2));

	// Better fit, only reach green towards the final 25%
	color[0] = (ratio <= 0.75) ? 255 : 255 - Math.round(255 * ((ratio-0.75)*4));
	color[1] = (ratio <= 0.8) ? 63 + Math.round(192 * (ratio*(5/4))) : 255;
	color[2] = (ratio <= 0.75) ? 0 : Math.round(64 * ((ratio-0.75)*4));

	// console.log(Math.round(percentage) + ": " + color);
	this.color.setStyle({width: width + '%', background:'rgb('+color.join(',')+')'});
}

PasswordChecker.prototype.checkPassword = function() {
	this.password = this.input.value;
	var hint = null;

	if (this.password.strip()) {
		var bitStrength = this.getEffectiveBitStrength();
		var goodStrength = 56;
		var percentageStrength = Math.min(100, 100 * (bitStrength / goodStrength));

		// Reduce the password strength with some common sense rules

		// Only apply 2 penalties at any time
		var penaltyCount = 0;

		// Does it include any punctuation, symbols or numbers?
		if (this.password.search(/[\W|\d]/) == -1) {
			// If not, penalise the strength by 10%
			percentageStrength = this.penalise(percentageStrength, 10);
			penaltyCount++;
			// Give the user a hint
			if (hint == null) {
				hint = 'Try using symbols and/or numbers.';
			}
		} else {
			percentageStrength = this.penalise(percentageStrength, -10);
		}

		// Does it include mixed case?
		if (penaltyCount < 2 && !(this.password.search(/[a-z]/) != -1 && this.password.search(/[A-Z]/) != -1)) {
			// If not, penalise the strength by 10%
			percentageStrength = this.penalise(percentageStrength, 10);
			penaltyCount++;
			// Give the user a hint
			if (hint == null) {
				hint = 'Try using mixed lower and UPPER case.';
			}
		} else {
			percentageStrength = this.penalise(percentageStrength, -10);
		}

		// Does it contain one of the example TL passwords?
		if (penaltyCount < 2 && this.password.toLowerCase().indexOf("luv 2 laf") != -1 || this.password.toLowerCase().indexOf("luv2laf") != -1) {
			// If so, penalise the strengh by 10%
			percentageStrength = this.penalise(percentageStrength, 10);
			penaltyCount++;
			// Give the user a hint
			if (hint == null) {
				hint = "Don't use an example password!";
			}
		}

		// Does it include characters repeated 3 times or more?
		// Always apply this penalty as it's a pretty bad one
		if (this.password.search(/(\w)\1+/) != -1) {
			// If so, penalise the strengh by 30%
			percentageStrength = this.penalise(percentageStrength, 30);
			penaltyCount++;
			// Give the user a hint
			if (hint == null) {
				hint = "Try not to repeat characters (aaa).";
			}
		}

		// Is the password all lowercase or all uppercase
		if (this.password.toLowerCase() == this.password || this.password.toUpperCase() == this.password) {
			// If so, penalise the strengh by 10%
			percentageStrength = this.penalise(percentageStrength, 10);
			penaltyCount++;
			// Give the user a hint
			if (hint == null) {
				hint = 'Try not using all lower or UPPER case.';
			}
		}

		// Does it have any repeated keyboard or alpha sequences?
		// We can't catch everything so just get the most likely suspects
		var hasSequence = false;

		// Do sequences of the alphabet first
		for(i = 0; i < (this.alphabet.length / 3); i++) {
			sub = this.alphabet.substring(i, i+3);
			if (this.password.search(new RegExp(sub, 'i')) != -1) {
				hasSequence = true;
				// Give the user a hint
				if (hint == null) {
					hint = 'Try not using alphabetic sequences (abc).';
				}
				break;
			}
		}
		if (!hasSequence) {
			for(i = 0; i < (this.numbers.length / 3); i++) {
				sub = this.numbers.substring(i, i+3);
				if (this.password.search(new RegExp(sub, 'i')) != -1) {
					hasSequence = true;
					// Give the user a hint
					if (hint == null) {
						hint = 'Try not using sequences of numbers (123).';
					}
					break;
				}
			}
		}
		if (!hasSequence && this.password.search(new RegExp('(' + this.keyboard.join('|') + ')')) != -1) {
			hasSequence = true;
			// Give the user a hint
			if (hint == null) {
				hint = 'Try not using keyboars sequences (qwert).';
			}
		}

		if (hasSequence) {
			// If so, penalise the strengh by 20%
			percentageStrength = this.penalise(percentageStrength, 20);
			penaltyCount++;
		}
		
		if ($('passwordHint')) {
			if (hint != null) {
				$('passwordHint').update(hint);
			} else {
				$('passwordHint').update();
			}
		}

		// Don't let the strength go above 100
		if (percentageStrength > 100) {
			percentageStrength = 100;
		}
	} else {
		percentageStrength = 0;
		if ($('passwordHint')) {
			$('passwordHint').update();
		}
	}

	this.setPasswordMeter(percentageStrength);
}

// Quick helper method to reduce a password strength percentage
// by a given percentage of the original
PasswordChecker.prototype.penalise = function(value, percent) {
	return Math.round(value - ((value / 100) * percent));
}

PasswordChecker.prototype.getEffectiveBitStrength = function() {
    // See http://www.codeproject.com/KB/security/passworddialog.aspx
    var chr = this.getCharSetSize();
    var len = this.password.length;
    return Math.round(Math.log(Math.pow(chr, len)) / Math.log(2));
}

PasswordChecker.prototype.getCharSetSize = function(password) {
    var n = 0;

    if (this.password.match(/[0-9]/)) n += 10;
    if (this.password.match(/[a-z]/)) n += 26;
    if (this.password.match(/[A-Z]/)) n += 26;
    if (this.password.match(/[\W|_]/)) n += 31;

    return n;
}

function initPasswordChecker() {
	$$('.jsPasswordChecker').each(function(el){
		new PasswordChecker(el);
	});
}

document.observe('dom:loaded', initPasswordChecker);
