var aRulesPattern = {
            'login'   : {'pattern' : '[\\w\\d_\\-\.]+', 'minlen' : 6, 'maxlen' : 16},
            'email'   : {'pattern' : '[\\w\\d_\.\\-]+@[\\w\\d\.\\-]{2,}\.[\\w]{2,}', 'minlen' : 7, 'maxlen' : 127},
            'multiple_email' : {'pattern' : '[\\w\\d_\.\\-]+@[\\w\\d\.\\-]{2,}\.[\\w]{2,}([\\s]{0,}(,|;)[\\s]{0,}[\\w\\d_\\.\\-]+@[\\w\\d\\.\\-]{2,}\.[\\w]{2,})*'},
            'text'    : {'minlen'  : 1, 'maxlen' : 250},
            'name'    : {'pattern' : '[\\w\\d\-\'\\`\\s\.\\,]+', 'minlen' : 1, 'maxlen' : 100},
            'address' : {'pattern' : '[\\w\\d\-\'\\`\\s\.\\,]+', 'minlen' : 1, 'maxlen' : 100},
            'city'    : {'pattern' : '[\\w\\d\-\'\\`\\s\.\\,]+', 'minlen' : 1, 'maxlen' : 100},
            'city_ru' : {'pattern' : '[\\w\\d\-\\s\'�-��-я]+', 'minlen' : 1, 'maxlen' : 100},
            'zip'     : {'pattern' : '[\\w\\d\\s\-]{10}'},
            'zip_us'  : {'pattern' : '^(([\\d]{5})|([\\d]{5}-[\\d]{4}))'},
            'zip_canada' : {'pattern' : '[\\d]{6}'},
            'phone'   : {'pattern' : '[\\w\\d\-\\(\\)\.\\+]+', 'minlen' : 7, 'maxlen' : 50},
            'phone_us': {'pattern' : '^(([2-9][\\d]{2}-[\\d]{3}-[\\d]{4})|(\\([2-9][\\d]{2}\\)[\\s]{0,1}[\\d]{3}-[\\d]{4}))'},
            'phone_extension' : {'pattern' : '[\\w\\d]{5}'},
            'phone_ru': {'pattern' : '[\\d\\-\\(\\)\.\\+]+', 'minlen' : 5, 'maxlen' : 50},
            'password': {'minlen'  : 6, 'maxlen' : 12},
            'money'   : {'pattern' : '[\\d]+(\.[\\d]{2}){0,1}', 'maxlen' : 10},
            'money_us': {'pattern' : '[\\$]{0,1}(([\\d]+\,[\\d]{3})|([\\d]+))(\.[\\d]{2}){0,1}', 'maxlen' : 10},
            'domain'  : {'pattern' : '^((ftp|http|https):\/\/){0,1}([\\w\\d\-]+\.){0,1}[\\w\\d\-]{2,}\.[\\w]{2,}[\/]{0,1}', 'maxlen' : 255},
            'url'     : {'pattern' : '^((ftp|http|https):\/\/){0,1}([\\w\\d\-\.]+\.){0,1}[\\w\\d\-]{2,}\.[\\w]{2,}(\/.*){0,1}', 'maxlen' : 255},
            'ccard'   : {'pattern' : '[\\d]{12,18}'},
            'cc_visa' : {'pattern' : '^4([\\d]{11}|[\\d]{14})'},
            'cc_master_card' : {'pattern' : '^5[1-5][\\d]{12}'},
            'cc_amex' : {'pattern' : '^3[47][\\d]{11}'},
            'cc_discover' : {'pattern' : '^6011[\\d]{8}'},
            'cc_diners_club' : {'pattern' : '^3(0[0-5]|[68])[\\d]{11}'},
            'cc_jcb'  : {'pattern' : '^(3[\\d]{4}|2131|1800)[\\d]{11}'},
            'cc_enroute' : {'pattern' : '^2(014|149)[\\d]{11}'},
            'cvv'     : {'pattern' : '[\\d]{3,4}'},
            'ssn'     : {'pattern' : '^([\\d]{2,3}-[\\d]{2}(-|\\d)[\\d]{4})'},
            'integer' : {'pattern' : '[\\d]+'},
            'float'   : {'pattern' : '[\\d]+\.[\\d]+'},
            'float_negative' : {'pattern' : '^\-[\\d]+\.[\\d]+'},
            'ip'      : {'pattern' : '[0-2][0-5]{0,1}[0-5]{0,1}\.[0-2][0-5]{0,1}[0-5]{0,1}\.[0-2][0-5]{0,1}[0-5]{0,1}\.[0-2][0-5]{0,1}[0-5]{0,1}'},
            'date_us' : {'pattern' : '^(0[1-9]|1[12])\/(0[1-9]|[12][\\d]|3[01])\/(([\\d]{2})|([12][\\d]{3}))'},
            'short_date_us' : {'pattern' : '^(0[1-9]|1[12])\/(([\\d]{2})|([12][\\d]{3}))'},
            'date_ru' : {'pattern' : '^(0[1-9]|[12]\\d|3[01])\.(0[1-9]|1[12])\.(([\\d]{2})|([12][\\d]{3}))'},
            'short_date_ru' : {'pattern' : '^(0[1-9]|[12]\\d|3[01])\.(0[1-9]|1[12])\.[\\d]{2}'},
            'time'    : {'pattern' : '^([01]\\d|2[0-3]):[0-5]\\d'},
            'time_us' : {'pattern' : '^((([01]\\d|2[0-3]):[0-5]\\d)|((0[1-9]|1[0-2]):[0-5]\\d[\s]{0,1}([aA]|[pP])[mM]))'},
            'required': {'minlen'  : 1}
            };

