(Observable = Class.create()).prototype = {
	register: function(event) {
		(Object.isArray(event) ? event : [event]).each(function(event) {
			(this.events || (this.events = {}))[event] = {
				observing: true, handlers: []
			};
		}.bind(this));
	},

	observe: function(event, handler) {
		if (!Object.isUndefined(this.events) && !Object.isUndefined(this.events[event]))
		{
			return event + '::' + this.events[event].handlers.push(handler);
		}
	},

	fire: function(event) {
		if (!Object.isUndefined(this.events) && !Object.isUndefined(this.events[event]))
		{
			this.events[event].handlers.each(function(args, handler) {
				handler.apply(window, args);
			}.curry($A(arguments).slice(1)));
		}
	}
};

BasicTemplate = Class.create(Observable, {
	/**
	 * @constructor
	 * @protected
	 * @param {Object} config control config
	 */
	initialize: function(config) {
		Object.extend(this, {
			/**
			 * Indicates whether template is frozen.
			 * @private
			 * @type Boolean
			 */
			frozen: false
		});

		Object.extend(this.config, {
			pageUrl: document.location.href,
			/**
			 * Data, which will be passed to every AJAX request.
			 * @protected
			 * @type Object
			 */
			passthrough: {},
			/**
			 * Localization strings.
			 * @protected
			 * @type Object
			 */
			strings: {}
		});

		Object.extend(this.config, config);

		if (!Object.isUndefined(this.onReady))
		{
			Event.onReady(this.onReady.bind(this));
		}
	},

	/**
	 * @protected
	 */
	getString: function(name) {
		return !Object.isUndefined(this.config.strings[name]) ? this.config.strings[name] : name;
	},

	/**
	 * Checks AJAX response whether it is JSON and evals it.
	 * @protected
	 * @param {XMLHttpRespose} transport
	 */
	evalResponse: function(transport) {
		var response = (
			Object.isUndefined(transport.responseText) ? transport : transport.responseText
		);

		return response.isJSON() ? response.evalJSON() : undefined;
	},

	setPassthroughVar: function(name, value) {
		this.config.passthrough[name] = value;
	},

	/**
	 * Forms right request URL.
	 * All the specified arguments will be interpreted
	 * as objects or strings and attached to the URL as GET params.
	 * @protected
	 */
	getUrl: function() {
		return BasicTemplate._getUrl(this.config.pageUrl, this.config.passthrough, arguments);
	},

	/**
	 * Freezes control actions.
	 * @protected
	 */
	freeze: function() {
		this.frozen = true;
	},

	/**
	 * Unfreezes control actions.
	 * @protected
	 */
	unfreeze: function() {
		(function() {this.frozen = false;}.bind(this)).delay(0.2);
	},

	/**
	 * Should be used instead of Event#observe to make
	 * possible freeze/unfreeze events processing all together.
	 * @protected
	 */
	addAction: function(element, event, handler, stopEvent, checkElement) {
		if (!checkElement || element)
		{
			Event.observe(element, event, function(handler, stopEvent, event) {
				if (this.frozen || stopEvent)
				{
					Event.stop(event);
				}

				if (!this.frozen)
				{
					handler(event)
				}
			}.bind(this, handler,  stopEvent));
		}
	},

	debug: function(message) {
		if (this.config.debug && ('undefined' != typeof console))
		{
			console.log(this.name + ': ' + message);
		}
		else
		{
			//alert(this.name + ': ' + message);
		}
	},

	resetForm: function(form) {
		errSelector = '#'+form.getAttribute('id')+' .field-error';
		if (errField = $$(errSelector))
		{
			errField.each(Element.remove);
		}

		// Clear form manually.

		form.getElements().each(function(sys, el) {
			var name = el.readAttribute('name');

			if (name && !sys[name] && ('button' != el.readAttribute('type')) && ('submit' != el.readAttribute('type')))
			{
				el.value = '';

				// For some reasons it is not enough
				// just to clear value in FF to reset select.
				if (!Object.isUndefined(el.selectedIndex))
				{
					el.selectedIndex = null;
				}
			}
		}.curry({'f_in': true, '_ia': true, '_iap': true, '_it_fs': true}));
	}
});

BasicTemplate._getUrl = function(pageUrl, passthrough, args) {
	var params = new Hash(passthrough), url = pageUrl ;

	if (args)
	{
		for (var i = 0; i < args.length; i++)
		{
			if (Object.isString(args[i]))
			{
				url += (-1 == url.indexOf('?') ? '?' : '&') + args[i];
			}
			else
			{
				params.update(args[i]);
			}
		}
	}

	return url + (
		params.values().length ? (-1 == url.indexOf('?') ? '?' : '&') + params.toQueryString() : ''
	);
}

BasicPage = Class.create(BasicTemplate, {
	initialize: function($super, config) {
		$super(config);

		Application.page = this;

		this.register('controlinit');
	}
});

BasicControl = Class.create(BasicTemplate, {
	initialize: function($super, name, config) {
		$super(config);

		if (!Object.isUndefined(Application.page))
		{
			Application.page.fire('controlinit', name, this);
		}

		Application.controls[this.name = name] = this;
	},

	getUrl: function($super) {
		if (false === this.config.ovrdGetUrl)
		{
			return $super.apply(window, $A(arguments).slice(1));
		}
		else
		{
			return $super.apply(window, ['IT:' + this.name].concat($A(arguments).slice(1)));
		}
	}
});


/**
 * Application object is used to link
 * all the page JS partes into one global scope.
 */
Application = {
	page: undefined,
	controls: {}
}