//Формат: FIELDID::FIELD NAME::TYPE::REQ/NREQ;;
//Пример: fldname::Имя::string::req;;
var char_lat_lo = 'abcdefghijklmnopqrstuvwxyz';
var char_lat_hi = char_lat_lo.toUpperCase();
var char_lat = char_lat_lo + char_lat_hi;
var char_rus_lo = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя';
var char_rus_hi = char_rus_lo.toUpperCase();
var char_rus = char_rus_lo + char_rus_hi;
var char_digits = '1234567890';
var char_quotes = '"\'`';
var char_spaces = ' \t\n\r';
var char_signs_basic = '+-*/.,=%()';
var char_signs = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~';
var char_names = char_lat + char_rus + char_spaces + '_\'&.,';
var char_email = char_lat + char_digits + '@._-';
var char_phone = char_digits + '()[] +-';
var char_number = char_digits + '+- .,eE';
var char_natural_number = char_digits + '+';
var char_integer = char_digits + '+- ';
var char_time	= char_digits + ':';
var char_url = char_lat + char_digits + '._-';

var _check_err_empty = 0, _check_err_maxlen = 1, _check_err_invalid = 2;
var _check_err_atleast = 3, _check_err_inset = 4, _check_err_minlen = 5, _check_err_eqlen = 6;

function validateChars(str, chars)
{	var n;
	for(n = 0; n < str.length; n++)
		if(chars.indexOf(str.charAt(n)) < 0) return false;
	return true;
}

function trimString(ss)
{
	if(!ss) return ss;
	var n, spac = " \t\n\r";
	ss = ss.toString();
	for(n = 0; n < ss.length; n++)
		if(spac.indexOf(ss.charAt(n)) < 0) break;
	ss = ss.substring(n, ss.length);
	for(n = ss.length - 1; n >= 0; n--)
		if(spac.indexOf(ss.charAt(n)) < 0) break;
	ss = ss.substring(0, n + 1);
	return ss;
}


function DataChecker(sLang)
{
	this.err_count = 0;
	this.fld_count = 0;
	this.form = null;
	this.err = new Array();
	this.fld = new Array();
	this.lang = sLang;
	this.dfmt = 'mm/dd/yy';
	this.err_templates = null;
	
	this.bild = FormChecker_Bild;
	this.add = FormChecker_Add;
	this.check = FormChecker_Check;
	this.message = FormChecker_Message;
	this.element = FormChecker_Element;
	this.field = FormChecker_Field;
	
	{
		if( sLang ) this.lang = sLang.toLowerCase();
		
		if( this.lang == "ru" )
		{
			this.err_templates = new Array(
				'Поле "--fieldDesc--" не может быть пустым',
				'Максимальным количеством символов в поле "--fieldDesc--" является --fieldMax--',
				'Поле "--fieldDesc--" заполнено некорректно',
				'В разделе "--fieldDesc--" должно быть выбрано хотя бы одно значение',
				'В разделе "--fieldDesc--" должно быть выбрано от 1 до --fieldLen-- значений',
				'Минимальным количеством символов в поле "--fieldDesc--" является --fieldMin--',
				'Количество символов в поле "--fieldDesc--" должно быть равно --fieldMax--'
			);
			
			this.dfmt = 'dd.mm.yy';
		}
		else {
			this.err_templates = new Array(
				'"--fieldDesc--" cannot be empty',
				'Maximum length of "--fieldDesc--" cannot be greater than --fieldMax--',
				'--fieldDesc-- is invalid',
				'"--fieldDesc--" section must have at least 1 value selected',
				'"--fieldDesc--" section must have 1 to --fieldLen-- values selected',
				'Minimum length of "--fieldDesc--" cannot be less than --fieldMin--',
				'The amount of characters in a field "--fieldDesc--" should be equal --fieldMin--'
			);
		
			//this.dfmt = 'mm/dd/yy';
			this.dfmt = 'dd.mm.yyyy';
		}
	
	}
}

