//Venezia
 var startingLon=12.338747;
 var startingLat=45.434169;

//Cambridgezia
// var startingLon=-71.08525;
// var startingLat=42.362899;
var zoom=15;
var map, selectControl, selectedFeature, allWaypoints, itineraryListFeatures, contents;

//cut off a text title or summary at a certain length
function truncateAt(text, len){
    if (text == null || text.length <= len){
        return text;
    }
    return text.substring(0,len-1) + '&#x2026;'; //ellipsisize this
}

//zooms the map to the bounds of a list of features (e.g. from a layer)
function zoomToBounds (featurelist) {
    if ( featurelist && featurelist.length > 0) {
        var bounds = new OpenLayers.Bounds();
        $.each(featurelist, function(){
          bounds.extend(this.geometry.getBounds());
        });
        // a little wiggle room
        bounds.bottom -= 500; bounds.top +=500; bounds.right += 500; bounds.left-=500;
    map.zoomToExtent(bounds);
    }
}

//set the message area above the map
function setTopMessage(part1,part2){
    $("#map_realtime_message").fadeOut("fast", function(){
        $(this).html(part1+" ").fadeIn("fast");
    });
    $("#map_realtime_object").fadeOut("fast", function(){
        $(this).html(part2).fadeIn("fast");
    });
    
}

function onFeatureHover(e){
    if (e.feature.data.type=="content"){
        msg = "Cast: ";
    } else if (e.feature.data.type=="waypoint" || e.feature.data.type=="itineraryWaypoint"){
        msg="Place: ";
    }
    setTopMessage(msg, e.feature.data.title);

}

function onFeatureUnhover(e){
    setTimeout(function(){
        setTopMessage("Map showing recent", map.manager.currentMode);
    }, 10);//slight delay so it always shows even if weird race cond. with an unhover
}

function onFeatureSelect(feature) {
  //nuke existing popups
  for (var i = map.popups.length - 1; i >= 0; i--){
    map.popups[i].destroy();
  };
  selectedFeature = feature;
  arr = jQuery.map(feature.data.category, function (a) { return a.name; });
  popup = new OpenLayers.Popup.FramedCloud("popup", 
  feature.geometry.getBounds().getCenterLonLat(),
  null,
  "<h3>"+feature.data.title+"</h3><p style='font-size:.8em'>"+feature.data.description+"<br /><strong>In category: "+arr.join(", ")+"</strong></p>",
  null, true, onPopupClose);
  feature.popup = popup;
  map.addPopup(popup);
}

//called when in the itinerary's "add feature to this itinerary" mode
function onFeatureSelectAdd(feature) {
  //nuke existing popups
  for (var i = map.popups.length - 1; i >= 0; i--){
    map.popups[i].destroy();
  };
  selectedFeature = feature;
  arr = jQuery.map(feature.data.category, function (a) { return a.name; });
  popup = new OpenLayers.Popup.FramedCloud("popup", 
  feature.geometry.getBounds().getCenterLonLat(),
  null,
  "<h3>"+feature.data.title+"</h3><p style='font-size:.8em'>"+feature.data.description+"<br /><strong>In category: "+arr.join(", ")+"</strong></p>"+
  '<a href="#" id="addwaypoint_'+feature.fid+'">&rarr; Add to this Itinerary</a>', 
  null, true, onPopupClose);
  feature.popup = popup;
  map.addPopup(popup);
  $("#addwaypoint_"+feature.fid).unbind('click').click(function(){
    onFeatureUnselect(feature);
    map.manager.currentManager.addToActiveItinerary(feature);
    map.manager.currentManager.loadBBoxContents=null;
    return false;
  });
}

function setfavorite(type, id, value, callback){
    if (type=='cast') { type = 'content';}
    $("#map_sidebar .loader").show("blind",{},500);
    $.ajax({
      type:'PUT',
      data : ""+value,
      dataType:'text',
      contentType:'text/x-json',
      processData:false,
      url : '/api/'+type+'/'+id+'/is_favorite/',
       success: function(){
         $("#map_sidebar .loader").hide("blind",{},500);
         callback();
       }

    });
   return false;
}

function favorite(type, id, callback){
  return setfavorite(type, id, true, callback);
}
function unfavorite(type, id, callback){
  return setfavorite(type, id, false, callback);
}


