(
function() {
	var Global = this;

	// Check Forever api
	if(typeof(Global.Forever) != "object") {
		throw new Error("Namespace: Forever is already defined.");
	}

	var Forever = Global.Forever;

	// Check YUI api
	Forever.lang.hasYUI(
		{
			util: [
				"Event",
				"Dom",
				"Get"
			]
		}
	);

	// Check Google api
	Forever.lang.hasGoogle(
		{
			maps: [
				"Map2",
				"ClientGeocoder",
				"GeocodeCache",
				"Overlay",
				"Marker",
				"Event",
				"LatLng",
				"LatLngBounds"
			]
		}
	);

	Forever.widget = Forever.widget || {};

	// Forever
	var Parse = Forever.lang.Parse;
	var Bind = Forever.lang.Bind;

	// Google
	var Geocoder = google.maps.ClientGeocoder;
	var Bounds = google.maps.LatLngBounds;
	var Cache = google.maps.GeocodeCache;
	var Point = google.maps.LatLng;
	var Map = google.maps.Map2;

	// YUI
	var CustomEvent = YAHOO.util.CustomEvent;
	var Cookie = YAHOO.util.Cookie;
	var Event = YAHOO.util.Event;
	var Dom = YAHOO.util.Dom;
	var Get = YAHOO.util.Get;

	Forever.widget.Map = function(el) {
		el = Dom.get(el);
		if(el) {
			this._map = new Map(el);
		} else {
			throw new Error(this + ": el must be a HTMLElement or DOM id reference!");
		}
		this.init();
	};

	Forever.widget.Map.prototype = {
		_map: null,
		_geocoder: null,

		_markers: {},
		_markerOverlay: null,

		noneEvent: null,
		singleEvent: null,
		multipleEvent: null,

		searchEvent: null,
		positionEvent: null,
		distributorEvent: null,

		/**
		 * Inits all events
		 * @method init
		 */
		init: function() {
			this.noneEvent = new CustomEvent("none", this, false, YAHOO.util.CustomEvent.FLAT);
			this.singleEvent = new CustomEvent("single", this, false, YAHOO.util.CustomEvent.FLAT);
			this.multipleEvent = new CustomEvent("multiple", this, false, YAHOO.util.CustomEvent.FLAT);

			this.searchEvent =  new CustomEvent("search", this, false, YAHOO.util.CustomEvent.FLAT);
			this.positionEvent = new CustomEvent("position", this, false, YAHOO.util.CustomEvent.FLAT),
			this.distributorEvent =  new CustomEvent("distributor", this, false, YAHOO.util.CustomEvent.FLAT);
		},

		/**
		 * Returns the google map object
		 * @method getMap
		 * @return {GMap2}
		 */
		getMap: function() {
			return this._map;
		},

		/**
		 * Center the map to specific latlng point and zoom
		 * @method setCenter
		 * @param {GLatLng} latlng Accepts only a GLatLng point
		 * @param {Number} zoom Accepts only a unsigned integer default is 0
		 * @return {this}
		 */
		setCenter: function(latlng, zoom) {
			this.getMap().setCenter(latlng, typeof(zoom) == "number" ? zoom : 0);
			return this;
		},

		/**
		 * Search for a latlng point and fires none, single or multiple event depending on the amounts of hits
		 * @method search
		 * @param address Accepts only a string containing an address
		 * @return {this}
		 */
		search: function(address) {
			if(!this._geocoder) {
				this._geocoder = new Geocoder(new Cache());
			}

			this.searchEvent.fire(address);

			this._geocoder.getLatLng(
				address,
				Bind(
					this,
					function(address) {
						var result = this._geocoder.getCache().get(address);
						if(result.Status.code === G_GEO_SUCCESS) {
							if(result.Placemark.length > 1) {
								this.multipleEvent.fire(result.Placemark);
							} else {
								this.singleEvent.fire(result.Placemark[0]);
							}
						} else {
							this.noneEvent.fire(result.Status);
						}
					},
					address
				)
			);

			return this;
		},

		/**
		 * Auto zooms your markers from current center
		 * @method autoZoom
		 * @return {this}
		 */
		autoZoom: function() {
			var bound = new Bounds();
		 	for(var index in this._markers) {
		 		bound.extend(this._markers[index].getLatLng());
		 	}
		 	this.setCenter(bound.getCenter(), this.getMap().getBoundsZoomLevel(bound));
			return this;
		},

		/**
		 * Add a marker to the map
		 * @method addMarker
		 * @param {GMarker} marker Accepts only GMarker
		 * @return {this}
		 */
		addMarker: function(marker) {
			this._markers[marker.getLatLng()] = marker;
			this._map.addOverlay(marker);
			return this;
		},

		/**
		 * Adds markers to map
		 * @method addMarkers
		 * @param {Array} markers Accepts only a array with GMarker
		 * @return {this}
		 */
		addMarkers: function(markers) {
			for(var i = 0; i < markers.length; i++) {
				this.addMarker(markers[i]);
			}
			return this;
		},

		getMarkers: function() {
			return this._markers;
		},

		/**
		 * Fires positionEvent with an array of objects containing id, lat, lng
		 * @method getPositionByDistributors
		 * @param {String | Array} id Accepts only string distributors ids
		 * @return {Boolean}
		 */
		getPositionsByDistributor: function(id) {
			if(typeof(id) == "string") {
				id = [id];
			}
			if(!YAHOO.lang.isArray(id)) {
				return false;
			}

			var i = 0;
			while(i < id.length) {
				var list = [];
				for(var x = 0; (x < 25) && (i < id.length); x++, i++) {
					if(/^\d{12}$/.test(id[i + x])) {
						list.push(id[i + x]);
					}
				}
				Get.script(
					Forever.widget.Map.URL_POSITION +
					"?key=" + Forever.widget.Map.KEY +
					"&request=" + Forever.widget.Map.Request +
					"&id=" + list.join(",")
				);
			}

			this._makeRequest("position");

			return true;
		},

		/**
		 * Fires distributorEvent with an array of objects containing organizations name, lat, lng
		 * @method getDistributorsByPosition
		 * @param {GLatLng} latlng
		 * @param {String} code placemark.AddressDetails.Country.CountryNameCode
		 * @param {Number} limit
		 * @return {Boolean}
		 */
		getDistributorsByPosition: function(latlng, code, limit) {
			limit = typeof(limit) == "number" ? limit : 5;

			var language;

			switch(code) {
				case 'DK':
					language = 'DA';
					break;
				case 'NO':
				case 'FI':
					language = code;
					break;
				case 'SE':
				default:
					language = 'SV';
					break;
			}

			Get.script(
				Forever.widget.Map.URL_DISTRIBUTOR +
				"?key=" + Forever.widget.Map.KEY +
				"&request=" + Forever.widget.Map.Request +
				"&limit=" + limit +
				"&lat=" + latlng.lat() +
				"&lng=" + latlng.lng() +
				"&language=" + language
			);

			this._makeRequest("distributor");

			return true;
		},

		/**
		 * Makes a server request
		 * @method _makeRequest
		 * @param {String} e Accepts only lowercased string with an event name
		 */
		_makeRequest: function(e) {
			Forever.widget.Map.Transactions[Forever.widget.Map.Request++] = {
				scope: this,
				callback: function(obj) {
					this.scope[e + "Event"].fire(obj);
				}
			};
		},

		/**
		 * Returns the classname
		 * @method toString
		 * @return "Forever.widget.Map"
		 */
		toString: function() {
			return "Forever.widget.Map";
		}
	};

	Forever.widget.Map.Request = 1;
	Forever.widget.Map.Transactions = {};

	Forever.widget.Map.URL_DISTRIBUTOR = "http://fshop.se/forever/map/distributor.php";
	Forever.widget.Map.URL_POSITION = "http://fshop.se/forever/map/position.php";

	Forever.widget.Map.KEY = "9e78870609de02a1982b714a8cd625d7";
}
)();