function DataChecker_Error(errfld,errtxt)
{
	this.fld = errfld;  // поле в котором возникла ошибка
	this.text = errtxt; // текст сообщения об ошибке
}

function FormChecker_Add(cfg_str)
{
	this.fld[this.fld_count++] = new DataChecker_Field(this,cfg_str);
}

function FormChecker_Bild(form_obj, cfg_line)
{
	if(cfg_line.substr(cfg_line.length-2,2)==';;')
		cfg_line=cfg_line.substr(0,cfg_line.length-2);

	if( !form_obj ) return false;
	if(typeof(form_obj) == 'string') this.form = eval("document." + form_obj);
	else this.form = form_obj
	if(!cfg_line) return false;
	
	var m;
	var cfg = cfg_line.split(';;');
	for(m = 0; m < cfg.length; m++)	this.add(cfg[m]);

	return true;
}

function FormChecker_Check()
{
	var n;
	for(n = 0; n < this.fld_count; n++)
		if( this.fld[n].isused )
				this.fld[n].check();
		
	return (this.err_count == 0);
}

function FormChecker_Message(msg_delimeter)
{
	var n, ret = "";
	if(!msg_delimeter) msg_delimeter = "\n";
	for(n = 0; n < this.err_count; n++)
	{
		if( n > 0 ) ret += msg_delimeter;
		ret +=  this.err[n].text;
	}
	return ret;
}

function FormChecker_Element(index)
{
	if(!index) index = 0;
	if(index < this.err_count)
	{
		var obj = this.form.elements[this.err[index].fld.name];
		if(obj.type) return obj; 
		else return obj[0];
	}
	return null;
}

function FormChecker_Field(index)
{
	var m;
	if( typeof(index) == 'string' )
	{
		for( m=0; m < this.fld_count; m++ )
			if( this.fld[m].name == index)
				return this.fld[m];
	}
	else if( index < this.fld_count)
	{
		return this.fld[index];
	}
	return null;
}


function DataChecker_Field(oParent,sDetail)
{
	this.name;	// имя поля на форме
	this.desc;	// название поля
	this.attrs; // тип поля, регулярка, функция
	this.req= false;	// необходимость заполнения
	this.isused= true;  // проводить проверку
	this.maxlen = Number.MAX_VALUE; // максимальная длинна текста
	this.minlen = 0; // минимальная длинна
	this.focus = null; // поле на которое устанавливается фокус
	this.type = null; // тип поля
	this.value = null; // значение поля после проверки
	this.collection = null; // коллекция полей формы
	this.element = null; // поле формы
	this.fld_set = null; // случай набора полей
	this.parent = oParent; // ссылка на DataChecker

	this.check = FieldChecker_Check;
	this.error = FieldChecker_Error;
	
	{
		var cfg_detail = sDetail.split("::");
		this.name = cfg_detail[0];
        this.fld_set = this.name.split(",");
		this.desc = cfg_detail[1];
		this.attrs = cfg_detail[2];
		this.req = (cfg_detail[3] == 'req'); 
		
		if( cfg_detail[4] )
		{
			var cfg_size = cfg_detail[4].split(",");
			if( cfg_size.length > 1 )
			{
				this.minlen = parseInt(cfg_size[0]);
				this.maxlen = parseInt(cfg_size[1]);
			}
			else if( cfg_size.length == 1 )
			{
				this.maxlen = parseInt(cfg_size[0]);
			}
		}
	}
}


function FieldChecker_Error(errType)
{
	var element = document.getElementById( this.name );
	if( element.className == 'text' || element.className == 'textChanged' )
		element.className = 'textError';
		
	if( arguments.length == 0 ) errType = _check_err_invalid;
	var errtxt = ( typeof(errType) == 'string' ) ? errType : new String(this.parent.err_templates[errType]);
	errtxt = errtxt.replace('--fieldDesc--', this.desc);
	errtxt = errtxt.replace('--fieldMax--', this.maxlen);
	errtxt = errtxt.replace('--fieldMin--', this.minlen);
	this.parent.err[this.parent.err_count++] = new DataChecker_Error(this,errtxt);		
}