function printf(fstring)
{
    var pad = function(str,ch,len)
    {
        var ps = '';
        for(var i = 0; i <Math.abs(len); i++ ) ps += ch;
        return len > 0 ? str + ps : ps + str;
    }
    var processFlags = function(flags,width,rs,arg)
    {
        var pn = function(flags,arg,rs)
                 {
                    if(arg>=0)
                    {
                        if(flags.indexOf(' ')>=0) rs = ' ' + rs;
                        else if(flags.indexOf('+')>=0) rs = '+' + rs;
                    }
                    else
                        rs = '-' + rs;
                    return rs;
                 }
        var iWidth = parseInt(width,10);
        if(width.charAt(0) == '0')
        {
            var ec=0;
            if(flags.indexOf(' ')>=0 || flags.indexOf('+')>=0) ec++;
            if(rs.length<(iWidth-ec)) rs = pad(rs,'0',rs.length-(iWidth-ec));
            return pn(flags,arg,rs);
        }
        rs = pn(flags,arg,rs);
        if(rs.length<iWidth)
        {
            if(flags.indexOf('-')<0) rs = pad(rs,' ',rs.length-iWidth);
            else rs = pad(rs,' ',iWidth - rs.length);
        }
        return rs;
    }
    var converters = new Array();
    converters['d'] = function(flags,width,precision,arg)
    {
        return converters['i'](flags,width,precision,arg);
    }
    converters['s'] = function(flags,width,precision,arg)
    {
        var iPrecision=parseInt(precision);
        var rs = arg;
        if(rs.length > iPrecision) rs = rs.substring(0,iPrecision);
        return processFlags(flags,width,rs,0);
    }
    converters['i'] =  function(flags,width,precision,arg)
    {
        var iPrecision=parseInt(precision);
        var rs = ((Math.abs(arg)).toString().split('.'))[0];
        if(rs.length<iPrecision) rs=pad(rs,' ',iPrecision - rs.length);
        return processFlags(flags,width,rs,arg);
    }
    farr = fstring.split('%');
    retstr = farr[0];
    fpRE = /^([-+ #]*)(\d*)\.?(\d*)([cdieEfFgGosuxX])(.*)$/;
    for(var i = 1; i < farr.length; i++)
    {
        fps = fpRE.exec(farr[i]);
        if(!fps) continue;
        if(arguments[i] != null) retstr += converters[fps[4]](fps[1], fps[2], fps[3], arguments[i]);
        retstr += fps[5];
    }
    return retstr;
}

/** Format text message for output
* @param string sKey - key of message
* @param string aParam - params to substitute
* @return  string formatted message.
*/
function validator_formatMes(sKey, aParam)
{
    var sStr = "printf('" + aValidatorMes[sKey] + "'";
    for(m = 0; m < aParam.length; m ++) sStr += ", '" + aParam[m] + "'";
    sStr += ")";
    return eval(sStr);
}

/** Gets field value, depending on field type
* @param object oField field
* @return string value of field
* @todo add support for dates
*/
function validator_getValue(oField)
{
    var sVal = oField.value;
    if (oField.type.toLowerCase() == 'checkbox' && !oField.checked) sVal = '';
    return sVal;
}

/** Checks is form valid
* @param object oForm  - form to validate
* @param array aSchema  - array of validation shemas (hash) related to certain field
* @param array aRules  - array of validation rules bentween 2 fields
* @param string sDiv  - name of html DIV element for errors output
* @return  boolean  true if all is ok or false + alert wiht error messages if form contains errors.
*/
function validator_isValid(oForm, aSchema, aRules, sDiv)
{
    // errors
    var aErr  = [];
    var oFld  = null; // first field with error

    // for each fields
    for (var i = 0; i < aSchema.length; ++i)
    {
        var oSchema = aSchema[i];
        var sVal = validator_getValue(oForm.elements[oSchema.field]);

        //optional param
        if (typeof(oSchema.mandatory) != 'undefined' && !oSchema.mandatory && !sVal.length) continue;
        sMes = typeof(oSchema.message) != 'undefined' ? oSchema.message : '';
        for (var a = 0; a < oSchema.rules.length; ++a)
        {
            var aRule = aRulesPattern[oSchema.rules[a]];
            var aTmpErr = [];
            // validation
            if (typeof(aRule.minlen) != 'undefined' && aRule.minlen > sVal.length)
                aTmpErr[aTmpErr.length] = validator_formatMes('minlen', [oSchema.title, aRule.minlen]);
            if (typeof(aRule.maxlen) != 'undefined' &&  aRule.maxlen < sVal.length)
                aTmpErr[aTmpErr.length] = validator_formatMes('maxlen', [oSchema.title, aRule.maxlen]);
            if (typeof(aRule.pattern) != 'undefined' && sVal.search(aRule.pattern) == -1)
                aTmpErr[aTmpErr.length] = validator_formatMes('pattern', [oSchema.title]);
            if (typeof(aRule.min) != 'undefined' && aRule.min > sVal)
                aTmpErr[aTmpErr.length] = validator_formatMes('min', [oSchema.title, aRule.min]);
            if (typeof(aRule.max) != 'undefined' && aRule.max < sVal)
                aTmpErr[aTmpErr.length] = validator_formatMes('max', [oSchema.title, aRule.max]);
            if (typeof(aRule.mineq) != 'undefined' && aRule.mineq >= sVal)
                aTmpErr[aTmpErr.length] = validator_formatMes('mineq', [oSchema.title, aRule.mineq]);
            if (typeof(aRule.maxeq) != 'undefined' && oSchema.maxeq < sVal)
                aTmpErr[aTmpErr.length] = validator_formatMes('maxeq', [aRule.title, aRule.maxeq]);

            // store error messages for field
            if (sMes && aTmpErr.length) // if given custom error message store only it
                aErr[aErr.length] = sMes;
            else // store all errors
                aErr = aErr.concat(aTmpErr);

            // if first error, try to set focus
            if (aErr.length && oFld == null)
                oFld = oForm.elements[oSchema.field];
        }
    }// end loop by single elements

    // make output
    if (aErr.length)
    {
        var sOut = '';
        var oDiv = document.getElementById(sDiv);
        if (oDiv){
            for (i=0; i<aErr.length; ++i) sOut += '<b style="color:red">'+aErr[i]+'</b><br>';
            oDiv.innerHTML = sOut;
        }
        else
            alert(aErr.join("\n"));
        if (oFld) oFld.focus();
        return false;
    }
    return true;
}
