/******************************************************************************/
/************************** GLOBAL MAPS SETTINGS ******************************/
var MAPS_UNITS = "m";
var MAPS_ZOOM_LEVELS_NUM = 19;
var MAPS_MAX_RESOLUTION = 156543.0339;
var MAPS_MAX_SCALE = 59908790.416673884;

var MAPS_REGION_MIN_SCALE = 59908790.416673884;
var MAPS_SUBREGION_MIN_SCALE = 27734017.045258757;
var MAPS_SUBSUBREGION_MIN_SCALE = 10734017.045258757;
var MAPS_REGION_ZOOM = 3;
var MAPS_SUBREGION_ZOOM = 5;
var MAPS_SUBSUBREGION_ZOOM = 7;

var MAPS_WORLD_CENTER_POS_X = 0;
var MAPS_WORLD_CENTER_POS_Y = 2500000;
var MAPS_WORLD_CENTER_POS_ZOOM = 2;

var MAPS_DEFAULT_POS_X = -11161382;
var MAPS_DEFAULT_POS_Y = 4956208;
var MAPS_DEFAULT_POS_ZOOM = 4;

var MAPS_LOCATION_ZOOM = 8;
var MAPS_LOCATION_GROUP_ZOOM = 7;
var MAPS_LOCATION_GROUP_SEARCH_DIST = 160000;


/******************************************************************************/
/******************************** MAPS CORE ***********************************/

tbMaps = {
    settings: {
        mapContainerID: 'map',                  // ID of map container

        regionsKmlURL: null,                    // url to locations KML file
        countriesKmlURL: null,                  // url to countries KML file 
        locationsKmlURL: null,                  // url to regions KML file

        popupDescriptionURL: null,              // url to popup description
        dreamPopupButtonsURL: null,             // url to dream popup buttons panel
        dreamDescriptionURL: null,              // url to dream description

        displayTooltip: false,                  // show tooltip while hovering over feature
        dreamTooltipURL: null,                  // display location dreams in tooltip

        defaultPosX: MAPS_DEFAULT_POS_X,        // initial map position, x coord
        defaultPosY: MAPS_DEFAULT_POS_Y,        // initial map position, y coord
        defaultPosZoom: MAPS_DEFAULT_POS_ZOOM,

        worldCenterPosX: MAPS_WORLD_CENTER_POS_X, // world center position, x coord
        worldCenterPosY: MAPS_WORLD_CENTER_POS_Y, // world center position, y coord
        worldCenterPosZoom: MAPS_WORLD_CENTER_POS_ZOOM, // world center position, zoom

        locationZoom: MAPS_LOCATION_ZOOM,       // default zoom when focus on location
        locationGroupZoom: MAPS_LOCATION_GROUP_ZOOM,
        locationGroupSearchDist: MAPS_LOCATION_GROUP_SEARCH_DIST,

        filtersActive: false,                   // if true handles map filtering
        zoomOnUserNearestLocation: false
    },

    map: null,

    events: {},

    locations: null,
    regions: null,

    select: null,
    navigation: null,
    tooltipControl: null,

    pointX: null,
    pointY: null,
    pointZoom: null,
    pointIconGraphic: null,

    initialized: false,
    storage: {                                 // stores additional data
        user: null
    },

    delayedTasks: { layersToRefresh: [] }
};


/* Initialize map - main function. */
tbMaps.initTBMap = function(options) {
    this.settings = $.extend(this.settings, options);
    this.settings.isRegionMap = this.settings.regionsKmlURL ? true : false;
    this.settings.isStaticMap = this.settings.locationsKmlURL ? false : true;

    this.navigation = new OpenLayers.Control.Navigation();
    var controls = [this.navigation,
                    new OpenLayers.Control.PanZoomBar({zoomStopHeight: 4}),
                    new OpenLayers.Control.ArgParser(),
                    new OpenLayers.Control.ScaleLine({geodesic: true}),
                    new OpenLayers.Control.Attribution()]
    var mapOptions = {
        controls: controls,
        projection: new OpenLayers.Projection("EPSG:900913"),
        displayProjection: new OpenLayers.Projection("EPSG:4326"),
        units: MAPS_UNITS,
        maxResolution: MAPS_MAX_RESOLUTION,
        numZoomLevels: MAPS_ZOOM_LEVELS_NUM, 
        maxScale: MAPS_MAX_SCALE,
        maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34,
                                         20037508.34, 20037508.34)
    };
    this.map = new OpenLayers.Map(this.settings.mapContainerID, mapOptions);
    var loadingpanel = new OpenLayers.Control.LoadingPanel();
    this.map.addControl(loadingpanel);

    this.navigation.disableZoomWheel();

    try {
        var mapLayer = new OpenLayers.Layer.CloudMade("CloudMade", {
		    key: CLOUDMADE_API_KEY
		});
    } catch(ex) {
        logger(ex.stack);
        return;
    }

    var styleMap = this.getLocationStyleMap();

    if (this.settings.isRegionMap) {
        this.renderComplexMap(mapLayer);
    } else if (!this.settings.isStaticMap) {
        this.renderBasicMap(mapLayer);
    } else {
        this.renderStaticMap(mapLayer);
    }
    this.initialized = true;
};