function FieldChecker_Check()	
{
	var m;	
	var char_test;
	var form_obj = this.parent.form;
    if(!( this.fld_set.length-1))
	{
		if(!form_obj.elements[this.name])
		{ 
			//this.error('INTERNAL ERROR. There is no field "' + this.name + '"'); 
			return;
		}
		if(form_obj.elements[this.name].type)
		{
			this.element = form_obj.elements[this.name];
			this.focus = this.element;
			this.type = this.element.type;
		}
		else
		{
	        this.collection = form_obj.elements[this.name];
			this.focus = this.collection[0];
			this.type = this.focus.type;
		}
		
		// Чтение данных
	    this.value = ''; 
		switch(this.type)
		{
			case 'radio': 
	            if(this.collection)
					for (oo = 0; oo < this.collection.length; oo++)
					{
						if(this.collection[oo].checked) this.value += this.collection[oo].value + ', ';
					}
					else
						if(this.element.checked) this.value += this.element.value + ', ';
				break;

			case 'checkbox':
				if(this.collection)
					for (oo = 0; oo < this.collection.length; oo++)
					{
						if(this.collection[oo].checked) this.value += this.collection[oo].value + ', ';
					}
				else
					if(this.element.checked) this.value += this.element.value + ', ';
				break;
			
			case 'select-one': 
				if(this.element.selectedIndex >= 0)
					this.value = this.element.options[this.element.selectedIndex].value;
				break;
				
			case 'select-multiple':
				this.value = '';
				this.collection = this.element.options;
				for (oo = 0; oo < this.collection.length; oo++)
				{
					var el = this.collection[oo].value;
					if(this.collection[oo].selected && el) this.value += el + ', ';
				}
				break;
				
			default:
				this.value = this.element.value;
		}

		// Проверка размера данных

		var val = trimString(this.value);
		// TEST	alert(this.name + "=" + this.value);
		// alert(this.name + "=" +this.minlen+","+this.maxlen);
		if(val) 
		{
			if( this.maxlen == this.minlen && this.value.length != this.maxlen )
			{
				this.error(_check_err_eqlen);
				return;
			}
			if(this.value.length > this.maxlen)
			{
				this.error(_check_err_maxlen);
				return;
			}
			else if(this.value.length < this.minlen)
			{
				this.error(_check_err_minlen);
				return;
			}
		}	
		if(this.req && val == '')
		{
			if( this.attrs == 'file' )
			{
				this.name = this.name.substring(3);
				val = form_obj.elements[this.name].value;
			}
				
			if( val == '' )
			{
				this.error(_check_err_empty);
				return;
			}
		}

		// Проверка формата

		if(val == '') 
			return; 

		switch(this.attrs)
		{
			case 'email':
				{	
					var re = /^([\w-.]+@)((\w|-){2,}\.)+(\w{2,4})$/ig;
					if( re.exec(this.value) == null ) this.error();
				}
				break;
				
			case 'phone':
			case 'fax':
				if( ! validateChars(this.value, char_phone) ) this.error();
				break;
				
			case 'number':
				if( ! validateChars(this.value, char_number) ) this.error();
				break;
				
			case 'natural_number':
				if( ! validateChars(this.value, char_natural_number) ) this.error();
				break;
				
			case 'integer':
				if( ! validateChars(this.value, char_integer)) this.error();
				break;
				
			case 'string':
				if( ! validateChars(this.value, char_names + char_digits + char_signs_basic)) this.error();
				break;
				
			case 'file':
				break;
				
			case 'date':
				var dfmt = this.parent.dfmt + ' [hh:mm]';
				if(check_date(this.value, dfmt) != 0) this.error();
				break;
				
	        case 'longdate':
				var dfmt = this.parent.dfmt + ' [hh:mm]';
				if(check_date(this.value, dfmt, 'long') != 0) this.error();
				break;
				
	        case 'shortdate':
				var dfmt = this.parent.dfmt + ' [hh:mm]';
				if(check_date(this.value, dfmt, 'short') != 0) this.error();
				break;
				
			case 'datetime':
				var dfmt = this.parent.dfmt + ' hh:mm';
				if(check_date(this.value, dfmt) != 0) this.error();
				break;
				
			case 'time':
				if( validateChars(this.value, char_time) )
				{
					var len, indColon;
					var hours, minutes;
					hours		= this.value.substring(0,2);
					minutes		= this.value.substring(3,5);
					indColon	= this.value.indexOf(':');
					len			= this.value.length;
					if(!( hours <= 23 && minutes <= 59 && hours >= 0 && minutes >= 0 && indColon == 2 && len == 5)) this.error();
				}
				else
					this.error();
				break;

			default:
				if(this.attrs.substring(0, 1) == '+')
				{
					var m;
					var attrs_sub = this.attrs.split('+');
					
					for(m = 1, char_test = ''; m < attrs_sub.length; m++)
						if(attrs_sub[m].substring(0, 1) == '#')
							char_test += String.fromCharCode(parseInt(attrs_sub[m].substring(1, attrs_sub[m].length)));
						else if(eval('char_' + attrs_sub[m]))
							char_test += eval('char_' + attrs_sub[m]);
							
					if( ! validateChars(this.value, char_test) ) this.error();
				}
				else if(this.attrs.substring(0, 1) == '/')
				{
					var re = eval(this.attrs);
					if( re.exec(this.value) == null ) this.error();
				}
				else if(this.attrs.substring(0, 1) == '=')
				{
					eval( this.attrs.substring(1)+"(this)" );
				}
				else if(this.attrs != "any") 
				{
					this.error('INTERNAL ERROR. Invalid type format for "' + this.name + '"'); 
				}
				break;
		}
    }
    else
    {
		var _any_checked = 0;
		for(i=0;i<this.fld_set.length;i++)
		{ 
			if(form_obj.elements[this.fld_set[i]].length)
				if(form_obj.elements[this.fld_set[i]][0].type.toLowerCase().indexOf('text')<0)
				{ 
					for(j=0;j<form_obj.elements[this.fld_set[i]].length;j++) 	
						if(form_obj.elements[this.fld_set[i]][j].checked) _any_checked++;
				}
				else
				{
					if(form_obj.elements[this.fld_set[i]][0].value.length)
						_any_checked++;
				}
			else
			    if(form_obj.elements[this.fld_set[i]].type.toLowerCase().indexOf('text')<0)
				{ 
					if(form_obj.elements[this.fld_set[i]].checked) _any_checked++;
				}
				else
				{
					if(form_obj.elements[this.fld_set[i]].value.length)
						_any_checked++;
		        }
        }
        
        
		if(_any_checked < 1 || _any_checked > this.maxlen)
		{
			if(this.maxlen==Number.MAX_VALUE) this.error(_check_err_atleast);
			else this.error(_check_err_inset);
		}
		
		if(this.attrs != "any" ) 
		{
			for( m=0; m < this.fld_set.length; m++)
			{
				char_test = this.fld_set[m]+'::'+this.desc+'::'+this.attrs;
				var f = new DataChecker_Field(this.parent,char_test);
				f.check();
			}
		}
		
	}
}

