var FormValidator = function( obj, printErrorExternal )
{
    this.FORM = null;
    this.printErrorExternal = printErrorExternal;

    this.LANG = lang;
    this.NEEDSUBMIT = true;
    this.RESULT = null;
	this.TEMPLATE = null;
    this.TARGET = 'alert';

    this.ERRORS = [];
    this.ERROR_TYPES =
    {
        EMPTY: 0,       // не может быть пустым
        MAXLEN: 1, 	    // максимальное значене
        MINLEN: 2, 	    // минимальное значение
        INVALID: 3,	    // заполнено некорректно
        ATLEAST: 4,     // должно быть выбрано больше n значений
        INSET: 5,       // должно быть выбрано n значений
        PWD: 6,         // разные пароли
        BAD_DAY: 7,     // не правильный день
        BAD_MONTH: 8,   // не правильный месяц
        BAD_YEAR: 9, 	// не правильный год
        BETWEEN: 10,    // значение в пределах от n до m
        LESS: 11,       // должно быть выбрано меньще чем n значений
        MAXSIZE: 12,    // максимальным количеством символов в поле
        MINSIZE: 13,    // минимальное количеством символов в поле
        BEETWEENSIZE: 14, // символов в пределах от n до m
        SIZE: 15,       // точное количество символов
        SIZENUMBER: 16, // значение должно быть равно
        BETWEENSEL: 17  // должно быть выбрано от n до m значений
    };
    this.ERROR_MSG_TEMPLATES = { rus: null, eng: null };
    this.ERROR_MSG_TEMPLATES.rus =
    [
        'Поле "--fieldDesc--" не может быть пустым\n',
        'Значение поля "--fieldDesc--" должно быть меньше --fieldLen--\n',
        'Значение поля "--fieldDesc--" должно быть больше --fieldLen--\n',
        'Поле "--fieldDesc--" заполнено некорректно\n',
        'Выберите хотя бы одну ленту для подписки\n',
        'Количество выбранных значений в разделе "--fieldDesc--" должно быть "--fieldLen--"\n',
        'Введенные пароли не совпадают, попробуйте еще раз\n',
        'В поле "--fieldDesc--" значение дня установлено не верно\n',
        'В поле "--fieldDesc--" значение месяца установлено не верно\n',
        'В поле "--fieldDesc--" значение года установлено не верно\n',
        'Значение поля "--fieldDesc--" должно находится в пределах --fieldLen--\n',
        'Количество выбранных значений в разделе "--fieldDesc--" должно быть меньше --fieldLen--\n',
        'Количество символов в поле "--fieldDesc--" не может превышать --fieldLen--\n',
        'Количество символов в поле "--fieldDesc--" должно превышать --fieldLen--\n',
        'Количество символов в поле "--fieldDesc--" должно находится в пределах --fieldLen--\n',
        'Количество символов в поле "--fieldDesc--" должно быть ровно --fieldLen--\n',
        'Значение поля "--fieldDesc--" должно быть --fieldLen--\n',
        'Количество выбранных значений в разделе "--fieldDesc--" должно находится в пределах --fieldLen--\n',
    ];
    this.ERROR_MSG_TEMPLATES.eng =
    [
        '"--fieldDesc--" cannot be empty\n',
        'Value of "--fieldDesc--" cannot be greater than --fieldLen--\n',
        'Value of "--fieldDesc--" cannot be greater than --fieldLen--\n',
        '"--fieldDesc--" is invalid\n',
        'Choose at least one subscription line\n',
        '"--fieldDesc--" section must have 1 to --fieldLen-- values selected\n',
        'The password and it’s confirmation are not identical\n',
        '"--fieldDesc--" contains incorrect day value\n',
        '"--fieldDesc--" contains incorrect month value\n',
        '"--fieldDesc--" contains incorrect year value\n',
        'Value of "--fieldDesc--" must be between --fieldLen-- letters\n',
        '"--fieldDesc--" section must have less than --fieldLen-- value selected\n',
        '"--fieldDesc--" is invalid\n',
        '"--fieldDesc--" is invalid\n',
        '"--fieldDesc--" is invalid\n',
        '"--fieldDesc--" is invalid\n',
        '"--fieldDesc--" is invalid\n',
        '"--fieldDesc--" is invalid\n'
    ];

    if ( obj )
    {
        if ( obj.nodeName && obj.nodeName.toUpperCase() == 'FORM' )
        {
            this.FORM = obj;
        }
        else if ( obj.form )
        {
            this.FORM = obj.form;
        }
        else if ( typeof( obj ) == "string" || typeof( obj ) == "number" )
        {
            this.FORM = document.forms[ obj ];
        }

        if ( !this.FORM )
        {
            return false;
        }
    }

/*
    try
    {
        if ( window.event )
        {
            window.event.returnValue = false;
        }
        else
        {
            this.FORM.onsubmit = function( e ) { return false; }
            // Comment: works only in Opera and NN6
            //this.FORM.addEventListener( "submit", function( e ) { e.PreventDefault(); return false; }, false );
        }
    }
    catch( e ) {}
*/

    this.processSettings( 'LANG' );
    this.processSettings( 'NEEDSUBMIT' );
    this.processSettings( 'RESULT' );
    this.processSettings( 'TARGET' ); // valuest: html || alert
    this.processSettings( 'TEMPLATE' );

    for ( var i = 0; i < this.FORM.elements.length; i++ )
	{
        var elem = this.FORM.elements[ i ];
        var attr = this.getAttributeByName( elem, 'FORMAT');
		if ( attr )
		{
			this.validate( elem, new FormItemFormatParser( attr ) );
		}
	}

    if ( this.ERRORS.length > 0 )
    {
        if ( this.RESULT || this.TARGET == 'html' )
        {
            this.clearPreviousErrrors();
            this.printErrors();
        }
        else
        {
            alert( this.getMessage() );
        }
    }
    else if ( this.NEEDSUBMIT )
    {
        this.FORM.submit();
    }
}

