/*
USAGE
var special_chars = "~!@#$%&*";
var pw = new Password(document.getElementById('da_password').value, special_chars);
var verdict = pw.getStrength();


*/


function Password(passwordInputId, arg_spc_chars)
{   
    var password = document.getElementById(passwordInputId).value; 
    var spc_chars = arg_spc_chars;
    this.lcase_count = 0;
    this.ucase_count = 0;
    this.num_count = 0;
    this.schar_count = 0;
    this.length = 0;
    this.strength = 0;
    this.runs_score = 0;
    this.verdict = '';

    // These numbers are just guesses on my part (and not
    // all that educated, either ;) Adjust accordingly.
    var verdict_conv = {'weak':2.7, 'medium':15, 'strong':25};

    // These are weighting factors.  I figure that including
    // numbers is a little better than including uppercase
    // because numbers probably are not vulnerable to
    // dictionary searches, and including special chars is
    // even better.  These factors provide yet another
    // dimension.  Again, there are only guesses.
    var flc = 1.0;  // lowercase factor
    var fuc = 1.0;  // uppercase factor
    var fnm = 1.3;  // number factor
    var fsc = 1.5;  // special char factor

    this.getStrength = function()
    {
        /*
        STRENGTHS
        0 FORGET IT
        1 VERY WEAK
        2 WEAK
        3 MEDIUM
        4 STRONG
        */           
        
        if ((this.run_score = this.detectRuns()) <= 1)
        {
            this.verdict = 1;
            var description = this.getStrengthDescription(this.verdict);
            var strength = {'strength':this.verdict, 'description':description};
            return strength;
        }

        var regex_sc = new RegExp('['+spc_chars+']', 'g');

        this.lcase_count = password.match(/[a-z]/g);
        this.lcase_count = (this.lcase_count) ? this.lcase_count.length : 0;
        this.ucase_count = password.match(/[A-Z]/g);
        this.ucase_count = (this.ucase_count) ? this.ucase_count.length : 0;
        this.num_count   = password.match(/[0-9]/g);
        this.num_count   = (this.num_count) ? this.num_count.length : 0;
        this.schar_count = password.match(regex_sc);
        this.schar_count = (this.schar_count) ? this.schar_count.length : 0;
        this.length = password.length;

        var avg = this.length / 4;

        // I'm dividing by (avg + 1) to linearize the strength a bit.
        // To get a result that ranges from 0 to 1, divide 
        // by Math.pow(avg + 1, 4)
        this.strength = ((this.lcase_count * flc + 1) * 
                         (this.ucase_count * fuc + 1) *
                         (this.num_count * fnm + 1) * 
                         (this.schar_count * fsc + 1)) / (avg + 1);

        if (this.strength > verdict_conv.strong)
            this.verdict = 4;
        else if (this.strength > verdict_conv.medium)
            this.verdict = 3;
        else if (this.strength > verdict_conv.weak)
            this.verdict = 2;
        else
            this.verdict = 0;

        var description = this.getStrengthDescription(this.verdict);
        var strength = {'strength':this.verdict, 'description':description};
        return strength;
    }

    // This is basically an edge detector with a 'rectified' (or
    // absolute zero) result.  The difference of adjacent equivalent 
    // char values is zero.  The greater the difference, the higher
    // the result.  'aaaaa' sums to 0. 'abcde' sums to 1.  'acegi'
    // sums to 2, etc.  'aaazz', which has a sharp edge, sums to  
    // 6.25.  Any thing 1 or below is a run, and should be considered
    // weak.
    this.detectRuns = function()
    {
        var parts = password.split('');
        var ords = new Array();
        for (var i=0; i<parts.length; i++)
        {  
            ords[i] = parts[i].charCodeAt(0);
        }

        var accum = 0;
        var lasti = ords.length-1

        for (var i=0; i < lasti; ++i)
        {
            accum += Math.abs(ords[i] - ords[i+1]);
        }

        return accum/lasti;
    }  
    this.toString = function()
    {
        return 'lcase: '+this.lcase_count+
               ' -- ucase: '+this.ucase_count+
               ' -- nums: '+this.num_count+
               ' -- schar: '+this.schar_count+
               ' -- strength: '+this.strength+
               ' -- verdict: '+this.verdict;
    }
    this.getStrengthDescription = function(strength)
    {
        switch(strength)
        {
            case 4: return('Strong'); break;
            case 3: return('Medium'); break;
            case 2: return('Very Weak'); break;
            case 1: return('Weak'); break;
            case 0: return('No Good'); break;
        }
    }
}