function onContentSelect(feature) {
  //when you're clicking on a photo, video, etc.
  //nuke existing popups
  for (var i = map.popups.length - 1; i >= 0; i--){
    map.popups[i].destroy();
  };
  selectedFeature = feature;
  arr = jQuery.map(feature.data.category, function (a) { return a.name; });
  var video_url;
  if (feature.data.content_flv!=null && feature.data.content_flv.url !=null){
     video_url = feature.data.content_flv.url;
  } else {
     video_url = feature.data.content.url; //use the uncompressed, otherwise
  }
  orig_url = feature.data.content.url;
  if (feature.data.is_favorite){
    favlink = '<span class="unfavoritecastcontent" id="unfavoritecastbubble_'+feature.fid+'"><a href="#"><img style="width:63px;height:18px;" src="/static/img/ico_fave2.png" alt="Unfavorite this" /></a></span>';
  } else {
   favlink = '<span class= "favoritecastcontent" id="favoritecastbubble_'+feature.fid+'"><a href="#"><img style="width:63px;height:18px;" src="/static/img/ico_fave1.png" alt="Favorite this" /></a></span>';
  }
  popup_html = '<h3>'+feature.data.title+'</h3><p style="font-size:.8em">'+
    feature.data.description+'<br /><strong>by <a href="/users/'+feature.data.author.username+'">'+feature.data.author.username+'</a>'+
    '</strong></p><p style="width:105px;" class="img-left"><a class="content_play_link" href="#"><img src="'+
    feature.data.thumbnail_url+'" alt="thumbnail" width="110"/><br/><img src="/static/img/ico_play.png" alt="thumbnail" /></a>';
  if (orig_url != null && orig_url.length > 3 && orig_url.substr(-3,2)=="3g"){
      popup_html = popup_html + '<br /><br /><a class="darklink" href="'+orig_url+'" >'+
      '<span class="ui-icon-arrowstop-1-s ui-icon" style="float:left;">&nbsp;</span>'+
      '<span style="float:left;">download</span></a>';
  }
  popup_html = popup_html + '</p><p>'+favlink+'</p>'+
    '<div id="overlay_popup_'+feature.fid+'" title="'+
    feature.data.title+'" style="display:none;">'+
    '<a id="player_popup_'+feature.fid+'" href="'+
    video_url+'">&nbsp;</a>'+
    '</div>';
  
  popup = new OpenLayers.Popup.FramedCloud("popup", 
  feature.geometry.getBounds().getCenterLonLat(),
  null,
  popup_html,
  null, true, onPopupClose);
  feature.popup = popup;
  map.addPopup(popup);
  $("#popup .favoritecastcontent a").unbind('click').click(function(){return onfavclick(this, 'cast', feature);});
  $("#popup .unfavoritecastcontent a").unbind('click').click(function(){return onunfavclick(this, 'cast', feature);});
  
  // setup button action. it will fire our overlay  
  $(".content_play_link").unbind('click').click(function(){
    // install flowplayer into flowplayer container 
    var player = $f("player_popup_"+feature.fid, "/static/flowplayer/flowplayer-3.1.1.swf", {clip: {  scaling: 'fit'}});
    $("#overlay_popup_"+feature.fid).dialog({
      width:640,
      height:518,
      autoOpen:false,
      // when overlay is opened, load our player 
      open: function(event, ui) {
        player.load(); 
      }, 
      // when overlay is closed, unload our player 
      close: function(event, ui) { 
        player.unload(); 
      }
    }).dialog('open');
    return false;
  });
}

function onPopupClose(evt) {
  // map.selectControl.unselect(selectedFeature);
  $.each(map.popups, function(){this.destroy();});
}
function onFeatureUnselect(feature) {
  map.removePopup(feature.popup);
  feature.popup.destroy();
  feature.popup = null;
}