//  Короткая запись года XX, большая этой константы, превратится в 19XX, иначе - в 20XX
var ShortYearRange = 30;
var tyear, tmonth, tday, thour, tminute;

/***********************************************************************/
/*		функция проверки корректности даты 

		m_error = check_date(str_date, format_date)

			str_date 		- строка содержащая дату для проверки

			format_date 		- шаблон формата даты. Примеры значений:
				"dd.mm.yy" ; "dd/mm/yy" ; "dd.mm.yy hh:mm", "dd.mm.yy [hh:mm]"
				"mm/dd/yy"
				
			
			m_error = 0  - если строка соответствует шаблону.
				= 1 -  если строка соответствует шаблону, но дата не корректна (32.01.2000)
				= 2 - если строка не соответствует шаблону
				= 3 - если шаблон задан не корректно

	Примечание:
		В качестве символов в шаблоне могут быть только "d","m", "y", "h" (или большие буквы "D","M"...) . В качестве разделителей полей даты могут быть ".","/",	"\".  В качестве разделитей полей времени может быть только ":". Других символов в шаблоне быть не может.
	"dd.mm.yy" - означает, что время вводить нельзя.
	"dd.mm.yy hh:mm" - означает, что время вводится обязательно.
	"dd.mm.yy [hh:mm]" - означает, что время можно вводить, а можно не вводить.

	Для формата "dd.mm.yy" корректными  значениями, например, будут "01.05.00"; "1.5.00"; "01.5.2000", а некорректным "01/05/2000". Т.е. кол-во символов в числе и месяце может варьироваться 1 или 2, в годе 2 или 4, а разделитель должен строго соответствовать шаблону.
	Кол-во символов в представлении часов и минут должно быть по 2.
	Шаблоны "d.mm.yy" или "dd.m.yy" и им подобные для упроощения задачи можно считать некорректными, т.к. на практике они вряд ли будут использоваться.		
*/

