/**
 * @author Walter Wimberly
 * @version 1.0 RC
 * @classDescription Strong Password JS tool to measure how strong
 * a password is.  It allows you to configure various settings.     
 * This tool calculates how strong a password is while it is being  
 * entered by the user.
 */

/**
 * 
 * @constructor
 * @param {Array}	settings	Associative array of all of the settings 
 * that you can configure.
 * @param {Function}	fn		a function that can be called when the 
 * password is being checked if it is being called at real time - this
 * allows for other sytles to be used if desired.
 */
function StrongPassword(settings, fn) {
	/**
	 * Stores the current strength of the password in a numeric fashion.
	 * @type int
	 */
	this.strength = 0;
	/**
	 * Show the strength of the password (as a number) be shown while is it calculated.
	 * @type boolean
	 */
	this.showStrength = false;
	/**
	 * Should numbers be allowed within the password.
	 * @type boolean
	 */
	this.allowNumbers = settings && settings.allowNumbers ? settings.allowNumbers : true;
	/**
	 * Should special characters (!@#$%^&*()-+_<>?/;[]{}) be allowed 
	 * within the password.
	 * @type boolean
	 */
	this.allowSpecialChars = settings && settings.allowSpecialChars ? settings.allowSpecialChars : false;
	/**
	 * The minimum "strength" score that is allowed for processing.
	 * @type int
	 */
	this.minAllowScore = settings && settings.minAllowScore ? settings.minAllowScore : 5;
	/**
	 * The minimum "strength" score that is allowed for a "Weak" setting.
	 * Very similar to the minAllowScore
	 * @type int
	 */
	this.minWeakScore = settings && settings.minWeakScore ? settings.minWeakScore : 9;
	/**
	 * The minimum "strength" score that is allowed for an "Intermediate"  
	 * level strength password.
	 * @type int
	 */
	this.minIntermediateScore = settings && settings.minIntermediateScore ? settings.minIntermediateScore : 14;
	/**
	 * The minimum "strength" score that is allowed for a "Strong" level 
	 * strength password.
	 * @type int
	 */
	this.minStrongScore = settings && settings.minStrongScore ? settings.minStrongScore : 18;
	/**
	 * What text is displayed if the password is too weak to be allowed.
	 * This can be any valid HTML string.
	 * @type string
	 */
	this.scoreTextNotAcceptable = settings && settings.scoreTextNotAcceptable ? settings.scoreTextNotAcceptable : 'Password Not Acceptable';
	/**
	 * What text is displayed if the password is of weak strength.
	 * This can be any valid HTML string.
	 * @type string
	 */
	this.scoreTextWeak = settings && settings.scoreTextWeak ? settings.scoreTextWeak : '<em>Weak Password</em>';
	/**
	 * What text is displayed if the password is of intermediate strength.
	 * This can be any valid HTML string.
	 * @type string
	 */
	this.scoreTextIntermediate = settings && settings.scoreTextIntermediate ? settings.scoreTextIntermediate : '<em>Just</em> Strong Enough Password';
	/**
	 * What text is displayed if the password is of strong strength.
	 * This can be any valid HTML string.
	 * @type string
	 */
	this.scoreTextStrong = settings && settings.scoreTextStrong ? settings.scoreTextStrong : '<strong>Strong</strong> Passsword';
	/**
	 * What is the URL for the file to look up against standard passwords.
	 * Default value is blank.  If the field is blank, then it will skip
	 * this step.  
	 * @type string
	 */
	this.dictionaryLookupFile = settings && settings.dictionary ? settings.dictionary : '';
	/**
	 * The id value of the password field to check.  If it is not passed, 
	 * a default value of password will be used.
	 * @type string
	 */
	this.src = settings && settings.source ? settings.source : 'password';
	/**
	 * This private variable is used to store the created DOM element which
	 * shows the value of the strength of the password.
	 * @private	
	 * @type string
	 */
	this.target = settings && settings.target ? settings.target : this.src + '_target';
	/**
	 * If true (default) then it will check the password as it is being typed.
	 * @type boolean
	 */
	this.realTimeCheck = settings && settings.realTimeCheck ? settings.realTimeCheck : true;
	
	this.sourcetype = settings && settings.sourcetype ? settings.sourcetype : "";
	this.sourceuser_id = settings && settings.sourceuser_id ? settings.sourceuser_id : 0;
	this.bannedFieldsIds = settings && settings.bannedFieldsIds ? settings.bannedFieldsIds : [];
	
	// avoide confusion over this within an event handler
	var wdSPSelf = this;
	
	// initialize the strength counter
	if(!settings.target)
	  $(this.src).insert({after: '<span id="' + this.target + '" class="wdStrengthHint"></span>'});
  this.validate = function() {
    wdSPSelf.strength = 0;
    var password = $(wdSPSelf.src).getValue();
    
    
    // define the regular expression necessary
    var rmatch = '!@#$%^&*()-+_<>?/;[]{}';
    
    var fShitAfterAjax = function(ajaxModifier)
    {
      var currentStrength = 0;
      currentStrength += ajaxModifier;
      //checking the banned fields list
      for(var iBanned = 0; iBanned < wdSPSelf.bannedFieldsIds.length; iBanned ++)
      {
        var sFieldValue = $(wdSPSelf.bannedFieldsIds[iBanned]).value;
        if(sFieldValue.length > 0 && password == sFieldValue)
          currentStrength -= 100;
      }
      
      // reset strength
      
      currentStrength += password.length;
      
  
      for(i = 0, max = password.length - 1; i < max; i++) {
        // take one away if the two characters next to each other are equal
        if(password.charAt(i) == password.charAt(i + 1)) {
          currentStrength-=2;
        }
        
        // give points for using capital letters
        if(password.charAt(i) == password.charAt(i).toUpperCase()) {
          currentStrength++;
        }
        
        // give points for using numbers
        if(wdSPSelf.allowNumbers) {
          if(password.charAt(i) * 1 == password.charAt(i)) {
            currentStrength++;
            
            // check for numbers in a sequence
            tmpNum = password[i] * 1;
            if(tmpNum + 1 == password.charAt(i + 1) || 
              tmpNum - 1 == password.charAt(i + 1)) {
              currentStrength-=3;
              }
          }
        }
        
        // give points for using special characters
        if(wdSPSelf.allowSpecialChars) {
          for(j = 0; j < rmatch.length; j++) {
            if(rmatch.charAt(j) == password.charAt(i)  ) {
              currentStrength+=3;
            }
          }
        }
      }
      
      // final password strength checks
      
      // revent 'cheating' with only upper case letters
      if(password == password.toUpperCase()) {
        currentStrength -= password.length;
      }
      
      // demonstrates mixed case?
      if(password != password.toLowerCase()) {
        currentStrength += 5; 
      }
      
    // remove existing classes
      $(wdSPSelf.target).removeClassName($(wdSPSelf.target).readAttribute("class"));
      $(wdSPSelf.target).addClassName('wdStrengthHint');

      
      // set the strength values...
      cssClass = 'wdTooWeakPassword';
      targetText = wdSPSelf.scoreTextNotAcceptable;
      if(currentStrength > wdSPSelf.minStrongScore) {
        cssClass = 'wdStrongPassword';
        targetText = wdSPSelf.scoreTextStrong;
      } else if(currentStrength >= wdSPSelf.minIntermediateScore) {
        cssClass = 'wdIntermediatePassword';
        targetText = wdSPSelf.scoreTextIntermediate;
      } else if(currentStrength >= wdSPSelf.minWeakScore) {
        cssClass = 'wdWeakPassword';
        targetText = wdSPSelf.scoreTextWeak;
      }
      
      if(wdSPSelf.showStrength) {
        $(wdSPSelf.target).update(currentStrength + ' : ' + targetText);
      } else {
        $(wdSPSelf.target).update(targetText);
      }
        
      $(wdSPSelf.target).addClassName(cssClass);
      
      //empty password -> no checks
      if(password.length == 0)
      {
        currentStrength = 100;
        $(wdSPSelf.target).removeClassName($(wdSPSelf.target).readAttribute("class"));
        $(wdSPSelf.target).update('');
      }
      
      wdSPSelf.strength = currentStrength;
      try {
        fn();
      } catch( e ) {
        // do nothing...user generated error, and we don't need to worry about their mistakes.
      }
    }
    
    // final error? checks
    // link to server side code and check to see if it is a dictionary 
    // word or a partial dictionary word - fancy checks will even 
    // convert numbers into letters to see if its a simple/conversion
    // check, but still a word...
    if(wdSPSelf.dictionaryLookupFile !== '' && wdSPSelf.dictionaryLookupFile !== undefined) {
      new Ajax.Request(wdSPSelf.dictionaryLookupFile, {
        evalJS: true,
        method: 'post',
        parameters: {password: password, sourceuser_id: wdSPSelf.sourceuser_id, sourcetype: wdSPSelf.sourcetype},
        onSuccess: function(json) {
        fShitAfterAjax(parseInt(json.responseJSON.returnValue));
        }
      });
    } else 
      fShitAfterAjax(0);
  };
	
	if( this.realTimeCheck ) {
	  $(this.src).observe("keyup", this.validate);
	}
}

StrongPassword.prototype = {
	getPasswordStrength: function() {
		return this.strength;
	}
}