//Initialise the 'map' object
function init() {
  function mapEvent(event) {
      //update the view
      if (map != null && map.manager != null && map.manager.currentManager != null && this.manager.currentManager.loadBBoxContents != null){
          this.manager.currentManager.loadBBoxContents();
      }
  }    
  map = new OpenLayers.Map ("map", {
    eventListeners: {
      "moveend": mapEvent
    },
    controls:[
    new OpenLayers.Control.Navigation(),
    new OpenLayers.Control.PanZoomBar()],
    maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34),
    maxResolution: 156543.0399,
    numZoomLevels: 19,
    units: 'm',
    projection: new OpenLayers.Projection("EPSG:900913"),
    displayProjection: new OpenLayers.Projection("EPSG:4326")
  } );
  var baselayer = new OpenLayers.Layer.TMS(
    "OpenStreetMap (Mapnik)",
    "http://a.tile.openstreetmap.org/",
    {
      type: 'png', getURL: osm_getTileURL,
      displayOutsideMaxExtent: true,
      attribution: 'Data by <a href="http://openstreetmap.org/">OpenStreetMaps</a> <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
    }
  );
  map.addLayer(baselayer);
  map.addControl(new OpenLayers.Control.Attribution());
  var category_mappings = {
      "bridges" : "icon_category_bridge.png",
      "piazzas & campos" : "icon_category_combo.png",
      "food & drink" : "icon_category_dining.png",
      "nightlife" : "icon_category_martini.png",
      "buildings & monuments" : "icon_category_monument.png",
      "museum" : "icon_category_museum.png",
      "religious spots" : "icon_category_religious.png",
      "shopping" : "icon_category_shopping.png",
      "theatre" : "icon_category_theatre.png" 
  };
  //function to set external image for features
  var getType = function(feature) {
    //if it's a content, just use a lookup marker for now
    if (feature.data.type=="content"){
      return "/static/img/mapmarkers/cast/icon_cast_up.png";
    } else if (feature.data.type == "itineraryWaypoint"){
      //if it's in an itinerary, mark it as such
        return "/static/img/mapmarkers/itinerary/icon-itinerary-"+feature.data.itineraryOrder+"-"+feature.data.position+".png";
    }
    else if (feature.data.type == "waypoint") {
        //see if we have an icon for this
        category_label = null;
        if (feature.data.category != null && feature.data.category.length > 0){
            category_label = category_mappings[feature.data.category[0].name.toLowerCase()];
        } 
        if (category_label != null){
          return "/static/img/mapmarkers/category/"+category_label;
        } else {
          return "/static/img/mapmarkers/category/icon_category_wildcard.png";
        }
        
    } else { //who knows?
       return "/static/img/mapmarkers/category/icon_category_wildcard.png";
    }
  };
  // create the layer styleMap by giving the default style a context
  var context = {
    getType: getType,
    getHoverType: function(feature) {
        //use the "over" image if it's a Cast
        if (feature.data.type=="content"){
            return "/static/img/mapmarkers/cast/icon_cast_over.png";
        }
        return getType(feature);
    }
  };


    var style = new OpenLayers.Style({
        externalGraphic: "${getType}", // using context.getType(feature)
        pointRadius: 22,
        graphicYOffset:-40
        }, {context: context});
    var hoverstyle = new OpenLayers.Style({
          externalGraphic: "${getHoverType}",
          pointRadius:28,
          graphicYOffset:-50,
          cursor: "pointer"
      }, {context:context});
    var selectstyle = new OpenLayers.Style({
            externalGraphic: "${getHoverType}",
            pointRadius:28,
            graphicYOffset:-50
          }, {context:context});
    //always show selected marker in gold
    //var selectedStyle = new OpenLayers.Style(template, {context: {getType: function(f){return lookup[2];}}});

    vector_layer = new OpenLayers.Layer.Vector(
      "Waypoints",
      {
        styleMap: new OpenLayers.StyleMap({"default":style, "select": selectstyle, "temporary":hoverstyle}),
        isBaseLayer: false
        //rendererOptions: {yOrdering: true}
      }
    );

    map.addLayers([vector_layer]);
    //map.addControl(new OpenLayers.Control.MousePosition());
    selectControl = new OpenLayers.Control.SelectFeature(vector_layer,
      {onSelect: onFeatureSelect, onUnselect: onFeatureUnselect}, {title:'View a Place'});
    map.addControl(selectControl);
    // hoverControl = new OpenLayers.Control.SelectFeature(vector_layer,
    //     {multiple: false, hover: true, onSelect: onFeatureHover, onUnselect: onFeatureUnhover});
    // map.addControl(hoverControl);
    // map.hoverControl = hoverControl;
    //hoverControl.activate();
    
    map.selectControl = selectControl; //for easy access
    
    var highlightCtrl = new OpenLayers.Control.SelectFeature(vector_layer, {
        hover: true,
        highlightOnly: true,
        renderIntent: "temporary",
        eventListeners: {
            beforefeaturehighlighted: onFeatureHover,
            // featurehighlighted: onFeatureHover,
            featureunhighlighted: onFeatureUnhover
        }
    });
        
    map.addControl(highlightCtrl);
    highlightCtrl.activate();
    
  if( ! map.getCenter() ){
    var lonLat = new OpenLayers.LonLat(startingLon, startingLat).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());
    map.setCenter (lonLat, zoom);
  }
  
  //create initial mapManager and populate
  map.manager = new mapManager(map, vector_layer, "#map_sidebar");
  selectControl.activate();
}

function osm_getTileURL(bounds) {
    var res = this.map.getResolution();
    var x = Math.round((bounds.left - this.maxExtent.left) / (res * this.tileSize.w));
    var y = Math.round((this.maxExtent.top - bounds.top) / (res * this.tileSize.h));
    var z = this.map.getZoom();
    var limit = Math.pow(2, z);

    if (y < 0 || y >= limit) {
        return OpenLayers.Util.getImagesLocation() + "404.png";
    } else {
        x = ((x % limit) + limit) % limit;
        return this.url + z + "/" + x + "/" + y + "." + this.type;
    }
}

function PathsManager(featureLayer){
    //this.featureLayer = featureLayer;
    this.featureLayer = new OpenLayers.Layer.Vector("Trails");
    map.addLayer(this.featureLayer);    
    var self = this;
    this.loadBBoxContents = function(){
        return false; //not implemented yet
    };
    this.showTrails = function(jsondata){
        var pathstyle = {
            strokeColor: "#FF0000",
            strokeWidth: 8,
            strokeDashstyle: "solid",
            pointRadius: 16,
            pointerEvents: "visibleStroke",
            strokeOpacity: 0.8
        };
        var pathcolors = ["#dd2244","#44dd22", "#3399aa", "#FF0000", "#0000FF"];
        geojson_format = new OpenLayers.Format.GeoJSON({internalProjection: map.getProjectionObject(), 
              externalProjection: map.displayProjection});
        paths = geojson_format.read(jsondata);
        for (var i = paths.length-1; i>=0; i--){
            tmp = {};
            paths[i].style = $.extend(tmp, pathstyle, {strokeColor:pathcolors[i]});
        }
        self.featureLayer.destroyFeatures();
        self.featureLayer.addFeatures(paths);
        zoomToBounds(self.featureLayer.features);
    };
    this.loadTrails = function(){
        $.get('/api/tracks/', function(jsondata){
            self.showTrails(jsondata);
        });
    };
    this.loadUserTrails = function(author){
        $.get('/api/tracks/by/'+author+'/', function(jsondata){
            self.showTrails(jsondata);
        });        
    };

}

