function preg_print_pre(obj, reg)
{
	if (!reg) reg = /.*/;
	var p = ''
	for (var prop in obj) {
		if (prop.match(reg) ) {
			p += prop + ': '+obj[prop] + '\n'
		}
	}
	alert(p)
}


// Main AJAX classs
function Request() {}

Request.timeout = 60 * 1000; // 60 seconds
Request.method = 'GET';
Request.headers = {};
Request.params = null;
Request.progressText = 'Loading ...';

Request.makeRequest = function(p_url, p_busyReq, p_progId, p_successCallBack, p_errorCallBack, p_pass, p_object) {
	//p_url: the web service url
	//p_busyReq: is a request for this object currently in progress?
	//p_progId: element id where progress HTML should be shown
	//p_successCallBack: callback function for successful response
	//p_errorCallBack: callback function for erroneous response
	//p_pass: string of params to pass to callback functions
	//p_object: object of params to pass to callback functions

	if (p_busyReq) return;
	var req = Request.getRequest();
	if (req != null) {
		p_busyReq = true;
		Request.showProgress(p_progId);
		req.onreadystatechange = function() {
			if (req.readyState == 4) {
				p_busyReq = false;
				window.clearTimeout(toId);
				try {
					if (req.status == 200) {
	//					preg_print_pre(req)
						p_successCallBack(req, p_pass, p_object);
					} else {
						p_errorCallBack(req, p_pass, p_object);
					}
					Request.hideProgress(p_progId);
				}
				catch (e) {
//					alert('AJAX error')
				}
			}
		}
		var $ajax_mark = (p_url.indexOf('?') ? '&' : '?') + 'ajax=yes';
		req.open(Request.method, p_url + $ajax_mark, true);

		if (Request.method == 'POST') {
			Request.headers['Content-type'] = 'application/x-www-form-urlencoded';
			Request.headers['referer'] = p_url;
		}
		else {
			Request.headers['If-Modified-Since'] = 'Sat, 1 Jan 2000 00:00:00 GMT';
		}

		Request.sendHeaders(req);
		if (Request.method == 'POST') {
			req.send(Request.params);
			Request.method = 'GET'; // restore method back to GET
		}
		else {
			req.send(null);
		}

		var toId = window.setTimeout( function() {if (p_busyReq) req.abort();}, Request.timeout );
	}
}

Request.processRedirect = function($request) {
	var $match_redirect = new RegExp('^#redirect#(.*?)($|\\s.*)').exec($request.responseText);
	if ($match_redirect != null) {
		// redirect to external template requested
		window.location.href = $match_redirect[1];
		return true;
	}
	return false;
}
Request.sendHeaders = function($request) {
	for (var $header_name in Request.headers) {
		if (typeof Request.headers[$header_name] == 'function') {
			continue;
		}
		$request.setRequestHeader($header_name, Request.headers[$header_name]);
	}
	Request.headers = {}; // reset header afterwards
}

Request.getRequest = function() {
	var xmlHttp;
	try { xmlHttp = new ActiveXObject('MSXML2.XMLHTTP'); return xmlHttp; } catch (e) {}
	try { xmlHttp = new ActiveXObject('Microsoft.XMLHTTP'); return xmlHttp; } catch (e) {}
	try { xmlHttp = new XMLHttpRequest(); return xmlHttp; } catch(e) {}
	return null;
}

Request.showProgress = function(p_id) {
	if (p_id != '') {
		var $content_div = $( jq('#' + p_id) );
		var $content_offset = $content_div.offset();
		var $content_width = $content_div.width();
		var $content_height = $content_div.height();

//		alert('id: ' + p_id + '; ch: ' + $content_div.get(0).clientHeight + '; sh: ' + $content_div.get(0).style.height);

		var $parent_div = $content_div.parents(':first');

		// use parent height, when own height is larger, then parent's
		$content_width = Math.min($content_width, $parent_div.width());
		$content_height = Math.min($content_height, $parent_div.height());

		var $progress_overlay = $( jq('#' + p_id + '_progress') );

		if ($progress_overlay.length == 0) {
			$progress_overlay = $('<div id="' + p_id + '_progress"></div>');

			var $maximal_height = $(window).height() - $content_offset.top;

			$progress_overlay.css(
				{
					width: $content_div.width(),
					height: $content_height > 0 ? $content_height : $maximal_height,
					left: $content_offset.left,
					top: $content_offset.top + $parent_div.scrollTop(),
					position: 'absolute',
					zIndex: 30,
					opacity: 0.8,
					backgroundColor: '#2D79D6',

					// don't show progress overlay, when target div is hidden
					display: $content_offset.top /*$content_height*/ > 0 ? 'block' : 'none'
				}
			);

			$('body').append($progress_overlay);

			$progress_overlay.html( Request.getProgressHtml() );
		}
		else {
			if ($content_height > 0) {
				// show progress, only when target div is visible
				$progress_overlay.css(
					{
						width: $content_width,
						height: $content_height,
						top: $content_offset.top + $parent_div.scrollTop()
					}
				);
			}

			$progress_overlay.show();
		}

	}
}

