(function($) {
$.extend($.fn, {
    formValidate: function(settings) {

        var formValidator = $.data(this[0], 'formValidator');
        if (formValidator) {
            return formValidator;
        }

        formValidator = new $.formValidator(settings, this[0]);
        $.data(this[0], 'formValidator', formValidator);

        this.submit(function(event) {
            return formValidator.execute();
        });

        return formValidator;
    }
});

$.formValidator = function(settings, form) {
    this.settings = $.extend({}, $.formValidator.defaults, settings);
    this.currentForm = form;
    this.initialize();
};

$.extend($.formValidator, {
    defaults: {
        errorClass: 'validate_error',
        highlight: function (element, errorClass) {
            $(element).addClass(errorClass);
        },
        unhighlight: function (element, errorClass) {
            $(element).removeClass(errorClass);
        }
    },

    messages: {
        required: {
            input: 'を入力してください。',
            radio: 'を選択してください。',
            checkbox: 'をチェックしてください。'
        },
        email: 'の書式が不正です。',
        digits: 'は数字を入力してください'
    },

    classRules: {
        required: {required: true},
        email:    {email: true},
        digits:   {digits: true}
    },

    prototype: {
        initialize: function() {
            this.reset();
        },

        reset: function() {
            this.errorList = [];
            this.currentElements = $([]);
        },

        execute: function() {
            this.check();
            if (!this.isValid()) {
                this.showErrors();
                return false;
            }
            return true;
        },

        elements: function() {
            return $([]).add(this.currentForm.elements)
                .filter(":input")
                .not(":hidden, :submit, :reset, :image, [disabled]")
                ;
        },

        showErrors: function() {
            var messages = [];
            for (var i = 0; this.errorList[i]; i++) {
                var error = this.errorList[i];
                this.settings.highlight.call(this, error.element, this.settings.errorClass);
                messages.push(error.message);
            }
            for (var i = 0, elements = this.validElements(); elements[i]; i++) {
                this.settings.unhighlight.call(this, elements[i], this.settings.errorClass);
            }
            alert(messages.join("\n"));
        },

        isValid: function() {
            return this.errorList.length == 0;
        },

        validElements: function() {
            return this.currentElements.not(this.invalidElements());
        },

        invalidElements: function() {
            return $(this.errorList).map(function() {
                return this.element;
            });
        },

        prepareForm: function() {
            this.reset();
        },

        check: function() {
            this.prepareForm();
            var e;
            var finish_names = {};
            for (i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++) {
                e = elements[i];
                if (finish_names[e.name] !== undefined) {
                    continue;
                }
                var rules = this.getRules(e);
                var result = null;
                for (method in rules) {
                    var rule = { method: method, parameters: rules[method] };
                    result = $.formValidator.methods[method].call(this, $.trim(e.value), e, rule.parameters);
                    if (!result) {
                        this.addError(e, rule);
                    }
                }
                finish_names[e.name] = result;
            }
        },

        addError: function(element, rule)
        {
            var message = $.formValidator.messages[rule.method];
            if (typeof message != "string") {
                var message_key = 'input';
                var check_type = element.type.toLowerCase();
                switch (check_type) {
                case 'radio':
                case 'checkbox':
                    message_key = check_type;
                    break;
                }
                message = message[message_key];
            }

            var errText = this.errorText(element);
            message = errText + message;
            this.errorList.push({
                message: message,
                element: element
            });
        },

        errorText: function(element) {
            return this.isCheckable(element)
                ? $(element).attr('title')
                : $('label', $(this.currentForm)).filter('[@for="' + element.id + '"]').text();
        },

        optional: function(element) {
            return !$.formValidator.methods.required.call(this, $.trim(element.value), element);
        },

        isCheckable: function(element) {
            return (/radio|checkbox/i.test(element.type));
        },

        getRules: function(element) {
            var rules = {};
            var classes = $(element).attr('class');
            classes && $.each(classes.split(' '), function() {
                if (this in $.formValidator.classRules) {
                    $.extend(rules, $.formValidator.classRules[this]);
                }
            });
            return rules;
        },

        findByName: function(name) {
            var form = this.currentForm;
            return $(document.getElementsByName(name)).map(function(index, element) {
                return element.form == form && element.name == name && element  || null;
            });
        }
    },

    methods: {
        required: function(value, element, params) {
            switch(element.nodeName.toLowerCase()) {
            case 'select':
                var options = $("option:selected", element);
                return options.length > 0
                    && ( element.type == "select-multiple"
                        || ($.browser.msie && !(options[0].attributes['value'].specified) ? options[0].text : options[0].value).length > 0);
            case 'input':
                if (this.isCheckable(element)) {
                    return this.findByName(element.name).filter(':checked').length > 0;
                }
            default:
                return value.length > 0;
            }
        },

        email: function(value, element) {
            return this.optional(element)
                || /^[\+\-_a-zA-Z0-9][a-zA-Z0-9\?\+\-\._\/]*\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,6}|[0-9]{1,3})(\]?)$/i.test(element.value);
        },

        digits: function(value, element) {
            return this.optional(element)
                || /^\d+$/.test(value);
        }
    }
});

})(jQuery);