//deal with showing/managing things inside an active itinerary
function activeItineraryManager(sidebar, featureLayer, itineraryId, itineraryOrder){
  
  this.showItineraryWaypointsOnMap = function(){
    self.featureLayer.destroyFeatures();
    if (self.activeItineraryFeatures != null && self.activeItineraryFeatures.length>0){
      self.featureLayer.addFeatures(self.activeItineraryFeatures);  
      //zoom to bounds (hop to this itinerary)
      zoomToBounds(self.featureLayer.features);
      map.selectControl.onSelect = onFeatureSelect;
    }
  };
  
  this.loadItineraryWaypoints = function(jsondata){
    //once we have json data loaded for an itinerary, display it
    geojson_format = new OpenLayers.Format.GeoJSON({internalProjection: map.getProjectionObject(), 
            externalProjection: map.displayProjection});
    if (jsondata.ordered_waypoints != null && jsondata.ordered_waypoints.features!= null && jsondata.ordered_waypoints.features.length > 0){
      self.activeItineraryFeatures = geojson_format.read(jsondata.ordered_waypoints);  
    } else {
      self.activeItineraryFeatures = [];
    }
    
    itineraryTitle = jsondata.title;
    $("#waypoints_list .heading h3").html(itineraryTitle).attr('id', 'itinerarytitle_'+jsondata.id);
    //find our order from the position in the list
    for (var j = self.activeItineraryFeatures.length - 1; j >= 0; j--){
       self.activeItineraryFeatures[j].data.position = j+1;
       self.activeItineraryFeatures[j].data.itineraryOrder = self.activeItineraryOrder;
       self.activeItineraryFeatures[j].data.type = "itineraryWaypoint";
    }   
    self.showItineraryWaypointsOnMap();
    $("#waypoints_list ul").html(''); //clear the itinerary features list in case there's anything in there
      if (self.activeItineraryFeatures != null && self.activeItineraryFeatures.length>0){
        $.each(self.activeItineraryFeatures, function(){
          $("#waypoints_list ul").append('<li id="waypointlistitem_'+this.fid+'">'+this.data.title+"</li>");
        });
      }
      $("#waypoints_list ul").disableSelection(); //don't allow text selection
      self.makeWaypointsSelectable();      
      $("#waypoints_list").show("blind",{},500);
      $("#map_sidebar .loader").hide("blind",{},500);
      //the back to list link
      $("#show_itineraries").unbind('click').click(function(){
        map.manager.showItineraries();
        return false;
      });
      
      //let owners edit titles of their itineraries
      $('.edit').editable(function(value,settings){
        url = '/api/itineraries/'+this.id.split('_')[1]+"/title/";
        $.ajax({
          type:'PUT',
          data : $.compactJSON(value),
          dataType:'text',
           contentType:'text/x-json',
           processData:false,
           url : url,
          success: function(msg){
           return value;
           },
           error: function(msg){
             alert("sorry - there was an error editing the title! Try that again.");
           }
          });
          return value;
          
      } , {
          
      });

      $("#add_to_itinerary").unbind('click').click(function(){
        //look for nearby waypoints
        var lonLat = map.getCenter().transform(map.getProjectionObject(), map.displayProjection);        
        self.loadBBoxContents = function(){
            bounds = map.calculateBounds().transform(map.getProjectionObject(), map.displayProjection);
            url = "/api/waypoints/within/"+bounds.toBBOX()+"/?count=30";
          
            $.getJSON(url, function(featurecollection){
              geojson_format = new OpenLayers.Format.GeoJSON({internalProjection: map.getProjectionObject(), 
                externalProjection: map.displayProjection});
                allWaypoints = geojson_format.read(featurecollection);
                //only show the waypoints not already in the current itinerary
                $.each(allWaypoints, function(){this.data.type="waypoint";});
                var itineraryFids = $.map(self.activeItineraryFeatures, function(a){return a.fid;});
                var otherMapFids = $.map(vector_layer.features, function(a){return a.fid;});
                //also skip things already on the map
                allWaypoints = $.grep(allWaypoints, function(a){
                  return ( $.inArray(a.fid, itineraryFids) == -1 && $.inArray(a.fid, otherMapFids) == -1 ); 
                });
                vector_layer.addFeatures(allWaypoints);
                // zoomToBounds(allWaypoints);
                map.selectControl.onSelect = onFeatureSelectAdd;
            });  
        };
        self.loadBBoxContents();
        return false;
      });
  };
  
  /*
  * Called to make the waypoints list sortable or deletable.
  */
  this.makeWaypointsSortable = function(){
      self.loadBBoxContents=null; //in case we're in add mode, stop showing unrelated waypoints
      self.showItineraryWaypointsOnMap();
      
    $("#reorder_waypoints").unbind('click').html('Done Sorting').click(function(){
      self.makeWaypointsSelectable();
      return false;
    });

    $("#waypoints_list li").unbind('click');
    $("#waypoints_list li").prepend('<span class="ui-icon ui-icon-grip-dotted-vertical"></span>').append('<a class="delete_waypoint" href="#"><span class="ui-icon ui-icon-trash"></span></a>');
    //when a trash waypoint link is clicked, delete it
    $(".delete_waypoint").unbind('click').click(function(){
      li = jQuery(this.parentNode);
      fid = parseInt(this.parentNode.id.split('_')[1], 10);
      //remove this feature from the active features list
      self.activeItineraryFeatures = $.grep(self.activeItineraryFeatures, function(f){return f.fid != fid;});
            
      li.hide("blind", {}, 500, function(){
        li.remove();
        self.putWaypointsFromList();
      });
      return false;
    });

    $("#waypoints_list ul").sortable({
      handle:'span',    
      //on updated sorted order, PUT the new order to the server
      update: function(event, ui) {
        new_order_fids = $.map($("#waypoints_list li"), function(a){return a.id.split('_')[1];});
        newfeats = [];
        $.each(new_order_fids, function(i, fid){
            newfeats[i] = $.grep(self.activeItineraryFeatures, function(f){return f.fid == fid;})[0];
            newfeats[i].data.position = i+1;
        });
        self.activeItineraryFeatures = newfeats;
        self.putWaypointsFromList();
      }
    });
  };
  
  this.makeWaypointsSelectable = function(){
    $("#reorder_waypoints").unbind('click').html('Reorder/Delete').click(function(){
      self.makeWaypointsSortable();
    });
    $("#waypoints_list ul").sortable('destroy');
    $("#waypoints_list li").unbind('click').click(function(){
      $("#waypoints_list li").removeClass("ui-selected");
      $(this).addClass("ui-selected");
      //find this in the waypoints list
      //make it selected
      //pan to it
      fid = parseInt(this.id.split('_')[1], 10);
      feature = $.grep(self.activeItineraryFeatures, function(f){return f.fid == fid;})[0];

      map.panTo(feature.geometry.getBounds().getCenterLonLat());
      onFeatureSelect(feature);
      return false;
    });
    //$("#waypoints_list ul").selectable();
    $("#waypoints_list li span.ui-icon").remove(); //get rid of the draggable handle
  };
  
  this.loadItinerary = function(){
    $("#map_sidebar .loader").show("blind",{},500);
     $('#map_sidebar>.sidebar_list:visible').hide("blind", {}, 500, function(){
      url = "/api/itineraries/"+self.activeItineraryId+"/";
      $.getJSON(url, function(data){
        self.loadItineraryWaypoints(data);
      });
    });
  };
  
  this.sidebar = sidebar;
  this.featureLayer = featureLayer;
  this.activeItineraryFeatures = []; //the features in the active itinerary
  this.activeItineraryId = itineraryId;
  this.activeItineraryOrder = order;
  this.addMode = false; //are we in the mode where we're adding waypoints to an itinerary?
  var self = this;
  this.loadItinerary();

  //add a waypoint to the active itinerary
  this.addToActiveItinerary = function(feature){
    $("#map_sidebar .loader").show("blind",{},500);
    geojson_format = new OpenLayers.Format.GeoJSON({internalProjection: map.getProjectionObject(), 
      externalProjection: map.displayProjection});
    //make a list of ids
    feature.data.type = "itineraryWaypoint";
    feature.data.position = self.activeItineraryFeatures.length+1;
    feature.data.itineraryOrder = self.activeItineraryOrder;
    self.activeItineraryFeatures[self.activeItineraryFeatures.length] = feature;
    //update list of waypoints with the values we have now
    $("#waypoints_list ul").append('<li id="waypointlistitem_'+feature.fid+'">'+feature.data.title+"</li>");
    self.putWaypointsFromList();
  };
  
  this.putWaypointsFromList = function(){
    //gather our list of waypoints
    itineraryFeatureIds = $.map(self.activeItineraryFeatures, function(item){
      return {'id' : item.fid};
    });
    //update on server
    $("#map_sidebar .loader").show("blind",{},500);
    //PUT to server
    $.ajax({
      type:'PUT',
      data : $.compactJSON({'ordered_waypoints':{"type": "FeatureCollection", "features": itineraryFeatureIds}}),
      dataType:'text',
       contentType:'text/x-json',
       processData:false,
      url : '/api/itineraries/'+this.activeItineraryId+'/',
      success: function(msg){
        //get back the new Itinerary object
        //load our waypoints back into the map
        self.showItineraryWaypointsOnMap();
        $("#map_sidebar .loader").hide("blind",{},500);
      }
    });
  };
}