/* Render complex map - having regions & locations using BBOX strategy. */
tbMaps.renderComplexMap = function(mapLayer) {
    var locationStyleMap = this.getLocationStyleMap();
    this.locations = this.getMapRegionLocations(locationStyleMap);

    var countriesStyleMap = this.getCountryStyleMap();
    this.countries = this.getMapRegionCountries(countriesStyleMap);

    var regionStyleMap = this.getRegionStyleMap();
    this.regions = this.getMapRegions(regionStyleMap);
    this.map.addLayers([mapLayer, this.regions, this.countries, this.locations]);

    if (this.settings.displayTooltip) {
        this.tooltipControl = new OpenLayers.Control.SelectFeature(this.locations, {
            hover: true,
            highlightOnly: true,
            eventListeners: {
                "featurehighlighted": this.events.tooltipSelect,
                "featureunhighlighted": this.events.tooltipUnselect
            }
        });
        this.map.addControl(this.tooltipControl);
        this.tooltipControl.activate();
    }

    this.map.events.on({
        "zoomend": this.events.mapZoomEnd
    });

    this.locations.events.on({
        "beforefeaturesadded": this.events.beforeFeaturesAdded,
        "beforefeatureadded": this.events.beforeFeatureAdded,
        "beforefeatureremoved": this.events.beforeFeatureRemoved,        
        "featureselected": this.events.onFeatureSelect,
        "featureunselected": this.events.onFeatureUnselect
    });
    
    this.regions.events.on({
        "featureselected": this.events.onRegionFeatureSelect
    });
    this.countries.events.on({
        "featureselected": this.events.onRegionFeatureSelect
    });

    this.select = new OpenLayers.Control.SelectFeature([this.regions, this.countries, this.locations]);

    this.map.addControl(this.select);
    this.select.activate();   

    var centerLonLat = new OpenLayers.LonLat(this.settings.worldCenterPosX, this.settings.worldCenterPosY);
    this.map.setCenter(centerLonLat, this.settings.worldCenterPosZoom);
};

/* Render basic map with locations only. All content is loaded at once. */
tbMaps.renderBasicMap = function(mapLayer) {
    var locationStyleMap = this.getLocationStyleMap();
    this.locations = this.getMapLocations(locationStyleMap);

    this.map.addLayers([mapLayer, this.locations]);

    this.map.events.on({
        "zoomend": this.events.mapZoomEnd
    });

    this.locations.events.on({
        "beforefeatureremoved": this.events.beforeFeatureRemoved,        
        "featuresadded": this.events.onFeaturesAdded,
        "beforefeatureadded": this.events.beforeFeatureAdded,
        "featureselected": this.events.onFeatureSelect,
        "featureunselected": this.events.onFeatureUnselect
    });
    this.select = new OpenLayers.Control.SelectFeature([this.locations]);

    this.map.addControl(this.select);
    this.select.activate();   

    var pos = new OpenLayers.LonLat(this.settings.defaultPosX, this.settings.defaultPosY);
    this.map.setCenter(pos, this.settings.defaultPosZoom);
};

tbMaps.renderStaticMap = function(mapLayer) {
    this.map.addLayer(mapLayer);

    var markers = new OpenLayers.Layer.Markers( "Markers" );
    this.map.addLayers([markers]);

    if (this.settings.pointIconGraphic) {
        var icon = new OpenLayers.Icon(this.settings.pointIconGraphic, new OpenLayers.Size(40, 40));
    } else {
        var icon = null;
    }
    var markerPos = new OpenLayers.LonLat(this.settings.pointX, this.settings.pointY);
    marker = new OpenLayers.Marker(markerPos, icon);
    markers.addMarker(marker);

    var pos = new OpenLayers.LonLat(this.settings.pointX, this.settings.pointY);
    this.map.setCenter(pos, this.settings.pointZoom || this.settings.defaultPosZoom);
};

/* Gets map locations layer. */
tbMaps.getMapLocations = function(styleMap) {
    var locations = new OpenLayers.Layer.Vector("KML Locations", {
        projection: this.map.displayProjection,
        strategies: [new OpenLayers.Strategy.Fixed()],
        protocol: new OpenLayers.Protocol.HTTP({
            url: this.settings.locationsKmlURL,
            format: new OpenLayers.Format.KML({
                extractStyles: true,
                extractAttributes: true
            })
        }),
        styleMap: styleMap
    });
    return locations;
};

/* Gets map regions layer. */
tbMaps.getMapRegionLocations = function(styleMap) {
    var locations = new OpenLayers.Layer.Vector("KML Locations", {
        minScale: MAPS_SUBSUBREGION_MIN_SCALE,
        maxScale: 0,
        projection: this.map.displayProjection,
        strategies: [new OpenLayers.Strategy.BBOX()],
        protocol: new OpenLayers.Protocol.HTTP({
            url: this.settings.locationsKmlURL,
            format: new OpenLayers.Format.KML({
                extractStyles: true,
                extractAttributes: true
            }),
            params: this.getMapFilters()
        }),
        styleMap: styleMap
    });
    return locations;
};

tbMaps.getMapRegionCountries = function(styleMap) {
    var locations = new OpenLayers.Layer.Vector("KML Countries", {
        minScale: MAPS_SUBREGION_MIN_SCALE,
        maxScale: MAPS_SUBSUBREGION_MIN_SCALE,
        projection: this.map.displayProjection,
        strategies: [new OpenLayers.Strategy.BBOX()],
        protocol: new OpenLayers.Protocol.HTTP({
            url: this.settings.countriesKmlURL,
            format: new OpenLayers.Format.KML({
                extractStyles: true,
                extractAttributes: true
            }),
            params: this.getMapFilters()
        }),
        styleMap: styleMap
    });
    return locations;
};

