function Geocoder(map, cfg, cookies) {
	this.map  = map;
	this.cfg  = cfg;
	this.cookies = cookies;
	this.cont = $(cfg.elements.container).show();
	this.initOpener(cfg.opened);
	this.initGeoCoder();
	cfg.autocomplete && this.initAutocomplete();
}

Geocoder.prototype = {

	elem: function (name) {
		return this.cont.find(this.cfg.elements[name]);
	},

	active: false,
	initOpener: function (opened) {
		var block  = this.elem('block');
		var toogle = function (active) {
			var anim = this.cfg[active ? 'show' : 'hide'];
			block.stop(true, true)[anim.method](anim.speed, function () {
				this.elem('input').focus().val('');
			}.bind(this));
			opener[active ? 'addClass' : 'removeClass']('active');
		}.bind(this);
		var opener = this.elem('opener').click(function() {
			toogle(this.active = !this.active);
		}.bind(this));
		toogle(this.active = opened);
		return this;
	},

	initAutocomplete: function () {
		var cfg = this.cfg;
		this.elem('input')
			.keyup(function(e){
				if (e.keyCode == 13) this.elem('submit').click();
			}.bind(this))
			.autocomplete({
				minLength: this.cfg.autocomplete.minLength,
				source: function(request, response) {
					$.ajax({
						url: this.cfg.autocomplete.url,
						jsonp: 'callback',
						dataType: 'jsonp',
						cache: cfg.ajaxCache,
						data : { q: request.term },
						success: function (result) {
							response(result);
						}
					});
				}.bind(this),
				focus: function( event, ui ) {
					$(this).val( ui.item.label.stripTags() );
					return false;
				},
				select: function(event, ui) {
					$(this).val( ui.item.label.stripTags() );
					return false;
				}
			})
			.data( "autocomplete" )
				// Хак, чтобы сделать вёрстку в списке доступной
				._renderItem = function( ul, item ) {
					return $( "<li></li>" )
						.data( "item.autocomplete", item )
						.append( "<a>" + item.label + "</a>" )
						.appendTo( ul );
				};
		return this;
	},

	marker: null,
	movePositionMarker: function (point, msg, dragMsg) {
		return PositionPointer.moveGeoMarker.call(this, this.cfg, point, this.cfg.centering, msg, dragMsg);
	},
	savePosition: function(point) {
		// this.cookies ?!
		return PositionPointer.savePosition.call(this, this.cookies.search, point, this.cookies.options);
	},

	initGeoCoder: function () {
		var onError = this.cfg.onError;
		var onFound = this.movePositionMarker.bind(this);
		var bb = this.cfg.boundedBy, bounds = bb && new YMaps.GeoBounds(
			new YMaps.GeoPoint(bb.left, bb.bottom),
			new YMaps.GeoPoint(bb.right, bb.top)
		);
		var msg = function (index, search) {
			return this.cfg.msg[index].replace(/\*/g, search);
		}.bind(this);
		var options = bb ? {
			boundedBy: bounds,
			strictBounds: true
		} : {};
		this.elem('submit').click(function () {
			var geocoder = new YMaps.Geocoder(this.elem('input').val(), options);
			YMaps.Events.observe(geocoder, geocoder.Events.Load, function () {
				if (this.length()) {
					var obj = this.get(0);
					onFound(obj.getGeoPoint(), msg('found', obj.text), msg('moved', obj.text));
				} else {
					onError && onError("Ничего не найдено");
				}
			});

			YMaps.Events.observe(geocoder, geocoder.Events.Fault, function (geocoder, error) {
				onError && onError(error.message);
			}.bind(this));
		}.bind(this));
	}
};

