/*
 ---

 name: StrongPass.js

 description: checks password strength of a string

 authors: Dimitar Christoff
 https://github.com/DimitarChristoff/StrongPass

 improved and customized by:
 Michael Danielczok (www.pcsg.de)

 license: MIT-style license.

 version: 1.0.5

 requires:
 - Core/String
 - Core/Element
 - Core/DOMEvent
 - Core/Class
 - Core/Array

 provides: StrongPass

 ...
 */
(function () {
    'use strict';

    var StrongPass = new Class({

        Implements: [Options, Events],

        options: {

            minChar: 6, // too short while less than this

            passIndex: 2, // Weak

            // output verdicts, colours and bar %
            label: 'Password strength: ',

            verdicts: [
                'Too Short',
                'Very weak',
                'Weak',
                'Good',
                'Strong',
                'Very strong'
            ],

            colors: [
                '#ccc',
                '#500',
                '#800',
                '#f60',
                '#050',
                '#0f0'
            ],

            width: [
                '0%',
                '10%',
                '30%',
                '60%',
                '80%',
                '100%'
            ],

            // tweak scores here
            scores: [
                10,
                15,
                25,
                45
            ],

            // when in banned list, verdict is:
            bannedPass: 'Not allowed',

            // styles
            passStrengthZen: 'div.pass-container',

            passbarClassZen: 'div.pass-bar', // css controls

            passbarHintZen: 'div.pass-hint',

            // output
            render: true, // it can just report for your own implementation

            injectTarget: null,

            injectPlacement: 'after'
        },

        bannedPasswords: [
            // see study here:
            //http://www.zdnet.com/article/top-25-passwords-overused-within-enterprises-password1-welcome-welcome1/
            '123456',
            '12345',
            '123456789',
            'password',
            'iloveyou',
            'princess',
            'rockyou',
            '1234567',
            '12345678',
            'abc123',
            'nicole',
            'daniel',
            'babygirl',
            'monkey',
            'jessica',
            'lovely',
            'michael',
            'ashley',
            '654321',
            'qwerty',
            'password1',
            'welcome',
            'welcome1',
            'password2',
            'password01',
            'password3',
            'p@ssw0rd',
            'passw0rd',
            'password4',
            'password123',
            'summer09',
            'password6',
            'password7',
            'password9',
            'password8',
            'welcome2',
            'welcome01',
            'winter12',
            'spring2012',
            'summer12',
            'summer2012'
        ],

        /**
         * @constructor
         * @param {DOMElement} element Base element to attach to
         * @param {Object} options* Options to merge in / attach events from
         * @fires StrongPass#ready
         * @returns SrongPass
         */
        initialize: function (element, options) {
            this.setOptions(options);
            this.element = document.id(element);
            this.options.render && this.createBox();
            this.attachEvents();
            this.fireEvent('ready');
        },

        /**
         * @description Attaches events and saves a reference
         * @returns {StrongPass}
         */
        attachEvents: function () {
            // only attach events once so freshen
            this.eventObj && this.element.removeEvents(this.eventObj);
            this.element.addEvents(this.eventObj = {
                keyup: this.runPassword.bind(this)
            });

            return this;
        },

        /**
         * @description Attaches pass elements.
         * @returns {StrongPass}
         */
        createBox: function () {
            //todo: should be templated
            var o     = this.options,
                width = o.containerWidth || this.element.getSize().x;

            this.stbox = new Element(o.passStrengthZen, {
                styles: {
                    width: width
                }
            });

            this.stdbar = new Element(o.passbarClassZen, {
                styles: {
                    //width: width - 2
                }
            }).inject(this.stbox);

            this.txtbox = new Element(o.passbarHintZen).inject(this.stbox);
            this.stbox.inject((document.id(o.injectTarget) || this.element), o.injectPlacement);

            return this;
        },

        /**
         * @description Runs a password check on the keyup event
         * @param {Object} event*
         * @param {String} password* Optionally pass a string or go to element getter
         * @fires StrongPass#fail StrongPass#pass
         * @returns {StrongPass}
         */
        runPassword: function (event, password) {
            password = password || this.element.get('value');

            var score = this.checkPassword(password),
                index = 0,
                o     = this.options,
                s     = Array.clone(o.scores),
                verdict;

            if (Array.indexOf(this.bannedPasswords, password.toLowerCase()) !== -1) {
                this.fireEvent('banned', password);
                verdict = o.bannedPass;
            }
            else {
                if (score < 0 && score > -199) {
                    index = 0;
                }
                else {
                    s.push(score);
                    s.sort(function (a, b) {
                        return a - b;
                    });
                    index = s.indexOf(score) + 1;
                }

                verdict = o.verdicts[index] || o.verdicts.getLast();
            }

            if (o.render) {
                this.txtbox.set('text', [o.label, verdict].join(''));
                this.stdbar.setStyles({
                    width     : o.width[index] || o.width.getLast(),
                    background: o.colors[index] || o.colors.getLast()
                });
            }

            /**
             * @event StrongPass#fail,StrongPass#pass
             */
            return this.fireEvent(['fail', 'pass'][+(this.passed = o.verdicts.indexOf(verdict) >= o.passIndex)], [index, verdict, password]);
        },

        /**
         * @type {Array}
         * @description The collection of regex checks and how much they affect the scoring
         */
        checks: [
            /* alphaLower */ {
                re   : /[a-z]/,
                score: 1
            },
            /* alphaUpper */ {
                re   : /[A-Z]/,
                score: 5
            },
            /* mixture of upper and lowercase */ {
                re   : /([a-z].*[A-Z])|([A-Z].*[a-z])/,
                score: 2
            },
            /* threeNumbers */ {
                re   : /(.*[0-9].*[0-9].*[0-9])/,
                score: 3
            },
            /* special chars */ {
                re   : /.[!@#$%^&*?_~]/,
                score: 6
            },
            /* multiple special chars */ {
                re   : /(.*[!@#$%^&*?_~].*[!@#$%^&*?_~])/,
                score: 8
            },
            /* all together now, does it look nice? */ {
                re   : /([a-zA-Z0-9].*[!@#$%^&*?_~])|([!@#$%^&*?_~].*[a-zA-Z0-9])/,
                score: 3
            },
            /* password of a single char sucks */ {
                re   : /(.)\1+$/,
                score: 2
            }
        ],

        checkPassword: function (pass) {
            var score   = 0,
                minChar = this.options.minChar,
                len     = pass.length,
                diff    = len - minChar;

            (diff < 0 && (score -= 100)) ||
            (diff >= 5 && (score += 15)) ||
            (diff >= 3 && (score += 8)) ||
            (diff === 2 && (score += 6));

            Array.each(this.checks, function (check) {
                pass.match(check.re) && (score += check.score);
            });

            // bonus for length per char
            score && (score += len);
            return score;
        }
    });

    if (typeof define === 'function' && define.amd) {
        // return an AMD module
        define(function () {
            return StrongPass;
        });
    }
    else {
        // exports to global object
        this.StrongPass = StrongPass;
    }

    // change to any object / ns
}.call(this));