/***********************************************************************/
function check_date(dat, fmt, yeartype)        
{
  var n, m;
  var tmp, re, opt, ord;

  fmt = fmt.toUpperCase();

//------------------- check for error symbols 

  re = /[^DMYH\\\/. :\[\]]/g;
  if(re.test(fmt)) return 3;

//------------------- shrink spaces

  re = /\s+/g; fmt = fmt.replace(re, ' ');

//------------------- 'dd.mm.yy hh:mm' -> 'd.m.y h:n' 

  re = /\[\s*HH\s*:\s*MM\s*\]/;  fmt = fmt.replace(re, '[h:n]');
  re = /HH\s*:\s*MM/;  fmt = fmt.replace(re, 'h:n');
  re = /DD/;  fmt = fmt.replace(re, 'd');
  re = /MM/;  fmt = fmt.replace(re, 'm');
  re = /YY/;  fmt = fmt.replace(re, 'y');
  re = /yYY/;  fmt = fmt.replace(re, 'y');

  re = /H/g; if(re.test(fmt)) return 3;
  re = /D/g; if(re.test(fmt)) return 3;
  re = /M/g; if(re.test(fmt)) return 3;
  re = /Y/g; if(re.test(fmt)) return 3;

//------------------- '[h:n]' -> 'H:N'

  re = /\[h:n\]/;  fmt = fmt.replace(re, 'H');

//------------------- check branches

  if(fmt.indexOf('\[') >=0 || fmt.indexOf('\]') >= 0) return 3;

//------------------- remember order

  re = /[\\\/. :]/g; ord = fmt.replace(re, '');

//------------------- trim pattern

  re = /^\s*(.+)\s*$/; fmt = (fmt.match(re))[1];

//------------------- prepare check pattern

  re = /\\/g; fmt = fmt.replace(re, '\\s*\\\\\\s*');
  re = /\//g; fmt = fmt.replace(re, '\\s*\\\/\\s*');
  re = /\./g; fmt = fmt.replace(re, '\\s*\\.\\s*');
  re = /:/g; fmt = fmt.replace(re, '\\s*:\\s*');
  re = /\s+/g; fmt = fmt.replace(re, '\\s+');

  re = /[dmh]/g; fmt = fmt.replace(re, '(\\d{1,2})');
  re = /[n]/g; fmt = fmt.replace(re, '(\\d\\d)');
  re = /[y]/g; fmt = fmt.replace(re, '(\\d{2,4})');
  re = /H/g; fmt = fmt.replace(re, '(\\d+\\s*:\\s*\\d\\d)*');

  dat = ' ' + dat + ' ';

//------------------- get & check date/time data

  var day=1, month=1, year=2000, hour=0, minute=0;

  re = new RegExp('^\\s*' + fmt + '\\s*$', '');
  tmp = dat.match(re);

  if(tmp != null) {

    for(n = 0, m = 1; n < ord.length; n++) {
      switch(ord.charAt(n)) {
        case 'd':
                  day = parseInt(tmp[m++],10); break;
        case 'm':
                  month = parseInt(tmp[m++],10); break;
        case 'y':
                  year = parseInt(tmp[m++],10); break;
        case 'h':
                  hour = parseInt(tmp[m++],10); break;
        case 'n':
                  minute = parseInt(tmp[m++],10); break;
        case 'H':
                  if(/(\d+)\s*:\s*(\d\d)/.test(tmp[m])) {
                    hour = parseInt(RegExp.$1,10);
                    minute = parseInt(RegExp.$2,10);
                    m++;
                  }
                  else {
                    m++;
                  }
      }
    }

//------------------- check day, month, year, hour, minute

    if(yeartype=='long' && year<1000) return 1;
    if(yeartype=='short' && year>100) return 1;
    if(year < 100) year += 2000;
    if(day < 1 || month < 1 || month > 12 || year > 9999) return 1;
    if(day > (new Array(0,31,29,31,30,31,30,31,31,30,31,30,31))[month]) return 1;
    if(month == 2) {
      if(!(year % 4 == 0 && !(year % 100 == 0 && year % 400 != 0))) {
        if(day > 28) return 1;
      }
    }
    if(hour < 0 || hour > 23 || minute < 0 || minute > 59) return 1;

      tyear = year;
      tmonth = month;
      tday = day;
      thour = hour;
      tminute = minute;
      return 0;

    return 0;
  }

  return 2;
}
// end of function check_date