tbMaps.getMapRegions = function(styleMap) {
    regions = new OpenLayers.Layer.Vector("KML Regions", {
        projection: this.map.displayProjection,
        maxScale: MAPS_SUBSUBREGION_MIN_SCALE,
        strategies: [new OpenLayers.Strategy.BBOX()],
        protocol: new OpenLayers.Protocol.HTTP({
            url: this.settings.regionsKmlURL,
            format: new OpenLayers.Format.KML({
                extractStyles: true,
                extractAttributes: true
            }),
            params: this.getMapFilters()
        }), 
        styleMap: styleMap
    });
    return regions;
};

tbMaps.showCountriesLayer = function() {
    if (this.countries) {
        this.locations.addOptions({minScale: MAPS_SUBSUBREGION_MIN_SCALE}, false);
        this.countries.setVisibility(true);
    }
}

tbMaps.hideCountriesLayer = function() {
    if (this.countries) {
        this.locations.addOptions({minScale: MAPS_SUBREGION_MIN_SCALE}, false);
        this.countries.setVisibility(false);
    }
}

tbMaps.getRegionStyleMap = function() {
    level0Filter = new OpenLayers.Filter.Comparison({
        type: OpenLayers.Filter.Comparison.EQUAL_TO,
        property: "regionLevel",
        value: 0 
    });
    var emptyFilter = new OpenLayers.Filter.Comparison({
        type: OpenLayers.Filter.Comparison.EQUAL_TO,
        property: "itemNum",
        value: 0 
    });
    var nonEmptyFilter = new OpenLayers.Filter.Comparison({
        type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
        property: "itemNum",
        value: 0 
    });
    var level0NonEmptyRule = new OpenLayers.Rule({
        minScaleDenominator: MAPS_REGION_MIN_SCALE,
        filter: new OpenLayers.Filter.Logical({
            type: OpenLayers.Filter.Logical.AND,
            filters: [level0Filter, nonEmptyFilter]
        }),
        symbolizer: {
            label: "${itemNum}",
            fontSize: "15px",
            fontColor: "#cccccc",
            fontWeight: "bold",
            labelXOffset: "${xOffset}",
            labelYOffset: "-6"
        }
    });
    var level0EmptyRule = new OpenLayers.Rule({
        minScaleDenominator: MAPS_REGION_MIN_SCALE,
        filter: new OpenLayers.Filter.Logical({
            type: OpenLayers.Filter.Logical.AND,
            filters: [level0Filter, emptyFilter]
        }),
        symbolizer: {
            label: "0",
            fontSize: "15px",
            fontColor: "#cccccc",
            fontWeight: "bold",
            labelXOffset: "${xOffset}",
            labelYOffset: "-6"
        }
    });

    var level1Rule = new OpenLayers.Rule({
        minScaleDenominator: MAPS_SUBREGION_MIN_SCALE,
        maxScaleDenominator: MAPS_REGION_MIN_SCALE,
        filter: new OpenLayers.Filter.Comparison({
            type: OpenLayers.Filter.Comparison.EQUAL_TO,
            property: "regionLevel",
            value: 1 
        }),
        symbolizer: {
            label: "${name} (${itemNum})",
            fontSize: "15px",
            fontColor: "#222222",
            fontWeight: "bold",
            labelXOffset: "${xOffset}",
            labelYOffset: "20"
        }
    });

    var level2Rule = new OpenLayers.Rule({
        minScaleDenominator: MAPS_SUBSUBREGION_MIN_SCALE,
        maxScaleDenominator: MAPS_SUBREGION_MIN_SCALE,
        filter: new OpenLayers.Filter.Comparison({
            type: OpenLayers.Filter.Comparison.EQUAL_TO,
            property: "regionLevel",
            value: 2
        }),
        symbolizer: {
            label: "${name} (${itemNum})",
            fontSize: "15px",
            fontColor: "#222222",
            fontWeight: "bold",
            labelXOffset: "${xOffset}",
            labelYOffset: "20"
        }
    });

    var styleMap = new OpenLayers.StyleMap({
        "default": new OpenLayers.Style({
            cursor: 'pointer',
            externalGraphic: '${icon}',
            graphicWidth: '${iconWidth}',
            graphicHeight: '${iconHeight}'
        }, {
            rules: [level0NonEmptyRule, level0EmptyRule, level1Rule, level2Rule]
        })
    });
    return styleMap;
};

tbMaps.getCountryStyleMap = function() {
    var zeroRule = new OpenLayers.Rule({
        filter: new OpenLayers.Filter.Comparison({
            type: OpenLayers.Filter.Comparison.EQUAL_TO,
            property: "itemNum",
            value: 0 
        }),
        symbolizer: {
            label: "${name}",
            fontSize: "15px",
            fontColor: "#222222",
            fontWeight: "bold",
            labelXOffset: "${xOffset}",
            labelYOffset: "20"
        }
    });

    var nonZeroRule = new OpenLayers.Rule({
        filter: new OpenLayers.Filter.Comparison({
            type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
            property: "itemNum",
            value: 0 
        }),
        symbolizer: {
            label: "${name} (${itemNum})",
            fontSize: "15px",
            fontColor: "#222222",
            fontWeight: "bold",
            labelXOffset: "${xOffset}",
            labelYOffset: "20"
        }
    });

    var styleMap = new OpenLayers.StyleMap({
        "default": new OpenLayers.Style({
            cursor: 'pointer',
            externalGraphic: '${icon}',
            graphicWidth: '${iconWidth}',
            graphicHeight: '${iconHeight}'
        }, {
            rules: [zeroRule, nonZeroRule]
        })
    });
    return styleMap;
};