//Show content in the map
function contentManager(sidebar, featureLayer){
  this.displayContents = function(jsondata){
    //display a FeatureList of items on our map.
    geojson_format = new OpenLayers.Format.GeoJSON({internalProjection: map.getProjectionObject(), 
            externalProjection: map.displayProjection});
    contents = geojson_format.read(jsondata);
    // if (!$("#itineraries_list:visible").length){ //if the waypoints list isn't open, hide the itineraries list
    //   $("#itineraries_list").hide("blind",{}, 500);
    // }
    // $("#waypoints_list ul").html('');
    //mark them as contents
    if (contents == null){
        contents = [];
    }
    $.each(contents, function(){
      this.data.type='content';
    });
    //   $("#waypoints_list ul").append('<li id="content_'+this.fid+'">'+this.data.title+"</li>");
    // });
    map.selectControl.onSelect = onContentSelect;
    self.featureLayer.destroyFeatures();
    self.featureLayer.addFeatures(contents);
    //self.makeContentsSelectable();
    // $("#waypoints_list").show("blind",{}, 500);
    $("#map_sidebar .loader").hide("blind",{},500);
  };
  
  this.loadContents = function(){
    $("#map_sidebar .loader").show("blind",{},500);
    //Load some Content items within the visible map.
    bounds = map.calculateBounds().transform(map.getProjectionObject(), map.displayProjection);
    //center = bounds.getCenterLonLat();
    url = "/api/content/within/"+bounds.toBBOX()+"/?required=content_flv";
    $.getJSON(url, function(data){
      self.displayContents(data);
    });
    //use this as the bbox loader
    self.loadBBoxContents = self.loadContents;
  };
  
  this.loadUserContents = function(author){
      self.author = author;
      $("#map_sidebar .loader").show("blind",{},500);
      bounds = map.calculateBounds().transform(map.getProjectionObject(), map.displayProjection);
      url = "/api/content/by/"+author+'/?required=content_flv'+"/within/"+bounds.toBBOX()+"/";
      $.getJSON(url, function(data){
        self.displayContents(data);
      });
      //use this as the bbox loader
      self.loadBBoxContents = function(){return self.loadUserContents(author);};
  };

  // //click a conent in the side list to pop to it on the map, open its overlay, and immediately start playing it
  // makeContentsSelectable = function(){
  //   $("#waypoints_list li").unbind('click').click(function(){
  //     $("#waypoints_list li").removeClass("ui-selected");
  //     $(this).addClass("ui-selected");
  //     //find this in the waypoints list
  //     //make it selected
  //     //pan to it
  //     fid = parseInt(this.id.split('_')[1])
  //     feature = $.grep(vector_layer.features, function(a){return a.fid == fid; })[0]
  //     map.panTo(feature.geometry.getBounds().getCenterLonLat());
  //     onContentSelect(feature);
  //   });
  //};
  this.sidebar = sidebar;
  this.featureLayer = featureLayer;
  var self = this;
}

