function Validator(form){
    var d = document, f = form || d.forms[0], elems = [];
    var $ = function(id){
        return d.getElementById(id);
    };
    if (!f) {
        throw "can't initialize validator. Form is undefined!";
    }
    var fn = this.prototype = {
        init: function(){
            fn.filter(elems, f.getElementsByTagName("input"));
            fn.filter(elems, f.getElementsByTagName("textarea"));
            fn.decorate(elems);
            fn.parse();
            fn.addEvent(f, "submit", function(){
                var els = elems, rs = true;
                for (var i = 0; i < els.length; i++) {
                    if (!fn.validate(els[i])) {
                        return rs = false;
                    }
                }
                return rs;
            });
        },
        parse: function(){
            var inputs = f.getElementsByTagName("input"), textareas = f.getElementsByTagName("textarea");
            var els = fn.toArray(inputs);
            els = els.concat(fn.toArray(textareas));
            for (var i = 0, l = els.length; i < l; i++) {
                fn.register(els[i]);
            }
        },
        showError: function(error, pos){
            var e = $('error'), s = e.style;
            if (s.display == "block") {
                return;
            }
            s.display = "block";
            e.innerHTML = error;
        },
        hideError: function(){
            var e = $('error'), s = e.style;
            s.display = "none";
        },
        register: function(el){
            fn.addEvent(el, "blur", function(e){
                fn.validate(el);
            });
        },
        validate: function(el){
            var func, msg = "";
            var cln = el.className.split(/\s+/);
            if (cln && cln.length > 0) {
                for (var i = 0; i < cln.length; i++) {
                    func = mds[cln[i]];
                    if (typeof func == "function") {
                        if (!func(el)) {
                            msg = el.getAttribute(cln[i]) || msgs[cln[i]];
                            if (typeof showError == "function") {
                                showError(msg);
                            }
                            else {
                                fn.showError(msg);
                            }
                            return false;
                        }
                        else {
                            typeof hideError == "function" ? hideError() : fn.hideError();
                        }
                    }
                }
            }
            return true;
        },
        toArray: function(els){
            var rs = [];
            for (var i = 0, l = els.length; i < l; i++) {
                rs.push(els[i]);
            }
            return rs;
        },
        stopBubble: function(e){
            if (e && e.stopPropagation) {
                e.stopPropagation();
            }
            else {
                window.event.cancelBubble = true;
            }
        },
        preventDefault: function(){
            if (e && e.preventDefault) 
                e.preventDefault();
            else 
                window.event.returnValue = false;
            return false;
        },
        decorate: function(els){
            var e;
            for (var i = 0, l = els.length; i < l; i++) {
                e = els[i];
                fn.addEvent(e, "focus", function(e){
                    var e = e || window.event, el = e.srcElement || e.target;
                    if (!el.className.match(/(^|\s)active(\s|$)/)) {
                        el.className += " active";
                    }
                    fn.stopBubble(e);
                });
                fn.addEvent(e, "blur", function(e){
                    var e = e || window.event, el = e.srcElement || e.target;
                    el.className = el.className.replace(/(^|\s)active(\s|$)/, "");
                    fn.stopBubble(e);
                });
            }
        },
        filter: function(rs, target){
            var el;
            for (var i = 0, l = target.length; i < l; i++) {
                el = target[i];
                if (el.type == "text" || el.tagName.toUpperCase() == "TEXTAREA") {
                    rs.push(el);
                }
            }
        },
        getInputsByName: function(name){
            var results = [];
            results.numChecked = 0;
            var input = elems;
            for (var i = 0; i < input.length; i++) {
                if (input[i].name == name) {
                    results.push(input[i]);
                    if (input[i].checked) 
                        results.numChecked++;
                }
            }
            return results;
        },
        addEvent: function(el, type, func){
            if (window.attachEvent) {
                el.attachEvent("on" + type, func);
            }
            else {
                el.addEventListener(type, func, false);
            }
        }
    };
    var mds = Validator.methods = {
        required: function(elem){
            if (elem.type == "chechbox" || elem.type == "radio") 
                return fn.getInputsByName(elem.name).numChecked;
            else 
                return elem.value.length > 0 && elem.value != elem.defaultValue;
        },
        email: function(elem){
            return !elem.value || /^[a-z0-9_+.-]+\@([a-z0-9-]+\.)+[a-z0-9]{2,4}$/i.test(elem.value);
        },
        url: function(elem){
            return elem.value == '' || !elem.value == 'http://' || /^https?:\/\/([a-z0-9-]+\.)+[a-z0-9]{2,4}.*$/.test(elem.value);
        },
        date: function(elem){
            return !elem.value || /^\d{2}\/\d{2}\/\d{2,4}$/.test(elem.value);
        }
    };
    var msgs = Validator.messages = {
        "required": "This field is required.",
        "email": "Not a valid email address.",
        "phone": "Not a valid phone number.",
        "date": "Not a valid date.",
        "url": "Not a valid URL.",
        "code": "Not a valid code."
    };
    fn.init();
};