tbMaps.getLocationStyleMap = function() {
    var opacityRule = new OpenLayers.Rule({
        filter: new OpenLayers.Filter.Comparison({
            type: OpenLayers.Filter.Comparison.LESS_THAN,
            property: "opacity",
            value: 1 
        }),
        symbolizer: {
            graphicOpacity: '${opacity}',
            fillOpacity: '${opacity}' 
        }
    });
    var xOffsetRule = new OpenLayers.Rule({
        filter: new OpenLayers.Filter.Comparison({
            type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
            property: "iconXOffset",
            value: undefined
        }),
        symbolizer: {
            graphicXOffset: '${iconXOffset}'
        }
    });
    var yOffsetRule = new OpenLayers.Rule({
        filter: new OpenLayers.Filter.Comparison({
            type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
            property: "iconYOffset",
            value: undefined
        }),
        symbolizer: {
            graphicYOffset: '${iconYOffset}'
        }
    });
    var defaultRule = new OpenLayers.Rule({
        symbolizer: {
            graphicOpacity: 1,
            fillOpacity: 1
        },
        elseFilter: true
    });

    var styleMap = new OpenLayers.StyleMap({
        "default": new OpenLayers.Style({
            cursor: 'pointer',
            externalGraphic: '${icon}',
            graphicWidth: '${iconWidth}',
            graphicHeight: '${iconHeight}'
        }, {
            rules: [opacityRule, xOffsetRule, yOffsetRule, defaultRule]
        })
    });
    return styleMap;
};

tbMaps.deactivateMap = function() {
    this.navigation.deactivate();
    if (this.select) {
        this.select.unselectAll();
        this.select.deactivate();
    }
};

tbMaps.activateMap = function() {
    this.navigation.activate();
    this.select.unselectAll();
    this.select.activate();   
};


/******************************************************************************/
/******************************* MAPS EVENTS **********************************/

tbMaps.events.mapZoomEnd = function(event) {
    tbMaps.select.unselectAll();

    if (tbMaps.getActiveLayer() == tbMaps.regions)
        tbMaps.showCountriesLayer();

    tbMaps.delayedTasks.layersToRefresh = uniqueArray(tbMaps.delayedTasks.layersToRefresh); // TODO put this to object
    var layersToRefresh = tbMaps.delayedTasks.layersToRefresh;
    for (var i in layersToRefresh) {
        var activeLayerName = tbMaps.getActiveLayer().name;
        var layerName = layersToRefresh[i];
        if (activeLayerName == layerName) {
            var layers = tbMaps.map.getLayersByName(layerName);
            for (var j in layers) {
                var layer = layers[j];
                logger('Processing delayed task. Refresh Layer: '+layer.name);
                layer.refresh({
                    force: true
                });
            }
            tbMaps.delayedTasks.layersToRefresh.pop(layerName);
        }
    }
};

tbMaps.events.onRegionFeatureSelect = function(event) {
    tbMaps.select.unselectAll();

    var feature = event.feature;
    var regionLevel = feature.attributes.regionLevel;
    var regionSubCategoriesNum = feature.attributes.regionSubCategoriesNum;
    var customZoom = parseInt(feature.attributes.customZoom);
    if (feature.attributes.itemNum)
        var itemNum = parseInt(feature.attributes.itemNum);
    else
        var itemNum = 0;

    if (regionLevel == 0 && regionSubCategoriesNum > 0) {
        tbMaps.map.setCenter(new OpenLayers.LonLat(
            feature.geometry.x, feature.geometry.y), customZoom ? customZoom : MAPS_REGION_ZOOM);
    } else if (regionLevel == 1) {
        if (itemNum < 150) {
            tbMaps.hideCountriesLayer();
        }
        tbMaps.map.setCenter(new OpenLayers.LonLat(
            feature.geometry.x, feature.geometry.y), customZoom ? customZoom : MAPS_SUBREGION_ZOOM);
    } else {
        if (itemNum < 150) {
            tbMaps.hideCountriesLayer();
        }
        tbMaps.map.setCenter(new OpenLayers.LonLat(
            feature.geometry.x, feature.geometry.y), customZoom ? customZoom : MAPS_SUBSUBREGION_ZOOM);
    }
};

tbMaps.events.onFeaturesAdded = function(event) {
    var featuresLen = event.features.length;
    if (featuresLen == 1) {
        var geom = event.features[0].geometry;
        var pos = new OpenLayers.LonLat(geom.x, geom.y);
        tbMaps.map.setCenter(pos, tbMaps.settings.locationZoom);
    } else if (featuresLen > 1) {
        if (tbMaps.settings.zoomOnUserNearestLocation && tbMaps.storage.user) {
            var userClosestFeature = tbMaps.getClosestFeature(tbMaps.storage.user);

            var closestFeatures = tbMaps.getFeaturesWithin(userClosestFeature, 
                tbMaps.settings.locationGroupSearchDist);
            var zoom = tbMaps.settings.locationGroupZoom;
            if (!closestFeatures.length) {
                zoom -= 1;
            }

            var userClosestFeatureLonLat = new OpenLayers.LonLat(
                userClosestFeature.geometry.x, userClosestFeature.geometry.y);
            tbMaps.map.setCenter(userClosestFeatureLonLat, zoom);
        } else {
            tbMaps.map.zoomToExtent(tbMaps.getFeaturesBounds(event.object));
        }
    }
};