//deal with showing/hiding/appending different things to the sidebar lists
//call the necessary managers from here to do other stuff
function mapManager(map, featureLayer, sidebarSelector){
  this.loadItinerariesFrom = function(url){
    $("#itineraries_list:visible").hide("blind", {}, 500); //hide the itin list if it's visible

    $.getJSON(url, function(itineraries){
      geojson_format = new OpenLayers.Format.GeoJSON({internalProjection: map.getProjectionObject(), 
            externalProjection: map.displayProjection});
      //for each itinerary,
      //assign its features a order & number and parse them
      self.itineraryListFeatures = [];
      if (itineraries.length < 1){
        $("#itineraries_list").show("blind", {}, 500);
        return; //since we don't have anything to show
      }
      
      $("#itineraries_list li").remove();
      for (var i = itineraries.length - 1; i >= 0; i--){
          itinerary = itineraries[i];
        featurecollection = itinerary.ordered_waypoints;
        //how many waypoints in the itin?
        if (featurecollection != null && featurecollection.features != null){
            waypointcount = featurecollection.features.length;
        } else {
            waypointcount = 0;
        }
        //show in sidebar
        $("#itineraries_list ul").prepend('<li>'+
          '<img class="meta" src="/static/img/mapmarkers/itinerary/icon_itinerary_'+(i+1)+'.png" alt="'+itinerary.title+'" width="30" />'+
          '<div class="meta">'+
            '<div class="title"><a class="itinerarylink" id="itinerarylink_'+itinerary.id+'" href="#">'+truncateAt(itinerary.title, 24)+'</a></div>'+
            '<div class="waypoints">'+waypointcount+' places</div>'+
            '<div class="abstract">'+truncateAt(itinerary.description,52)+'</div>'+
          '</div>'+
        '</li>');
    
        if (featurecollection.features != null){
            waypoints = geojson_format.read(featurecollection);
            order = i+1;
            for (var j = waypoints.length - 1; j >= 0; j--){
              waypoints[j].data.position = j+1;
              waypoints[j].data.itineraryOrder = order;
              waypoints[j].data.type = "itineraryWaypoint";
            }
            self.itineraryListFeatures = self.itineraryListFeatures.concat(waypoints);
          }
        }
      $("#itineraries_list").show("blind", {}, 500);
      self.featureLayer.destroyFeatures();
      self.featureLayer.addFeatures(self.itineraryListFeatures);
      map.selectControl.onSelect = onFeatureSelect;
      //show the itineraries in the sidebar
      //add onclicks to the Itineraries
      //when an itinerary link is clicked, load it
      $("#itineraries_list li a").unbind('click').click(function(){
        itineraryId = this.id.split('_')[1];
        //what # item is this in the list?
        order = $("#itineraries_list li a.itinerarylink").index(this)+1;
        //console.log("order is: "+order);
        self.currentManager = new activeItineraryManager(self.sidebar, self.featureLayer, itineraryId, order);
        return false;
      });
    });
    $("#create_itinerary_link").unbind('click').click(function(){
      //create an empty itinerary
      //PUT to server
      $.ajax({
        type:'POST',
        data : '{}', //start with a blank object
        dataType:'json',
         contentType:'text/x-json',
         processData:false,
        url : '/api/itineraries/',
        success: function(itinerary){
          //get back the new Itinerary object
          $("#map_sidebar .loader").hide("blind",{},500);
          //load our waypoints back into the map
          itineraryId = itinerary.id;
          order = 5;
          self.currentManager = new activeItineraryManager(self.sidebar, self.featureLayer, itineraryId, order);
        }
      });
      return false;
    });
  };
  
  //load featured itineraries
  this.loadItineraries = function (){
      return this.loadItinerariesFrom('/api/itineraries/?order=-created&order=-is_sponsored&order=-priority&count=4');
  };
    
  this.loadUserItineraries = function(author){
    return this.loadItinerariesFrom('/api/itineraries/by/'+author+'/?count=4&order=-created');
  };
  
  
  
  //e.g. back to the "show itineraries" button
  this.showItineraries = function(){
    self.currentManager = null;
    self.currentMode = "itineraries";
    setTopMessage("Map showing recent", self.currentMode);
    //vector_layer.addFeatures(self.itineraryListFeatures);
    //hide other lists
    $('#map_sidebar>.sidebar_list:visible').hide("blind",{},500, function(){
      self.loadItineraries();
      $("#waypoints_list ul").html(""); //clear the contents of the waypoints list
    });
  };
  
  this.showUserItineraries = function(author){
    self.currentManager = null;
    self.currentMode = "itineraries";
        setTopMessage("Map showing recent", self.currentMode);
    //vector_layer.addFeatures(self.itineraryListFeatures);
    //hide other lists
    $('#map_sidebar>.sidebar_list:visible').hide("blind",{},500, function(){
      self.loadUserItineraries(author);
      $("#waypoints_list ul").html(""); //clear the contents of the waypoints list
    });
  };
  
  this.showContents = function(){
    self.currentManager = new contentManager(self.sidebar, self.featureLayer);
    self.currentManager.loadContents();
    self.currentMode = "casts";
    setTopMessage("Map showing recent", self.currentMode);
    
    
  };
  this.showContentsSidebar = function(){
    $('#map_sidebar>.sidebar_list:visible').hide("blind",{},500, function(){
        $('#contents_list').show("blind", {}, 500);
    });
  };
  
  this.showUserContents = function(author){
    self.currentManager = new contentManager(self.sidebar, self.featureLayer);
    self.currentManager.loadUserContents(author);
    self.currentMode = "casts";
    setTopMessage("Map showing recent", self.currentMode);
  };
  
  this.showTrails = function(){
      self.currentManager = new PathsManager(self.featureLayer);
      self.currentManager.loadTrails();
      self.currentMode = "trails";
          setTopMessage("Map showing recent", self.currentMode);
  };
  this.showUserTrails = function(author){
      self.currentManager = new PathsManager(self.featureLayer);
      self.currentManager.loadUserTrails(author);
      self.currentMode = "trails";
          setTopMessage("Map showing recent", self.currentMode);
  };
  
  
  //Put the selected cast on the map, whether it's in our current display or not
  this.loadCast = function(castid){
      //see if we have to feature on the map already
      onmap = $.grep(self.featureLayer.features, function(f){return (f.data != null && f.data.type=="content" && f.fid == castid); });
      if (onmap.length > 0){
          //pop to it on the map
          onmap[0].data.type="content"; //if it wasn't marked that way already
          onContentSelect(onmap[0]);
      } else {
        //get the feature
        $.getJSON('/api/content/'+castid+'/', function(data){
              geojson_format = new OpenLayers.Format.GeoJSON({internalProjection: self.map.getProjectionObject(), 
              externalProjection: self.map.displayProjection});
              var feature = geojson_format.read(data)[0];
              feature.data.type = "content"; //since it's technically a list
              self.featureLayer.addFeatures([feature]);
              onContentSelect(feature);              
        });
      }
      return false;
    };
  
  
  this.map = map;
  this.featureLayer = featureLayer;
  this.sidebarSelector = sidebarSelector;
  this.sidebar =  $(sidebarSelector);
  this.itineraryFeatures = [];
  this.itineraryListFeatures;
  this.itinerariesPage = 1; //paging?
  this.currentManager = null; //eg contentManger or itineraryManager
  var self = this; //cache a copy so we can get to this in jquery

  //test
  //this.currentManager = new PathsManager(self.featureLayer);
  //this.loadItineraries(); //on init
  
}