Request.hideProgress = function(p_id) {
	if (p_id != '') {
		$( jq('#' + p_id + '_progress') ).hide();
	}
}

Request.setOpacity = function (opacity, id) {
	var $element = $( typeof(id) == 'string' ? jq('#' + id) : id );
	$element.css('opacity', opacity / 100);
}

Request.getProgressHtml = function() {
	return "<div class='progress' style='position: relative; top: 50%; text-align: center; color: white;'>" + Request.progressText + "<br /><img src='img/ajax_progress.gif' align='absmiddle' width='100' height='7' alt='" + Request.progressText + "' title='" + Request.progressText + "'/></div>";
}

Request.getErrorHtml = function(p_req) {
	//TODO: implement accepted way to handle request error
	return '[status: ' + p_req.status + '; status_text: ' + p_req.statusText + '; responce_text: ' + p_req.responseText + ']';
}

Request.serializeForm = function(theform) {
	if (typeof(theform) == 'string') {
		theform = document.getElementById(theform);
	}

	var els = theform.elements;
	var len = els.length;
	var queryString = '';

	Request.addField = function(name, value) {
		if (queryString.length > 0) queryString += '&';
		queryString += encodeURIComponent(name) + '=' + encodeURIComponent(value);
	};

	for (var i = 0; i<len; i++) {
		var el = els[i];
    	if (el.disabled) continue;

		switch(el.type) {
			case 'text':
			case 'password':
			case 'hidden':
			case 'textarea':
          		Request.addField(el.name, el.value);
				break;

			case 'select-one':
				if (el.selectedIndex >= 0) {
            		Request.addField(el.name, el.options[el.selectedIndex].value);
          		}
          		break;

			case 'select-multiple':
				for (var j = 0; j < el.options.length; j++) {
            		if (!el.options[j].selected) continue;
              		Request.addField(el.name, el.options[j].value);
          		}
          		break;

			case 'checkbox':
			case 'radio':
          		if (!el.checked) continue;
            	Request.addField(el.name,el.value);
          		break;
      	}
	}
	return queryString;
};

// AJAX ProgressBar class
function AjaxProgressBar($url) {
	this.WindowTitle = this.GetWindow().document.title;
	this.URL = $url;
	this.BusyRequest = false;
	this.LastResponceTime = this.GetMicroTime();
	this.ProgressPercent = 0; // progress percent
	this.ProgressTime = [];
	this.Query();
}

AjaxProgressBar.prototype.GetWindow = function() {
	return window.parent ? window.parent : window;
}

AjaxProgressBar.prototype.GetMicroTime = function() {
	var $now = new Date();
	return Math.round($now.getTime() / 1000); // because miliseconds are returned too
}

AjaxProgressBar.prototype.Query = function() {
	var $me = this;

	$.ajaxSetup({cache: false});

	$.get(
		this.URL,
		function ($response) {
			var $match_redirect = new RegExp('^#redirect#(.*?)($|\\s.*)').exec($response);

			if ( $match_redirect != null ) {
				$me.showProgress(100);
				// redirect to external template requested
				window.location.assign($match_redirect[1]);
				return;
			}

			if ( isNaN(parseFloat($response)) ) {
				// error message received, otherwise $response should be number
				alert($response);
				return;
			}

			if ( $me.showProgress($response) ) {
				$me.Query();
			}
		}
	);
}

// return time needed for progress to finish
AjaxProgressBar.prototype.GetEstimatedTime = function() {
	return Math.ceil((100 - this.ProgressPercent) * Math.sum(this.ProgressTime) / this.ProgressPercent);
}

AjaxProgressBar.prototype.FormatTime = function ($seconds) {
	$seconds = parseInt($seconds);

	var $minutes = Math.floor($seconds / 60);
	if ($minutes < 10) $minutes = '0' + $minutes;
	$seconds = $seconds % 60;
	if ($seconds < 10) $seconds = '0' + $seconds;

	return $minutes + ':' + $seconds;
}

AjaxProgressBar.prototype.showProgress = function ($percent) {
	this.ProgressPercent = $percent;
	var $now = this.GetMicroTime();
	this.ProgressTime[this.ProgressTime.length] = $now - this.LastResponceTime;
	this.LastResponceTime = $now;

	var $display_progress = parseInt(this.ProgressPercent);
	this.GetWindow().document.title = $display_progress + '% - ' + this.WindowTitle;
	document.getElementById('progress_display[percents_completed]').innerHTML = $display_progress + '%';
	document.getElementById('progress_display[elapsed_time]').innerHTML = this.FormatTime( Math.sum(this.ProgressTime) );
	document.getElementById('progress_display[Estimated_time]').innerHTML = this.FormatTime( this.GetEstimatedTime() );

	document.getElementById('progress_bar[done]').style.width = $display_progress + '%';
	document.getElementById('progress_bar[left]').style.width = (100 - $display_progress) + '%';
	return $percent < 100 ? true : false;
}

// AJAX PopupManager class
function AjaxPopupManager($url) {
	this.URL = $url;
	this.PopupSizes = [];
}