/* When few features have same point display one icon. */
tbMaps.events.beforeFeaturesAdded = function(event) {
    tbMaps.events.tooltipUnselect(event);
    var geometriesFeatures = [];
    for (var i = 0; i < event.features.length; i++) {
        var feature = event.features[i];
        var c = feature.data.locationId;
        if (c in geometriesFeatures) {
            geometriesFeatures[c].push(feature);
        } else {
            geometriesFeatures[c] = [feature];
        }
    }
    for (var key in geometriesFeatures)
    {
        if (geometriesFeatures[key].length > 1)
        {
            var len = geometriesFeatures[key].length;
            for (var j = 0 ; j < len ; j++)
            {
                if (len > 9)
                    var iconSmall = MEDIA_URL+'images/icons/multi/oo/small.png';
                else
                    var iconSmall = MEDIA_URL+'images/icons/multi/' + len + '/small.png';
                geometriesFeatures[key][j].attributes.icon = iconSmall;
            }
        }
    }
};

tbMaps.events.beforeFeatureAdded = function(event) {
    var feature = event.feature;
    if (feature.fid == 'userPositionPlacemark') {
        tbMaps.storage.user = feature;
        logger("Settings current user position to "+feature.geometry);
        return false;
    } else {
        return true;
    }
};

tbMaps.events.tooltipSelect = function(event) {
    var feature = event.feature;

    // if there is already open popup don't draw tooltip
    if (feature.popup){
        return;
    }

    if (feature.data.iconMedium) {
        var imageHTML = '<img class="tooltipIcon" alt="" src="'+feature.data.iconMedium+'" />';
    } else {
        var imageHTML = '';
    }
    if (feature.data.iconName) {
        var imageDesc = '<span class="tooltipIconDesc">' + truncate_chars(feature.data.iconName, 20) + '</span>';
    } else {
        var imageDesc = '';
    }
    var content = '<div class="tooltipPopup">'+imageHTML+imageDesc+'<div id="tooltipDreamBox"></div></div>';

    var tooltipPopup = new OpenLayers.Popup.FramedCloud("activetooltip",
        feature.geometry.getBounds().getCenterLonLat(), 
        null, content, null, false);
    feature.popup = tooltipPopup;
    tooltipPopup.fixedRelativePosition = true;
    tooltipPopup.anchor.offset.x = tooltipPopup.anchor.offset.x + 7;
    tooltipPopup.anchor.offset.y = tooltipPopup.anchor.offset.y + 5;
    tooltipPopup.calculateRelativePosition = function () { return 'br'; }
	tooltipPopup.panMapIfOutOfView = true;
    tbMaps.map.addPopup(tooltipPopup);

    var data = tbMaps.getMapFilters();
    data['location_id'] = feature.attributes.locationId;
    if (tbMaps.rankFilterIsActive()) {
        var features = tbMaps.getFeaturesByLocationId(feature.attributes.locationId);
        var dreamIds = [];
        for (var i = 0; i < features.length; i++) {
            dreamIds.push(features[i].attributes.dreamId);
        }
        data['dream_ids'] = dreamIds.join(',');
    }
    $.manageAjax.add('defaultQueue', {
        success: function(data) {
            var dreamList = [];
            $.each(data.dreams, function(idx, dream) { dreamList.push(dream.name) });
            if (dreamList.length == 1) {
                var dreamBox = '<div class="single-dream">'+truncate(dreamList[0], 8)+'</div>';
            } else {
                var dreamBox = '<ul>';
                for (var i = 0; i < dreamList.length; i++) {
                    var num = i + 1;
					if(num < 4){
						dreamBox += '<li>#'+num+') '+truncate(dreamList[i], 3)+'</li>';
					} else {
						break;
					}
                }
                dreamBox += '</ul>'+data.description;
            }
            $("#tooltipDreamBox").html(dreamBox);
        },
        url: tbMaps.settings.dreamTooltipURL,
        data: data,
        dataType: 'json'
    });
};

tbMaps.events.tooltipUnselect = function(event) {
    var feature = event.feature;
    if (feature && feature.popup){
        tbMaps.map.removePopup(feature.popup);
        feature.popup.destroy();
        delete feature.popup;
    }
};

tbMaps.events.onPopupClose = function(event) {
    tbMaps.select.unselectAll();
};

tbMaps.events.beforeFeatureRemoved = function(event) {
    tbMaps.events.tooltipUnselect(event);
    tbMaps.events.onFeatureUnselect(event);
};

tbMaps.events.onFeatureUnselect = function(event) {
    var feature = event.feature;
    if (feature && feature.popup) {
        tbMaps.map.removePopup(feature.popup);
        feature.popup.destroy();
        delete feature.popup;
    }
};

tbMaps.events.onFeatureSelect = function(event) {
    var feature = event.feature;
    tbMaps.events.tooltipUnselect(event);
    if (feature.attributes.locationId != undefined) {
        var landmarkId = feature.attributes.locationId;
    } else if (feature.attributes.airportId != undefined) {
        var landmarkId = feature.attributes.locationId;
    }
    var containerId = "landmark_description_"+landmarkId;
    var content = "<div class='landmark-description' id='"+containerId+"'>"+feature.attributes.description+"</div>";
    if (feature.attributes.name) {
        if(feature.attributes.name.length>35)
            feature.attributes.name=feature.attributes.name+'...';
        var content = "<h2 class='landmark-heading'>"+feature.attributes.name+"</h2>"+content;
    }
    var popup = new OpenLayers.Popup.FramedCloud("chicken", 
        feature.geometry.getBounds().getCenterLonLat(),
        new OpenLayers.Size(150,350),
        content, null, true, tbMaps.events.onPopupClose
    );
    if (feature.data.smallPopup)
    {
        popup.minSize = new OpenLayers.Size(300, 100);
        popup.maxSize = new OpenLayers.Size(300, 140);
        popup.autoSize = true;
    } else {
        popup.minSize = new OpenLayers.Size(355, 210);
    }
    feature.popup = popup;
    tbMaps.map.addPopup(popup);

    var data = tbMaps.getMapFilters();
    data['location_id'] = feature.attributes.locationId;
    data['dream_id'] = feature.attributes.dreamId;
    data['thingtodo_id'] = feature.attributes.thingtodoId;
    if (tbMaps.rankFilterIsActive()) {
        var features = tbMaps.getFeaturesByLocationId(feature.attributes.locationId);
        var dreamIds = [];
        for (var i = 0; i < features.length; i++) {
            dreamIds.push(features[i].attributes.dreamId);
        }
        data['dream_ids'] = dreamIds.join(',');
    }
    if (feature.attributes.locationId) {
        $.manageAjax.add('defaultQueue', {
            success: function(html) {
                if (tbMaps.settings.dreamPopupButtonsURL) {
                    loadDreamMapPopup(html, containerId, feature);
                } else {
                    $("#"+containerId).html(html);
                }
            },
            url: tbMaps.settings.popupDescriptionURL,
            data: data
        });
    }

    function loadDreamMapPopup(html, containerId, feature) {
        var $responseHtml = $(html);
        $("#"+containerId).html($responseHtml.find("#popup-dream-content"))
            .activatePopupNavigation()
            .loadPopupDescription()
            .loadPopupButtons();
    }
};