FormValidator.prototype.clearPreviousErrrors = function()
{
    for ( var i = 0; i < this.FORM.elements.length; i++ )
	{
        var elem = this.FORM.elements[ i ];
        var attr = this.getAttributeByName( elem, 'FORMAT');
		if ( attr )
		{
            try
            {
                document.getElementById( elem.name + this.RESULT ).innerHTML = '';
            }
            catch ( e ) { continue; }
        }
	}
}

FormValidator.prototype.getMessage = function()
{
    var message = '';
    for ( var i = 0; i < this.ERRORS.length; i++ )
    {
        message += this.ERRORS[i].getMessage();
    }
    return message;
}

FormValidator.prototype.printErrors = function()
{
    if ( this.printErrorExternal )
    {
        this.printErrorExternal.call( this, this.ERRORS );
    }
    else
    {
        for ( var i = 0; i < this.ERRORS.length; i++ )
        {
            var error = this.ERRORS[i];
            error.print();
        }
    }
}

FormValidator.prototype.processSettings = function( name )
{
    var attr = this.getAttributeByName( this.FORM, name );
    if( attr )
	{
		this[ name ] = attr;
	}
}

FormValidator.prototype.validate = function( element, format )
{
    switch ( element.type.toUpperCase() )
    {
        case 'RADIO':
            if ( countChecked.call( this ) == 0 )
            {
                format.size = 1;
                setError.call( this, this.ERROR_TYPES.ATLEAST );
            }
            break;
        case 'PASSWORD':
            var depend = this.getAttributeByName( element, 'DEPEND' );
            if ( depend && !isEmpty() )
            {
                var dependElem = this.FORM.elements[ depend ];
                if ( dependElem && dependElem.value != element.value )
                {
                    setError.call( this, this.ERROR_TYPES.PWD );
                }
            }
            else if ( isEmpty() )
            {
                setError.call( this, this.ERROR_TYPES.EMPTY );
            }
            else if ( format.size )
            {
                var sizeError = checkSize( element.value );
                if ( sizeError > 0 )
                {
                    setStringSizeError.call( this, sizeError );
                }
            }
            break;
        default:
            if ( format.required && isEmpty() )
            {
                setError.call( this, this.ERROR_TYPES.EMPTY );
                break;
            }
            else if ( format.type )
            {
                var check = checkType();
                if ( format.type == 'date' )
                {
                    setDateSizeError.call( this, check );
					break;
                }
                else if ( !check )
                {
                    setError.call( this, this.ERROR_TYPES.INVALID );
					break;
                }
            }

            var sizeError = checkSize.call( this, element.value );
            if ( sizeError > 0 )
            {
                if ( element.nodeName == 'SELECT' || element.type.toUpperCase() == 'CHECKBOX' )
                {
                    setSizeError.call( this, sizeError );
                }
                else if ( format.type == 'number' )
                {
                    setNumberSizeError.call( this, sizeError );
                }
                else
                {
                    setStringSizeError.call( this, sizeError );
                }
            }
            break;
    }

    /*  VALIDATION FUNCTIONS
    ***************************************/
    function setError( errorNumber )
    {
        var error = new FormError( element, this.ERROR_MSG_TEMPLATES[ this.LANG ][ errorNumber ], errorNumber, format, this.RESULT, this.TEMPLATE );
        this.ERRORS.push( error );
    }

    function isEmpty()
    {
        return ( !element.value.match( /.+/ ) );
    }

    function checkType()
    {
        var value = element.value.toString();
        if( isEmpty() ) return true;

        var re = '';
        switch( format.type )
        {
            case 'email':
                re = /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/;
                return value.match( re );
                break;
            case 'phone':
                re = /^(\\+\\d{1,3}\\x20?)?(\\d{3,4}\\x20?|\\(\\d{3,4}\\)\\x20?)?\\d{2,3}\\-?\\d\\d\\-?\\d\\d(\\s[\\d\\s\(\)]*)?$/;
                return value.match( re );
                break;
            case 'number':
                re = /^[-]?\d*\.?\d*$/;
                return value.match( re );
                break;
            case 'int':
                re = /^[-]?\d$/;
                return value.match( re );
                break;
            case 'date':
                return isValidDate( value );
                break;
        }
        return true;
    }

    function checkSize( value )
    {
        var compare = ( format.type == 'number' ) ? parseInt( value ) : ( '' + value ).length;
        if ( element.nodeName == 'SELECT' )
        {
            compare = countSelected();
        }
        else if ( element.type.toUpperCase() == 'CHECKBOX' )
        {
            compare = countChecked.call( this );
            if ( typeof( format.size ) == 'undefined' && compare == 0  ) return 5;
        }
        if ( typeof( format.size ) == 'undefined' ) return -1;
        var beetwin = format.size.toString().split( '-' );
        if ( beetwin.length > 1 && beetwin[0] != '' )
		{
            var from = parseInt( beetwin[0] );
			var to = parseInt( beetwin[1] );
            if ( ( from == to && compare != from ) || ( ( compare < from || compare > to ) && from != to ) )
            {
                return ( from == to ) ? 4 : 3;
            }
		}
        else if ( format.size < 0 && compare > format.size * -1 )
		{
			return 1;
		}
        else if ( format.size > 0 && compare < format.size )
		{
			return 2;
		}

        return -1;
    }

    function setSizeError( errorNumber )
    {
        switch ( errorNumber )
        {
            case 1:
                setError.call( this, this.ERROR_TYPES.LESS );
                break;
            case 2:
                setError.call( this, this.ERROR_TYPES.ATLEAST );
                break;
            case 3:
                setError.call( this, this.ERROR_TYPES.BETWEENSEL );
                break;
            case 4:
                setError.call( this, this.ERROR_TYPES.INSET );
                break;
            case 5:
                setError.call( this, this.ERROR_TYPES.EMPTY );
                break;
        }
    }

    function setNumberSizeError( errorNumber )
    {
        switch ( errorNumber )
        {
            case 1:
                setError.call( this, this.ERROR_TYPES.MAXLEN );
                break;
            case 2:
                setError.call( this, this.ERROR_TYPES.MINLEN );
                break;
            case 3:
                setError.call( this, this.ERROR_TYPES.BETWEEN );
                break;
            case 4:
                setError.call( this, this.ERROR_TYPES.SIZENUMBER );
                break;
        }
    }

    function setStringSizeError( errorNumber )
    {
        switch ( errorNumber )
        {
            case 1:
                setError.call( this, this.ERROR_TYPES.MAXSIZE );
                break;
            case 2:
                setError.call( this, this.ERROR_TYPES.MINSIZE );
                break;
            case 3:
                setError.call( this, this.ERROR_TYPES.BEETWEENSIZE );
                break;
            case 4:
                setError.call( this, this.ERROR_TYPES.SIZE );
                break;
        }
    }

     function setDateSizeError( errorNumber )
     {
        switch ( errorNumber )
        {
            case 1:
                setError.call( this, this.ERROR_TYPES.INVALID );
                break;
            case 2:
                setError.call( this, this.ERROR_TYPES.BAD_DAY );
                break;
            case 3:
                setError.call( this, this.ERROR_TYPES.BAD_MONTH );
                break;
            case 4:
                setError.call( this, this.ERROR_TYPES.BAD_YEAR );
                break;
        }
    }

    function countSelected()
    {
        var count = 0;
        for( var i = 0; i < element.options.length; i++ )
        {
            if( element.options[i].selected ) count++;
        }
        return count;
    }

    function countChecked()
    {
        var count = 0;
        for( var i = 0; i < this.FORM[ element.name ].length; i++ )
        {
            if( this.FORM[ element.name ][i].checked ) count++;
        }
        return count;
    }

    function comparePasswords()
    {
        var password = '';
        var password2 = '';
        for( var i = 0; i < this.FORM.elements.length; i++ )
        {
            var elem = this.FORM.elements[i];
            if ( elem.name.toLowerCase().indexOf( 'password' ) != -1)
            {
                password2 = ( password == '' ) ? '' : elem.value;
                password = ( password == '' ) ? elem.value : password;
            }
            if ( password2 != '' && password != '' )
            {
                return ( password2 == password );
            }
        }
        return true;
    }

    function isValidDate( str )
    {
        // формат даты: DD.MM.YYYY || DD-MM-YYYY || DD/MM/YYYY
        if ( str.search(/[0-9]{2}(\.|\/|\-)[0-9]{2}(\.|\/|\-)[0-9]{4}/g) == -1 ) return 1; // 'Hеправильный формат'

        var delim = str.substr( str.length - 5 ).slice(0,-4);
        var strArr = str.split( delim );

        if( strArr.length != 3 )
        {
            return 1;
        }

        var dd = strArr[0];
        var mm = strArr[1];
        var yy = strArr[2];

        if ( dd.substr(0, 1) == '0' ) dd = dd.substr(1);
        if ( mm.substr(0, 1) == '0' ) mm = mm.substr(1);

        if ( parseInt(dd) > 31 || parseInt(dd) < 1) return 2;
        else if ( parseInt( mm ) > 12 || parseInt( mm ) < 1) return 3;
        else if( delim != '.' && delim != '-' && delim != '/' ) return 4;

        var time = Date.parse( mm + '/' + dd + '/' + yy );
        var etalonDate = new Date( time );

        if(parseInt( dd ) != etalonDate.getDate() ) return 2; // Неправильная дата
        else if( parseInt( mm ) != etalonDate.getMonth() + 1 ) return 3; // Неправильный месяц
        else if ( parseInt( yy ) != etalonDate.getFullYear() || parseInt( yy ) > 9999 ) return 4; // Неправильный год

        return 0;
    }
    /*  VALIDATION FUNCTIONS
    ***************************************/
}