// function showContentColumns(){
//     $("#column_main").html('<div class="header clear"><h3>Casts Archive</h3>'+
//     '<span class="sort">Sort by: <a href="#">view count</a> | <a href="#">featured casts</a> | <a href="#">newest</a> | <a href="#">oldest</a></span>'+
//  '</div><div class="peak-dark-grey"></div><div class="streams"><ul class="stream thirds first"><li></li></ul>'+
//  '<ul class="stream thirds"><li></li></ul><ul class="stream thirds last"><li></li></ul>');
// }

//clicking on a link to a content in the bottom area plays it in an overlay
function bindContentAreaPlays(){
  $("a.showcastcontent").unbind('click').click(function(){
      id = this.id.split('_')[1];
      map.manager.loadCast(id);
      //scroll up to the map area
      $('html,body').animate({scrollTop: $("a[name=map_area]").offset().top}, 1000);
      return false;
  });
 }
function bindContentAreaItineraries(){
    $('a.showitincontent').unbind('click').click(function(){
      itineraryId = this.id.split('_')[1];
      order = 5; //always show this in the #5 color
      map.manager.currentManager = new activeItineraryManager(map.manager.sidebar, map.manager.featureLayer, itineraryId, order);
  });
}

//favorite something, link inside an appropriate class/ided div or span. Optionally give it a feature object to modify so the new value is saved to the map
function onfavclick(node, type, feature){
    parent = node.parentNode;
    return favorite(type, parent.id.split('_')[1], function(){
        $(node).html('<img src="/static/img/ico_fave2.png" alt="Unfavorite" />');
        parent.id = parent.id.replace("favorite", "unfavorite");
        $(parent).removeClass("favorite"+type+"content").addClass("unfavorite"+type+"content");
        if (feature != null){
            feature.data.is_favorite=true;
        }
        $(node).unbind('click').click(function(){return onunfavclick(node, type, feature);});
    });
}