/******************************************************************************/
/****************************** MAPS METHODS **********************************/

tbMaps.getFeaturesByLocationId = function(locationId) {
    var features = [];
    for (var i = 0; i < this.locations.features.length; i++) {
        var feature = this.locations.features[i];
        if (feature.attributes && feature.attributes.locationId == locationId)
            features.push(feature);
    }
    return features;
};

tbMaps.rankFilterIsActive = function() {
    return $('[name="rank_map_filter"]').val() ? true : false;
};

/* Get closest feature's closest feature from layer (by default uses locations layer). */
tbMaps.getClosestFeature = function(feature, layer) {
    var min = 1000000000000000;
    var minFeat = null;

    if (!layer) {
        var layer = this.locations;
    }

    for (var i = 0; i < layer.features.length; i++) {
        if (feature != layer.features[i]) {
            var dist = Math.sqrt(
                Math.pow(feature.geometry.x - layer.features[i].geometry.x, 2) + 
                Math.pow(feature.geometry.y - layer.features[i].geometry.y, 2))
            if (dist < min) {
                minFeat = layer.features[i]; 
                min = dist;
            }
        }
    }
    return minFeat;
};

/* Get bounds of features without `notImportant` flag set on */
tbMaps.getFeaturesBounds = function(layer) {
    bounds = new OpenLayers.Bounds();
    for (var i = 0 ; i < layer.features.length ; i++)
    {
        if (layer.features[i].data.notImportant) {
            continue;
        }
        bounds.extend(layer.features[i].geometry);
    }
    return bounds;
}

/* Get features within range less then given. By default operates in locations layer. */
tbMaps.getFeaturesWithin = function(feature, range, layer) {
    if (!layer) {
        var layer = this.locations;
    }

    var circle = OpenLayers.Geometry.Polygon.createRegularPolygon(
        feature.geometry, range, 20, 0)
    var featuresWithin = [];
    for (var i = 0; i < layer.features.length; i++) {
        if (feature != layer.features[i] && circle.containsPoint(layer.features[i].geometry)) {
            featuresWithin.push(layer.features[i]);
        }
    }
    return featuresWithin;
};

tbMaps.getMapFilters = function() {
    var params = [];
    var filterList = ['category', 'subcategory_tree', 'category_tree', 
                      'status', 'group', 'region', 'rank'];
    $.each(filterList, function(idx, filterName) {
        var $filter = $("[name='"+filterName+"_map_filter']");
        // for selects
        if ($filter.length == 1 && $filter.get(0).nodeName == 'SELECT') {
            var filterValue = $filter.slice(0).val();
            if (filterValue != '') {
                params[filterName] = filterValue;
            }
        // for checkboxs
        } else if ($("input[name='"+filterName+"_map_filter']").length) {
            params[filterName] = [];
            $("input[name='"+filterName+"_map_filter']:checked").each(function(idx, el) {
                params[filterName].push(el.value);
            });
        }
    });
    // make sure array values are evaluated
    return copyObject(params);
};

tbMaps.getActiveLayer = function() {
    var scale = this.map.getScale();
    var layers = [this.locations, this.countries, this.regions];
    for (var i = 0 ; i < layers.length ; i++)
    {
        if (layers[i] && layers[i].getVisibility() && scale > layers[i].maxScale && scale <= layers[i].minScale)
            return layers[i];
    }
};

tbMaps.reloadMapFilters = function(zoom) {
    if (this.initialized) {
        logger('Reloading Map Filters...');
        var currentLayer = this.getActiveLayer();
        if (currentLayer == this.regions) {
            var secLayer1 = this.locations;
            var secLayer2 = this.countries;
        } else if (currentLayer == this.countries) {
            var secLayer1 = this.locations;
            var secLayer2 = this.regions;
        } else {
            var secLayer1 = this.countries;
            var secLayer2 = this.regions;
        }

        this.select.unselectAll();
        var params = this.getMapFilters();

        // Handle non default layers (refresh when necessary)
        if (secLayer1.getVisibility())
        {
            logger('Layer: '+secLayer1.name+' set as delayed task');
            secLayer1.protocol.options.params = params;
            this.delayedTasks.layersToRefresh.push(secLayer1.name);
        }

        if (secLayer1.getVisibility())
        {
            logger('Layer: '+secLayer2.name+' set as delayed task');
            secLayer2.protocol.options.params = params;
            this.delayedTasks.layersToRefresh.push(secLayer2.name);
        }

        // Handle current layer (immediate refresh)
        logger('Reloading Layer: '+currentLayer.name);
        var baseParams = currentLayer.protocol.options.params;
        params['bbox'] = baseParams['bbox'];
        var updateParams = params;
        updateParams['update'] = true;
        currentLayer.protocol.options.params = updateParams;
        currentLayer.refresh({
            force: true,
            params: params
        });
        currentLayer.protocol.options.params = params;
    }
};