FormValidator.prototype.getAttributeByName = function( elem, name )
{
    name = name.toUpperCase();
	try
	{
		var value = elem.getAttribute( name );
        return ( ( value ) ? value : null );
	}
	catch(e)
	{
		return null;
	}
	return null;
}

var FormItemFormatParser = function( /*String*/ str )
{
    var values = str.split( '::' );
    var format = { type:'', name:'', required:false, size:'' };
    try
    {
        format.type = values[0];
        format.name = values[1];
        format.required = ( values[2] == 'opt' ) ? false : true;
        if( values[2] != 'opt' && values[2] != 'req' )
        {
            format.size = values[2];
        }
        else
        {
            format.size = values[3];
        }
    }
    catch(e)
    {
        return format;
    }
    return format;
}


var FormError = function( element, msg, errorNumber, format, prefix, template )
{
    this.field = element;  /* Object | Link on form element*/
    this.message = msg;  /* String | Text of error message */
    this.caption = format.name;  /* String | Visible name of form element */
    this.size = format.size;   /* Number | Size of field length or value (if type of field is number)*/
    this.type = errorNumber;   /* Number | Type of error */
    this.target = null;

    this.prefix = prefix;
    this.template = template;
}

FormError.prototype.getMessage = function()
{
    var size = this.size;
    if ( this.type == 1 || this.type == 12 || this.type == 11 )
    {
        size *= -1;
    }
    if ( this.type == 15 || this.type == 16 || this.type == 5 )
    {
        size = size.split('-')[0];
    }
    return this.message.replace( '--fieldDesc--', this.caption ).replace( '--fieldLen--', size );
}

FormError.prototype.print = function() // args = {prefix:'', template:''};
{
    this.target = document.getElementById( this.field.name + ( ( this.prefix ) ? this.prefix : '' ) );
    if ( !this.target ) return;

    if( this.template )
    {
        this.template = this.template.split('::');
        if ( this.template.length == 3 )
        {
            this.message = this.template[0].replace( 'text', this.message );
            this.caption = this.template[1].replace( 'name', this.caption );
            this.size = this.template[2].replace( 'size', this.size );
        }
    }
    else
    {
        this.caption = this.caption.bold();
    }

    this.target.innerHTML = this.getMessage();
}