function onunfavclick(node, type, feature){
    parent = node.parentNode;
    return unfavorite(type, parent.id.split('_')[1], function(){
        $(node).html('<img src="/static/img/ico_fave1.png" alt="Favorite this" />');
        parent.id = parent.id.replace("unfavorite", "favorite");
        $(parent).removeClass("favorite"+type+"content").addClass("unfavorite"+type+"content");
        if (feature != null){
            feature.data.is_favorite=false;
        }
        $(node).unbind('click').click(function(){return onfavclick(node, type, feature);});
    });
}

function bindContentAreaFavoriting(){
    $(".favoriteitinerarycontent a").unbind('click').click(function(){return onfavclick(this, 'itineraries');});
    $(".favoritecastcontent a").unbind('click').click(function(){return onfavclick(this, 'cast');});
    $(".unfavoriteitinerarycontent a").unbind('click').click(function(){return onunfavclick(this, 'itineraries');});
    $(".unfavoritecastcontent a").unbind('click').click(function(){return onunfavclick(this, 'cast');});

}

var oldcontents;
//call first to make sure we grab the initial contents of the column so the back button works there
function initialMakeSortLink(linkSelector, callback){
    oldcontents = $("#column-main").html();
    return makeSortLink(linkSelector, callback);
}
//make a link load something in the content area and call a callback
function makeSortLink(linkSelector, callback){
  $(linkSelector).unbind('click').click(function(){
    //TODO: show loader icon
     //hide content area rows that we have now, then load & show the new ones
    var href = this.href;
    $("#column-main").hide("slide", {}, 500, function(){
     // var oldcontents = $("#column-main").html();
      $("#column-main").load(href, {}, function(){
        $("#column-main").show("slide", {}, 500);
        $("#column-main .streams .header").prepend('<a id="mainbacklink" href="#">Back</a>&nbsp;');
        $("#mainbacklink").click(function(){
            $("#column-main").html(oldcontents);
            makeSortLink(linkSelector, callback); //re-do this when we load a page or sort
            callback();
            return false;
        });
        makeSortLink(linkSelector, callback); //re-do this when we load a page or sort
        callback();
      });
    });
    return false;
  });
}

// PageLoad function for profile pages
// This function is called by jquery's history plugin:
// 1. after calling $.historyInit();
// 2. after calling $.historyLoad();
// 3. after pushing "Go Back" button of a browser
function profileHistoryPageload(hash) {
    // alert("pageload: " + hash);
    // hash doesn't contain the first # character.
    if(hash) {
        if (hash.match('^cast-')){
            map.manager.loadCast(hash.split('-')[1]);
        }
    }
}