tbMaps.reloadMap = function() {
    if (this.initialized) {
        logger("Reloading map");
        // TODO reload only visible layer
        if (this.select) {
            this.select.unselectAll();
        }
        this.locations.refresh({
            force: true
        });
        if (typeof this.regions != 'undefined' && this.regions) {
            this.regions.refresh({
                force: true
            });
        }
        return true;
    }
    return false;
};

/******************************************************************************/
/******************************* MAPS UTILS ***********************************/

$.fn.loadPopupButtons = function() {
    var dreamIDRaw = $("[id^='popup_dream']:visible", this).attr('id');
    if (dreamIDRaw) {
        var dreamID = dreamIDRaw.split('_').slice(-1)[0];
        var url = tbMaps.settings.dreamPopupButtonsURL+'?dream='+dreamID;
        var $buttonsBox = $("#popup_buttons_box_"+dreamID, this);
        if (!$buttonsBox.hasClass('content-loaded')) {
            $.manageAjax.add('defaultQueue', {
                success: function(response) {
                    $buttonsBox.html(response).addClass('content-loaded').activatePopupModalButtons();
                },
                url: url,
                cacheResponse: false
            });
        }
    }
    return this;
};

$.fn.loadPopupDescription = function() {
    var dreamIDRaw = $("[id^='popup_dream']:visible", this).attr('id');
    if (dreamIDRaw) {
        var dreamID = dreamIDRaw.split('_').slice(-1)[0];
        var url = tbMaps.settings.dreamDescriptionURL+'?dream='+dreamID+'&length=250';
        var $popupDescriptionBox = $("#popup_description_box_"+dreamID, this);
        if (!$popupDescriptionBox.hasClass('content-loaded')) {
            if ($popupDescriptionBox.text()) {
                $popupDescriptionBox.addClass('content-loaded');
            } else {
                $.manageAjax.add('defaultQueue', {
                    success: function(response) {
                        $popupDescriptionBox.html(response).addClass('content-loaded');
                    },
                    url: url
                });
            }
        }
    }
    return this;
};

$.fn.activatePopupNavigation = function() {
    var container = ".popup-dream-description";
    var thisOrg = this;
    if ($(container, this).length == 1) {
        $("#nav-container").html('');
    }
    $("#prev-dream").click(function() {
        var $current = $(container+":visible", thisOrg);
        $current.hide();
        $current.prev(container).show();
        if (!$current.prev(container).prev(container).length) {
            $(this).hide();
        }
        $("#next-dream").show();
        $(thisOrg).loadPopupDescription().loadPopupButtons();
        return false;
    });
    $("#next-dream").click(function() {
        var $current = $(".popup-dream-description:visible");
        $current.hide();
        $current.next(container).show();
        if (!$current.next(container).next(container).length) {
            $(this).hide();
        }
        $("#prev-dream").show();
        $(thisOrg).loadPopupDescription().loadPopupButtons();
        return false;
    });
    return this;
};

function reloadPopupButtons(dreamID) {
    var url = tbMaps.settings.dreamPopupButtonsURL+'?dream='+dreamID;
    var $buttonsBox = $("#popup_buttons_box_"+dreamID);
    $.manageAjax.add('defaultQueue', {
        success: function(response) {
            $buttonsBox.html(response).activatePopupModalButtons();
        },
        url: url,
        cacheResponse: false
    });
}

$.fn.activatePopupModalButtons = function() {
    $("[id^='check_off_dream']", this).each(function() {
        var dreamID = this.id.split('_').slice(-1)[0];
        $(this).popup({ 
            width: 750,
            onSuccessResponse: function(res, status, xhr, $form) {
                $.cookie('tbmap_last_lon', null, options={path: '/'});
                $.cookie('tbmap_last_lat', null, options={path: '/'});
                $.cookie('tbmap_last_zoom', null, options={path: '/'});
                $.cookie('tbmap_last_dreamid', null, options={path: '/'});
                reloadPopupButtons(dreamID);
            },
            beforeLoad: function($dialog) {
                var center = tbMaps.map.getCenter();
                var zoom = tbMaps.map.getZoom();
                $.cookie('tbmap_last_lon', center.lon, options={path: '/'});
                $.cookie('tbmap_last_lat', center.lat, options={path: '/'});
                $.cookie('tbmap_last_zoom', zoom, options={path: '/'});
                $.cookie('tbmap_last_dreamid', dreamID, options={path: '/'});
            }
        });
    });
    $("[id^='add_to_my_dream_list']", this).each(function() {
        var dreamID = this.id.split('_').slice(-1)[0];
        $(this).popup({ 
            width: 500,
            onSuccessResponse: function(res, status, xhr, $form) {
                $.cookie('tbmap_last_lon', null, options={path: '/'});
                $.cookie('tbmap_last_lat', null, options={path: '/'});
                $.cookie('tbmap_last_zoom', null, options={path: '/'});
                $.cookie('tbmap_last_dreamid', null, options={path: '/'});
                reloadPopupButtons(dreamID);
            },
            beforeLoad: function($dialog) {
                var center = tbMaps.map.getCenter();
                var zoom = tbMaps.map.getZoom();
                $.cookie('tbmap_last_lon', center.lon, options={path: '/'});
                $.cookie('tbmap_last_lat', center.lat, options={path: '/'});
                $.cookie('tbmap_last_zoom', zoom, options={path: '/'});
                $.cookie('tbmap_last_dreamid', dreamID, options={path: '/'});
            }
        });
    });
    return this;
};