AjaxPopupManager.prototype.GetSize = function ($template, $callback) {
	if ( $callback === undefined || !$.isFunction($callback) ) {
		alert('Please specify callback');
	}

	if ( !isset(this.PopupSizes[$template]) ) {
		var $me = this;

		$.get(
			this.URL,
			{
				'events[adm]': 'OnGetPopupSize',
				'template_name': $template
			},
			function ($popup_size) {
				// store response to cache for future use
				$me.PopupSizes[$template] = $popup_size;
				$callback($popup_size);
			}
		);
	}
	else {
		$callback(this.PopupSizes[$template]);
	}
};

// AJAX DropdownPreloader class
function AjaxDropdownPreloader($url, $input_mask, $filter_field, $dependend_field, value, $has_empty) {
	this.URL = $url;
	this.InputMask = $input_mask;
	this.FilterField = $filter_field;
	this.DependendField = $dependend_field;
	this.Titles = this.prepareTitles();
	this.Value = value;
	this.HasEmpty = $has_empty === undefined ? true : $has_empty;
	this.BusyRequest = false;
}

AjaxDropdownPreloader.prototype.prepareURL = function()
{
	return this.URL.replace('#DEPENDEND#', this.Dependend).replace('#FILTER_VALUE#', this.getValue(this.FilterField));
}

AjaxDropdownPreloader.prototype.prepareTitles = function() {
	var $control = this.getControl(this.DependendField);
	var $i = 0;
	var $ret = new Array ();
	while ($i < $control.options.length) {
		$ret[$control.options[$i].value] = $control.options[$i].innerHTML;
		$i++;
	}
	return $ret;
}

AjaxDropdownPreloader.prototype.getValue = function($field_name) {
	var $control = this.getControl($field_name);
	if ($control.tagName == 'INPUT') return $control.value;
	return $control.selectedIndex > 0 ? $control.options[$control.selectedIndex].value : '';
}

AjaxDropdownPreloader.prototype.Query = function () {
	var $url = this.prepareURL();
	var $selected_value = this.Value || this.getValue(this.DependendField);
	// remove all existing options
	this.removeOptions();
	Request.makeRequest($url, this.BusyRequest, '', this.successCallback, this.errorCallback, $selected_value, this);
}

AjaxDropdownPreloader.prototype.getControl = function($field) {
	var $id = this.InputMask.replace('#FIELD#', $field);
	return document.getElementById($id);
}

AjaxDropdownPreloader.prototype.successCallback = function($request, $params, $object) {
	if (Request.processRedirect($request) === true) {
		return ;
	}
	var control = $object.getControl($object.DependendField)
	$object.ProcessXMLNode($request.responseXML, control, $params);
	runOnChange(control);
	$object.AfterProcess();
}

AjaxDropdownPreloader.prototype.ProcessXMLNode = function($node, $dst_field, $selected_value) {
	for (var i = 0; i < $node.childNodes.length; i++) {
		var $child = $node.childNodes.item(i);
		switch ($child.tagName) {
			case 'option':
				var opt_value = $child.getAttribute('value');
				var title;
				if (opt_value) { // value is passed explicically
					title = $child.firstChild.nodeValue
				}
				else {
					opt_value = $child.firstChild.nodeValue;
					title = this.Titles[$child.firstChild.nodeValue];
				}
				this.addOption($dst_field, opt_value, title, $child.attributes);
				if (!$dst_field.multiple && (opt_value == $selected_value)) {
					$dst_field.options[$dst_field.options.length - 1].selected = true;
				}
				break;
			case 'field_options':
				if (this.HasEmpty && !$dst_field.multiple) {
					this.addOption($dst_field, '', '');
				}

				// add new states
				this.ProcessXMLNode($child, $dst_field, $selected_value);
				if ($dst_field.options.length == 0 || $dst_field.options.length == (this.HasEmpty ? 2 : 1)) {
					$dst_field.value = $dst_field.options[$dst_field.options.length - 1].value;
				}
				break;
		}
	}
}

AjaxDropdownPreloader.prototype.AfterProcess = function() {

}

AjaxDropdownPreloader.prototype.removeOptions = function($object) {
	if (!$object) $object = this.getControl(this.DependendField);
	if ($object.options.length > 0) {
		while ($object.options.length > 0) {
			$object.remove(0);
		}
	}
}

AjaxDropdownPreloader.prototype.addOption = function($object, $value, $title, attributes) {
	var $option = document.createElement('OPTION');
	$object.options.add($option, $object.options.length);
	$option.innerText = $title;
	$option.innerHTML = $title;
	$option.value = $value;
	if (attributes) {
		for (var i=0; i<attributes.length; i++) {
			if (attributes[i].nodeName == 'value') continue;
			$option.setAttribute(attributes[i].nodeName, attributes[i].nodeValue);
		}
	}
}

AjaxDropdownPreloader.prototype.errorCallback = function($request, $params, $object) {
	alert('AJAX Error; class: AjaxDropdownPreloader; ' + Request.getErrorHtml($request));
}