(function(){
	if (typeof(oldmos) == "undefined") oldmos = window.oldmos || {};
	
	// container - DOM node
	// opts - Object with map options
	oldmos.map = function(container, opts) {
		var _this = this;

		this.init = function() {
			this.opts = opts;

			this.year_from = _this.opts.year_min;
			this.year_to = _this.opts.year_max;

			// Самая общая инициализация карты
			this.map = new GMap2(container);
			_this.map.setCenter(
				new GLatLng(
					_this.opts.center_lat,
					_this.opts.center_lng
				),
				_this.opts.zoom
			);
			_this.map.setUIToDefault();

			// По непонятной причине не работает событие load. Эмулируем его через интервал.
			var load_interval = window.setInterval(
				function() {
					if (_this.map.isLoaded()) {
						window.clearInterval(load_interval);
						_this.on_map_load();
					}
				},
				100
			);

			// Кэш - массив ID точек, показанных на карте
			this.marker_cache = {};

			// Создание набора иконок
			this.map.icons = {};

			// Создание прототипов иконок
			this.map.icons.proto = {};
			this.map.icons.proto.star = add_icon_proto({
				"image" : _this.opts.icon_path + "star.png",
				"iconSize" : new GSize(20, 20),
				"iconAnchor" : new GPoint(10, 10),
				"infoWindowAnchor" : new GPoint(0, 0)
			});
			this.map.icons.proto.cluster = add_icon_proto({
				"image" : _this.opts.icon_path + "cluster_32.png",
				"iconSize" : new GSize(32, 32),
				"iconAnchor" : new GPoint(16, 16),
				"infoWindowAnchor" : new GPoint(0, 0)
			});
			this.map.icons.proto.photo = add_icon_proto({
				"image" : _this.opts.icon_path + "0.png",
				"iconSize" : new GSize(11, 11),
				"iconAnchor" : new GPoint(6, 6),
				"infoWindowAnchor" : new GPoint(-1, 0)
			});

			this.map.icons.star = add_icon(_this.map.icons.proto.star);

			this.map.icons.photo = {};
			this.map.icons.photo._default = add_icon(_this.map.icons.proto.photo);

			var directions_arr = ["n", "w", "s", "e", "nw", "ne", "sw", "se"];
			for (var i = 1890; i <= 2000; i += 10) {
				this.map.icons.photo[i] = {};
				this.map.icons.photo[i]._default = add_icon(
					this.map.icons.proto.photo,
					{"image" : _this.opts.icon_path + i + ".png"}
				);
				$.each(directions_arr, function(y, item) {
					_this.map.icons.photo[i][item] = add_icon(
						_this.map.icons.photo[i]["_default"],
						{
							"shadow" : _this.opts.icon_path + item + ".png",
							"shadowSize" : new GSize(33, 37)
						}
					);
				});
			}

			function add_icon_proto(opts) {
				var icon = new GIcon();
				$.each(opts, function (i, item) {
					icon[i] = item;
				});
				return icon;
			}

			function add_icon(proto, opts) {
				// Создает иконку по прототипу
				var icon = new GIcon(proto);
				if (typeof(opts) != "undefined") {
					$.each(opts, function (i, item) {
						icon[i] = item;
					});
				}
				return icon;
			}
		}

		// Функция производит начальные действия с картой в зависимоти от ее типа
		this.on_map_load = function() {
			switch (_this.opts.type) {
			case "main" :
				// todo: переделать так, чтобы не зависеть от высоты таймлайна
				$(container).height($(window).height() - 165);
				_this.map.checkResize();
				_this.map.setCenter(new GLatLng(_this.opts.center_lat, _this.opts.center_lng));

				_this.timeline = new oldmos.timeline(_this, _this.year_from, _this.year_to);
			case "view" :
				GEvent.addListener(
					_this.map,
					"zoomend",
					_this.on_zoom_end
				);
				GEvent.addListener(
					_this.map,
					"dragend",
					_this.on_drag_end
				);

				_this.get_markers(true);

				break;
			case "edit" :
				if (_this.opts.starred) {
					_this.on_add_dblclick(null, _this.map.getCenter());
				}
			case "add" :
				_this.map.disableDoubleClickZoom();
				
				GEvent.addListener(
					_this.map,
					"dblclick",
					_this.on_add_dblclick
				);

				break;
			}
		}

		// Функция устанавливает временной интервал карты, если он отличается от текущего
		// а также передергивает маркеры
		this.set_year_range = function(year_from, year_to) {
			if (year_from != _this.year_from || year_to != _this.year_to) {
				_this.year_from = year_from;
				_this.year_to = year_to;
				_this.get_markers(true);
			}
		}

		this.get_markers = function(clear) {
			if (typeof(clear) == "undefined") {
				clear = false;
			}

			if (_this.map.getZoom() >= _this.opts.cluster_threshold) {
				_this.flush_marker_cache();
				_this.map.clearOverlays();
				__get_photo_markers();
			} else {
				if (clear) {
					_this.flush_marker_cache();
					_this.map.clearOverlays();
				}
				__get_cluster_markers();
			}
		}

		this.pan_to_address = function(address) {
			var geocoder = new GClientGeocoder();

			geocoder.getLatLng(
				address,
				function(latlng) {
					if (!latlng) {
						alert(address + " not found");
					} else {
						//_this.map.setCenter(latlng, 16);
						_this.map.setZoom(16);
						_this.map.panTo(latlng);
					}
				}
			);
		}

		this.is_marker_cached = function(id) {
			return typeof(_this.marker_cache[id]) != "undefined";
		}

		this.add_marker_to_cache = function(id) {
			if (!_this.is_marker_cached(id)) {
				_this.marker_cache[id] = true;
			}
		}

		this.flush_marker_cache = function() {
			_this.marker_cache = {};
		}

		var __get_cluster_markers = function() {
			var bounds = _this.map.getBounds();
			var bounds_str = bounds.getNorthEast().toUrlValue() + "," + bounds.getSouthWest().toUrlValue();
			if (_this.year_from != _this.opts.year_min || _this.year_to != _this.opts.year_max) {
				var years_str = "/" + _this.year_from + "-" + _this.year_to;
			} else {
				var years_str = "";
			}
			$.getJSON(
				"/map/clusters/" + bounds_str + "/" + _this.map.getZoom() + years_str,
				__set_cluster_markers
			);
		}

		var __set_cluster_markers = function(data, json_status) {
			if (data.clusters && data.request.zoom == _this.map.getZoom()) {
				$.each(data.clusters, function(i, item) {
					if (_this.is_marker_cached(item.id)) {
						return;
					} else {
						_this.add_marker_to_cache(item.id);
					}

					var marker = new ClusterMarker_(
						new GLatLng(item.avg_lat, item.avg_lng),
						item.photo_count,
						[
							{width: 32, height: 32, url: _this.opts.icon_path + "cluster_32.png"},
							{width: 48, height: 48, url: _this.opts.icon_path + "cluster_48.png"},
							{width: 64, height: 64, url: _this.opts.icon_path + "cluster_64.png"}
						],
						Math.round(item.size) / 2
					);
					
					_this.map.addOverlay(marker);
				});
			}
			if (data.photos) {
				__set_photo_markers(data);
			}
		}

		var __get_photo_markers = function() {
			var bounds = _this.map.getBounds();
			var bounds_str = bounds.getNorthEast().toUrlValue() + "," + bounds.getSouthWest().toUrlValue();
			if (_this.year_from != _this.opts.year_min || _this.year_to != _this.opts.year_max) {
				var years_str = "/" + _this.year_from + "-" + _this.year_to;
			} else {
				var years_str = "";
			}
			$.getJSON(
				"/map/photos/" + bounds_str + "/" + _this.map.getZoom() + years_str,
				__set_photo_markers
			);
		}

		var __set_photo_markers = function(data, json_status) {
			if (data.request.zoom == _this.map.getZoom()) {
				$.each(data.photos, function(i, item) {
					if (_this.is_marker_cached(item.id)) {
						return;
					} else {
						_this.add_marker_to_cache(item.id);
					}

					if (item.id == _this.opts.starred) {
						var marker = new GMarker(
							new GLatLng(item.lat, item.lng),
							{
								"icon" : _this.map.icons["star"],
								"title" : item.year
							}
						);
					} else {
						var icon = _this.map.icons.photo["_default"];

						if (item.year_from != 0 && item.year_to != 0) {
							var icon_year = Math.floor(((item.year_from + item.year_to) / 2) / 10) * 10; // Берем по среднему и округляем до 10
							if (typeof(_this.map.icons.photo[icon_year]) != "undefined") {
								if (item.dir) {
									var icon = _this.map.icons.photo[icon_year][item.dir];
								} else {
									var icon = _this.map.icons.photo[icon_year]["_default"];
								}
							}
						}

						var marker = new GMarker(
							new GLatLng(item.lat, item.lng),
							{
								"icon" : icon
							}
						);

						// Расширяем маркер своими свойствами
						marker.url = item.url;
						marker.html = item.html;

						if (item.html) {
							GEvent.addListener(marker, "mouseover", function() {
								this.openExtInfoWindow(
									_this.map,
									"opacity_window",
									this.html,
									{beakOffset: 2}
								);
							});

							GEvent.addListener(marker, "mouseout", function() {
								this.closeExtInfoWindow(_this.map);
							});
						}

						GEvent.addListener(marker, "click", function(e) {
							if (ui_newwin) {
								window.open(this.url,'_blank');
							} else {
								window.location.href = this.url;
							}
						});
					}

					_this.map.addOverlay(marker);
				});
			}
		}

		this.on_zoom_end = function(zoom_old, zoom_new) {
			if (zoom_new < _this.opts.min_zoom) {
				_this.map.setZoom(zoom_old);
				return;
			}
			_this.get_markers(true);
		}

		this.on_drag_end = function() {
			_this.get_markers(false);
		}

		this.on_add_dblclick = function(overlay, latlng, overlay_latlng) {
			_this.map.clearOverlays(); // Уберем имеющиеся точки, чтобы не возникло задвоений
			var marker = new GMarker(
				latlng,
				{"icon" : _this.map.icons["star"]}
			);
			_this.map.addOverlay(marker);

			$("#lat").val(latlng.lat());
			$("#lng").val(latlng.lng());
		}

		_this.init();
	}

	oldmos.timeline = function(map, year_from, year_to) {
		var _this = this;

		this.init = function() {
			this.map = map;
			this.year_from = year_from;
			this.year_to = year_to;

			this.parent = $("#line");

			this.left_pin = $("#left_pin").draggable({
				axis : "x",
				containment : [
					_this.parent.offset().left - $("#left_pin").width(),
					0,
					_this.parent.offset().left + _this.parent.width(),
					0
				],
				drag : on_left_pin_drag,
				stop : on_left_pin_stop
			});

			this.right_pin = $("#right_pin").draggable({
				axis : "x",
				containment : [
					_this.parent.offset().left,
					0,
					_this.parent.offset().left + _this.parent.width(),
					0
				],
				drag : on_right_pin_drag,
				stop : on_right_pin_stop
			});

			$("#left_pin, #right_pin").mousedown(function(e){
				$(this).css({cursor : "url('/public/images/closedhand_8_8.cur'), default"});
			});
			$("#left_pin, #right_pin").mouseup(function(e){
				$(this).css({cursor : "url('/public/images/openhand_8_8.cur'), default"});
			});

			$(window).resize(_this.redraw);

			_this.redraw();
		}

		this.redraw = function() {
			_this.left_pin.css({left : (get_start_pos_by_year(_this.year_from) - _this.left_pin.width()) + "px"});
			if (_this.year_from == _this.year_to) {
				_this.right_pin.css({left : (get_start_pos_by_year(_this.year_to)) + "px"});
			} else {
				_this.right_pin.css({left : get_finish_pos_by_year(_this.year_to) + "px"});
			}

			set_containment(_this.left_pin);
			set_containment(_this.right_pin);

			draw_fill_and_year(_this.left_pin);
			draw_fill_and_year(_this.right_pin);
		}

		// Функция ограничивает область движения противоположного пина
		// pin - jQuery Object. 
		var set_containment = function(pin) {
			if (pin.get(0) === _this.left_pin.get(0)) {
				// Ограничение области движения правого пина
				var right_pin_cont = _this.right_pin.draggable("option", "containment");
				_this.right_pin.draggable("option", "containment", [pin.offset().left + pin.width(), 0, right_pin_cont[2], right_pin_cont[3]]);
			} else if (pin.get(0) === _this.right_pin.get(0)) {
				// Ограничение области движения левого пина
				var left_pin_cont = _this.left_pin.draggable("option", "containment");
				_this.left_pin.draggable("option", "containment", [left_pin_cont[0], left_pin_cont[1], pin.offset().left - pin.width(), 0]);
			}
		}

		// Функция отрисовывает серую полоску слева или справа от пина и год над ним
		var draw_fill_and_year = function(pin) {
			var left_pin_pos = _this.left_pin.position().left + _this.left_pin.width();
			var right_pin_pos = _this.right_pin.position().left;

			if (pin.get(0) === _this.left_pin.get(0)) {
				$("#left_grey").width(left_pin_pos);

				var caption = $("#left_caption");
				caption.html(_this.year_from);
				if (left_pin_pos <= caption.width()) {
					var caption_pos = 0;
				} else {
					var caption_pos = left_pin_pos - caption.width();
				}
				caption.css({left: caption_pos + "px"});
			} else if (pin.get(0) === _this.right_pin.get(0)) {
				$("#right_grey").width(_this.parent.width() - right_pin_pos);

				var caption = $("#right_caption");
				caption.html(_this.year_to);
				if (_this.parent.width() - right_pin_pos <= caption.width()) {
					var caption_pos = _this.parent.width() - caption.width();
				} else {
					var caption_pos = right_pin_pos;
				}
				caption.css({left: caption_pos + "px"});
			}

			if (_this.year_from == _this.year_to) {
				$("#left_caption, #right_caption").hide();
				var common_caption = $("#common_caption");
				common_caption.html(_this.year_from);
				if (left_pin_pos <= caption.width()) {
					var common_caption_pos = 0;
				} else if (_this.parent.width() - right_pin_pos <= caption.width()) {
					var common_caption_pos = _this.parent.width() - caption.width();
				} else {
					var common_caption_pos = Math.round((left_pin_pos + right_pin_pos) / 2 - (common_caption.width() / 2));
				}
				common_caption.html(_this.year_from).css({left: common_caption_pos + "px"}).show();
			} else {
				$("#common_caption").hide();
				$("#left_caption, #right_caption").show();
			}
		}

		var on_left_pin_drag = function(e, ui) {
			_this.year_from = get_year_by_pos(ui.position.left + ui.helper.width());
			draw_fill_and_year(ui.helper);
		}

		var on_right_pin_drag = function(e, ui) {
			_this.year_to = get_year_by_pos(ui.position.left);
			draw_fill_and_year(ui.helper);
		}

		var on_left_pin_stop = function(e, ui) {
			set_containment(ui.helper);
			_this.map.set_year_range(_this.year_from, _this.year_to);
		}

		var on_right_pin_stop = function(e, ui) {
			set_containment(ui.helper);
			_this.map.set_year_range(_this.year_from, _this.year_to);
		}

		var get_year_by_pos = function(pos) {
			var year_step = _this.parent.width() / (_this.map.opts.year_max - _this.map.opts.year_min);
			return Math.round(pos / year_step + _this.map.opts.year_min);
		}

		// Возвращает координату начала года на таймлайне
		var get_start_pos_by_year = function(year) {
			var year_step = _this.parent.width() / (_this.map.opts.year_max - _this.map.opts.year_min);
			return Math.floor(year_step * (year - _this.map.opts.year_min));
		}

		// Возвращает координату конца года на таймлайне
		var get_finish_pos_by_year = function(year) {
			var year_step = _this.parent.width() / (_this.map.opts.year_max - _this.map.opts.year_min + 1);
			return Math.ceil(year_step * (year - _this.map.opts.year_min + 1) - 1);
		}

		_this.init();
	}

	/*
		Portion of MarkerClusterer class version 1.0, copyright (c) 2009 Xiaoxi Wu
		Licensed under the Apache License, Version 2.0 (the "License");
		http://www.apache.org/licenses/LICENSE-2.0
	*/

	/**
	* ClusterMarker_ creates a marker that shows the number of markers that
	* a cluster contains.
	*
	* @constructor
	* @private
	* @param {GLatLng} latlng Marker's lat and lng.
	* @param {Number} count Number to show.
	* @param {Array of Object} styles The image list to be showed:
	*   {String} url Image url.
	*   {Number} height Image height.
	*   {Number} width Image width.
	*   {Array of Number} anchor Text anchor of image left and top.
	*   {String} textColor text color.
	* @param {Number} padding Padding of marker center.
	*/
	function ClusterMarker_(latlng, count, styles, padding) {
		var index = 0;
		var dv = count;
		while (dv !== 0) {
			dv = parseInt(dv / 10, 10);
			index ++;
		}

		if (styles.length < index) {
			index = styles.length;
		}
		this.url_ = styles[index - 1].url;
		this.height_ = styles[index - 1].height;
		this.width_ = styles[index - 1].width;
		this.textColor_ = styles[index - 1].opt_textColor;
		this.anchor_ = styles[index - 1].opt_anchor;
		this.latlng_ = latlng;
		this.index_ = index;
		this.styles_ = styles;
		this.text_ = count;
		this.padding_ = padding;
	}

	ClusterMarker_.prototype = new GOverlay();

	/**
	* Initialize cluster marker.
	* @private
	*/
	ClusterMarker_.prototype.initialize = function (map) {
		this.map_ = map;
		var div = document.createElement("div");
		var latlng = this.latlng_;
		var pos = map.fromLatLngToDivPixel(latlng);
		pos.x -= parseInt(this.width_ / 2, 10);
		pos.y -= parseInt(this.height_ / 2, 10);
		var mstyle = "";
		if (document.all) {
			mstyle = 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="' + this.url_ + '");';
		} else {
			mstyle = "background:url(" + this.url_ + ");";
		}
		if (typeof this.anchor_ === "object") {
			if (typeof this.anchor_[0] === "number" && this.anchor_[0] > 0 && this.anchor_[0] < this.height_) {
				mstyle += 'height:' + (this.height_ - this.anchor_[0]) + 'px;padding-top:' + this.anchor_[0] + 'px;';
			} else {
				mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
			}
			if (typeof this.anchor_[1] === "number" && this.anchor_[1] > 0 && this.anchor_[1] < this.width_) {
				mstyle += 'width:' + (this.width_ - this.anchor_[1]) + 'px;padding-left:' + this.anchor_[1] + 'px;';
			} else {
				mstyle += 'width:' + this.width_ + 'px;text-align:center;';
			}
		} else {
			mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
			mstyle += 'width:' + this.width_ + 'px;text-align:center;';
		}
		var txtColor = this.textColor_ ? this.textColor_ : 'black';

		div.style.cssText = mstyle + 'cursor:pointer;top:' + pos.y + "px;left:" +
			pos.x + "px;color:" + txtColor +  ";position:absolute;font-size:11px;" +
			'font-family:Arial,sans-serif;font-weight:bold';
		div.innerHTML = this.text_;
		map.getPane(G_MAP_MAP_PANE).appendChild(div);
		var padding = this.padding_;
		
		GEvent.addDomListener(div, "click", function () {
			var pos = map.fromLatLngToDivPixel(latlng);
			var sw = new GPoint(pos.x - padding, pos.y + padding);
			sw = map.fromDivPixelToLatLng(sw);
			var ne = new GPoint(pos.x + padding, pos.y - padding);
			ne = map.fromDivPixelToLatLng(ne);
			var zoom = map.getBoundsZoomLevel(new GLatLngBounds(sw, ne), map.getSize());
			map.setCenter(latlng, zoom);
		});
		
		this.div_ = div;
	};

	/**
	* Remove this overlay.
	* @private
	*/
	ClusterMarker_.prototype.remove = function () {
		this.div_.parentNode.removeChild(this.div_);
	};

	/**
	* Copy this overlay.
	* @private
	*/
	ClusterMarker_.prototype.copy = function () {
		return new ClusterMarker_(this.latlng_, this.index_, this.text_, this.styles_, this.padding_);
	};

	/**
	* Redraw this overlay.
	* @private
	*/
	ClusterMarker_.prototype.redraw = function (force) {
		if (!force) {
			return;
		}
		var pos = this.map_.fromLatLngToDivPixel(this.latlng_);
		pos.x -= parseInt(this.width_ / 2, 10);
		pos.y -= parseInt(this.height_ / 2, 10);
		this.div_.style.top =  pos.y + "px";
		this.div_.style.left = pos.x + "px";
	};

	/**
	* Hide this cluster marker.
	*/
	ClusterMarker_.prototype.hide = function () {
		this.div_.style.display = "none";
	};

	/**
	* Show this cluster marker.
	*/
	ClusterMarker_.prototype.show = function () {
		this.div_.style.display = "";
	};

	/**
	* Get whether the cluster marker is hidden.
	* @return {Boolean}
	*/
	ClusterMarker_.prototype.isHidden = function () {
		return this.div_.style.display === "none";
	};

})();