$.fn.setGeoPosition = function(url, name, set_pin) {
    var obj_id = $(this).val();
    if (obj_id != '') {
        var url = url+'?'+name+'='+obj_id;
        $.manageAjax.add('defaultQueue', {
            success: function(data) {
                if (data != null) {
                    var x = data['point'][0];
                    var y = data['point'][1];
                    var zoom = data['zoom'];
                    if (typeof geodjango_point != 'undefined' && geodjango_point) {
                        geodjango_point.map.setCenter(new OpenLayers.LonLat(x, y), zoom);
                    }
                    if (set_pin) {
                        var point = new OpenLayers.Geometry.Point(x,y);
                        var feature = new OpenLayers.Feature.Vector(point);
                        if (typeof geodjango_point != 'undefined' && geodjango_point) {
                            geodjango_point.layers.vector.addFeatures(feature);
                        }
                    } 
                }
            },
            url: url,
            dataType: 'json'
        });
    }
};

$.fn.autoSetChoiceMarkerPos = function() {
    $(this).change(function() {
        var $pointContainer = $(this).parent().next();
        if ($pointContainer.hasClass("choice-position")) {
            var rawPoint = $pointContainer.text();
            if (rawPoint) {
                rawPoint = rawPoint.split(';');
                x = rawPoint[0];
                y = rawPoint[1];
                geodjango_point.map.setCenter(new OpenLayers.LonLat(x, y), 7);
                var point = new OpenLayers.Geometry.Point(x, y);
                var feature = new OpenLayers.Feature.Vector(point);
                geodjango_point.layers.vector.addFeatures(feature);
            } else {
                geodjango_point.clearFeatures();
            }
        }
    });
};

$.fn.mapOverlay = function(params) {
    var defaultParams = {
        regionSelect: true,
        categorySelect: false,
        url: null,
        onFirstLoad: null,
        onLoad: null,
        onClose: null
    };
    var settings = $.extend(defaultParams, params);
    var $obj = $(this);
    var $selector = $obj.find("#map-selector");

    if (!$selector.length) {
        return this;
    }

    if ($selector.hasClass('content-loaded')) {
        $selector.show();
        $(".map-overlay").show();
        if (settings.onLoad) {
            settings.onLoad();
        }
    } else {
        $.manageAjax.add('defaultQueue', {
            success: function(html) {
                $selector.addClass('content-loaded').show().html(html);
                var $overlay = $('<div class="map-overlay ui-widget-overlay"></div>');
                $overlay.css({'width': $obj.width(),
                              'height': $obj.height()});
                $obj.prepend($overlay);
                if (settings.onFirstLoad) {
                    settings.onFirstLoad();
                }
                $selector.find('#cancel-button').click(function() {
                    closeOverlayFilters();
                    return false;
                });
                $selector.find('form').submit(function() {
                    applyMapFilters();
                    closeOverlayFilters();
                    return false;
                });
                if (settings.onLoad) {
                    settings.onLoad();
                }
            },
            url: settings.url
        });
    }

    function closeOverlayFilters() {
        if (settings.onClose) {
            settings.onClose();
        }
        $(".map-overlay").hide();
        $selector.hide();
    }
    function applyMapFilters() {
        if (settings.categorySelect) {
            tbMaps.reloadMapFilters();            
        }
        if (settings.regionSelect) {
            var regionData = $selector.find('#id_region').val();
            if (regionData == '') {
                var centerLonLat = new OpenLayers.LonLat(tbMaps.settings.worldCenterPosX, tbMaps.settings.worldCenterPosY);
                tbMaps.map.setCenter(centerLonLat, tbMaps.settings.worldCenterPosZoom);
            } else {
                var splittedData = regionData.split('|');
                var posX = splittedData[0];
                var posY = splittedData[1];
                var regionLevel = splittedData[2];
                var zoom = regionLevel > 0 ? MAPS_SUBREGION_ZOOM : MAPS_REGION_ZOOM;
                tbMaps.map.setCenter(new OpenLayers.LonLat(posX, posY), zoom);
            }
        }
    }
    return this;
};

$.fn.initMapFilters = function() {
    setRegionInitPos();
    return this;
};

$.fn.updateMapFilters = function() {
    // TODO don't update when click triggered automatically at first load
    $(this).click(function() {
        var zoom = setRegionInitPos();
        tbMaps.reloadMapFilters(zoom);
        return false;
    });
    return this;
};

function setRegionInitPos() {
    var regionFilter = $("[name='region_map_filter']").val();
    var zoom;
    if (regionFilter) {
        var regionFilterSplitted = regionFilter.split('|');
        if (regionFilterSplitted.length > 2) {
            var level = parseInt(regionFilterSplitted[1]);
            var coords = regionFilterSplitted[2].split(',');
            zoom = level == 0 ? MAPS_REGION_ZOOM : MAPS_SUBREGION_ZOOM;
            tbMaps.map.setCenter(new OpenLayers.LonLat(coords[0], coords[1]), zoom);
        }
    }
    else
    {
        zoom = tbMaps.settings.worldCenterPosZoom;
        tbMaps.map.setCenter(new OpenLayers.LonLat(0, 0), zoom);
    }
    return zoom;
}