/***********************************************************************/
// функция преобразования даты к строке (для загрузки значения поля даты в форму)  
//
//			mdate 			- дата которую требуется преобразовать в строку
//			format_date 		- шаблон формата даты. Cм. выше
//

function date_to_str(mdate, format_date_in)
{
  function right(str, len)
  {
    return str.substring(str.length - len, str.length);
  }

	var re, n, fl, format_date = format_date_in;
	
	format_date = format_date.toUpperCase();
	
	if(/[^DMYH\\\/. :]/.test(format_date)) return '';

	// Меням обозначение минут на NN

	format_date = format_date.replace(/:(\s*)MM/, ':' + RegExp.$1 + 'NN');

	format_date = format_date.replace(/HH/, 'h');
	format_date = format_date.replace(/NN/, 'n');
	format_date = format_date.replace(/YY/g, 'y');
	format_date = format_date.replace(/yy/, 'z');
	format_date = format_date.replace(/MM/, 'm');
	format_date = format_date.replace(/DD/, 'd');
	format_date = format_date.replace(/M/, 'k');
	format_date = format_date.replace(/D/, 'c');
	
	if(/[DMYH]/.test(format_date)) return '';

	format_date = format_date.replace(/z/, '' + mdate.getFullYear());
	format_date = format_date.replace(/y/, right('0000' + mdate.getFullYear(), 2));
	format_date = format_date.replace(/m/, right('00' + (mdate.getMonth() + 1), 2));
	format_date = format_date.replace(/d/, right('00' + mdate.getDate(), 2));
	format_date = format_date.replace(/k/, '' + (mdate.getMonth() + 1));
	format_date = format_date.replace(/c/, '' + mdate.getDate());
//	format_date = format_date.replace(/h/, right('00' + mdate.getHours(), 2));
//	format_date = format_date.replace(/n/, right('00' + mdate.getMinutes(), 2));

	return format_date;
}

/***********************************************************************/
//  функция преобразования строки к дате (строке)
//
//			str_date 		- строка содержащая дату для преобразования
//			format_date 		- шаблон формата даты. Cм. выше

function str_to_date(str_date, format_date)
{
	var res;

	res = check_date(str_date, format_date);
	if(res == 0) {
		return new Date(tyear, tmonth - 1, tday, thour, tminute);
	}

	return new Date();
}
