var MapMarkerManager = Class.extend({
  init:             function(map, positions, limit){
                      this.map       = map;
                      this.positions = positions;
                      this.limit     = limit;
                      this.padding   = 2;
                      this.init      = false;
                      
                      this.times     = new Object();
                      
                      if(arguments.length > 3)
                        this.icon    = arguments[3];
                      else
                        this.icon    = G_DEFAULT_ICON;
                      
                      if(arguments.length > 4)
                        this.pluralIcon = arguments[4];
                      else
                        this.pluralIcon = this.icon;

                      this.inBounds  = new Array();
                      this.outBounds = new Array();
                      this.extras    = new Array();
                      
                      var obj        = this;
                      
                      GEvent.addListener(map, "load", function(){ obj.refresh(true); });
                      GEvent.addListener(map, "zoomend", function(){ obj.refresh(); });
                      GEvent.addListener(map, "moveend", function(){ obj.move(); });
                    },
  refresh:          function(){
                      if(arguments.length > 0 && arguments[0])
                        this.init = true;
                      
                      if(!this.init)
                        return;
                      
                      // clear only the markers managed by this object
                      var gmap = this.map;
                      $.each(this.inBounds, function(){ gmap.removeOverlay(this); });
                      
                      this.inBounds  = new Array();
                      this.outBounds = new Array();
                      this.extras    = new Array();
                      
                      var grid       = new Object();
                      var bounds     = this.map.getBounds();
                      var inclBounds = this.getMarkerBounds(); 
                      
                      var point, hash, marker;
                      
                      for(var i = 0; i < this.positions.length; i++){
                        point    = this.map.fromLatLngToDivPixel(this.positions[i].getLatLng());
                        hash     = this.gridHash(point);
                        
                        if(!grid[hash]) grid[hash] = new Array();
                        
                        grid[hash].push(this.positions[i]);
                      }
                      
                      for(var hash in grid){
                        var center = this.hashCenter(hash);
                        
                        if(inclBounds.contains(center)){
                          marker = this.createMarker(grid[hash]);

                          this.map.addOverlay(marker);
                          this.inBounds.push(marker);
                        } else {
                          this.extras.push({ hash: hash, positions: grid[hash] });
                        }
                        
                      }
                    },
  move:             function(){
                      var bounds    = this.getMarkerBounds();
                      var map       = this.map;
                      
                      var newOut    = $.map(this.inBounds, function(marker, index){
                        return (!$.inArray(marker.getPoint(), bounds)) ? marker : null;
                      });

                      $.each(newOut, function(){ map.removeOverlay(this); });
                      
                      this.inBounds = $.map(this.inBounds, function(marker, i){
                        return (!$.inArray(this, newOut)) ? marker : null;
                      });
                      
                      var remExtras = new Array();
                      for(var i = 0; i < this.extras.length; i++){
                        if(bounds.contains(this.hashCenter(this.extras[i].hash))){
                          marker = this.createMarker(this.extras[i].positions);
                          this.map.addOverlay(marker);
                          this.inBounds.push(marker);
                          remExtras.push(i);
                        }
                      }
                      
                      this.extras = $.map(this.extras, function(extra, i){ return (remExtras.indexOf(i) == -1) ? extra : null; });
                      
                      var newIn  = $.map(this.outBounds, function(marker, i){ 
                        return (bounds.contains(marker.getPoint())) ? marker : null;
                      });
                      
                      $.each(newIn, function(){ map.addOverlay(this); });
                      
                      this.outBounds = $.map(this.outBounds, function(marker, i){
                        return (!$.inArray(this, newIn)) ? marker : null;
                      });
                      
                      this.inBounds  = $.merge(this.inBounds, newIn);
                      this.outBounds = $.merge(this.outBounds, newOut);
                    },
  gridHash:         function(point){
                      var x = Math.floor(point.x / this.limit);
                      var y = Math.floor(point.y / this.limit);
                      
                      return x.toString()+"_"+y.toString();
                    },
  hashCenter:       function(hash){
                      var xy     = hash.match(/([0-9\-]+)_([0-9\-]+)/);
                      
                      var point  = new GPoint((xy[1] * this.limit) + Math.round(this.limit / 2), (xy[2] * this.limit) + Math.round(this.limit / 2));
                      return this.map.fromDivPixelToLatLng(point);
                    },
  getMarkerBounds:  function(){
                      var mapBounds = this.map.getBounds();
                      return MapUtils.resizeBounds(mapBounds, 2);
                    },
  createMarker:     function(positions){
                      var content, center, icon;
                      if(positions.length > 1){
                        content = this.compoundContent(positions);
                        
                        var bounds = new GLatLngBounds();
                        $.each(positions, function(){ bounds.extend(this.getLatLng()); });
                        center  = bounds.getCenter();
                        icon    = this.pluralIcon;
                      } else {
                        content = positions[0].content;
                        center  = positions[0].getLatLng();
                        icon    = this.icon;
                      }
                      var marker = new GMarker(center, icon);
                    marker.bindInfoWindowHtml('<div style="width: 380px; height: 140px; overflow-y: auto; margin-top: 10px; font-size: 13px;">' +
                                              content +
                                              '</div>');
                      return marker;
                    },
  compoundContent:  function(positions){
                      var content;

                        content  = '<div class="map-content">';
                        $.each(positions, function(i){ 
                          if(i > 0)
                            content += "<br /><div class=\"divider\"></div><br />";
                          content   += this.content; 
                        });
                        content += '</div>';                        
                      
                      return content;
                    }
});

var MapUtils = {
  resizeBounds: function(bounds, delta){
                  var span      = bounds.toSpan();
                  var ne        = bounds.getNorthEast();
                  var sw        = bounds.getSouthWest();
                  
                  var latAdj    = ((span.lat() * delta) - span.lat()) / 2;
                  var lngAdj    = ((span.lng() * delta) - span.lng()) / 2;
                  
                  var ne2       = new GLatLng(ne.lat() + latAdj, 
                                              ne.lng() + lngAdj);
                  var sw2       = new GLatLng(sw.lat() - latAdj, 
                                              sw.lng() - lngAdj);
                  return new GLatLngBounds(sw2, ne2);
                }  
};

var MapPosition = Class.extend({
  init:         function(lat, lng, content){
                  this.lat     = lat;
                  this.lng     = lng;
                  this.content = content;
                },
  getLatLng:    function(){
                  return new GLatLng(this.lat, this.lng);
                }
});

var MapMarkerManager2 = Class.extend({
  init:             function(map, markers, callback){
                      this.map       = map;
                      this.padding   = 2;
                      this.init      = false;
                      this.callback  = callback;
                      this.working   = new MapWorkingControl('/images/loading.gif', this.map);
                      this.loaded    = 0;
                      this.markers   = { };
                      this.markers[callback] = markers;

                      this.refreshCallback = (arguments.length > 3) ? arguments[3] : null;
                      this.icon            = (arguments.length > 4) ? arguments[4] : null;
                      this.pluralIcon      = (arguments.length > 5) ? arguments[5] : null;
                      
                      this.inBounds  = new Array();
                      this.outBounds = new Array();
                      this.extras    = new Array();

                      var obj        = this;

                      GEvent.addListener(map, "load", function(){ obj.refresh(true); });
                      GEvent.addListener(map, "zoomend", function(){ obj.refresh(); });
                      GEvent.addListener(map, "moveend", function(){ obj.move(); });
                    },
  refresh:          function(){          
                      if(arguments.length > 0 && arguments[0])
                        this.init = true;
                      if(!this.init)
                        return;

                      if(this.refreshCallback)
                        this.refreshCallback();
                      // clear only the markers managed by this object
                      var gmap = this.map;
                      $.each(this.inBounds, function(){ gmap.removeOverlay(this);});
                      
                      this.inBounds  = new Array();
                      this.outBounds = new Array();
                      this.extras    = new Array();
                      
                      var bounds     = this.map.getBounds();
                      var inclBounds = this.getMarkerBounds(); 
                      var zoom       = this.map.getZoom();
                      if(!this.markers[this.callback] || !this.markers[this.callback][zoom]){
                        this.loadMarkers(zoom);
                        return;
                      }
                      var marker;
                      
                      for(var i = 0; i < this.markers[this.callback][zoom].length; i++){
                        marker = this.markers[this.callback][zoom][i];
                        if(inclBounds.contains(marker.getPoint())){
                          this.inBounds.push(marker);
                        } else {
                          this.outBounds.push(marker);
                        }
                      }
                      this.working.show();
                      var obj = this;
                      setTimeout(function(){
                         (function(){ this.addMarkers()}).apply(obj);
                       }, 250); 
                  },
  loadMarkers:      function(zoom){
                      if(!this.callback)
                        return;

                      this.working.show();
                      var obj = this;
                      $.ajax({
                        url: this.callback,
                        type: "GET",
                        data: ({'zoom' : zoom, 'markers' : 1, 'loaded' : this.loaded}),
                        dataType: "script",
                        success: function(msg) {
                          obj.finishLoad(zoom, msg);
                        }
                      });
                     
                    },
  finishLoad:       function(zoom, request){
                      try {
                        eval(request.responseText);
                        if(this.loaded == 0)
                          this.markers[this.callback] = markers;
                        else
                          this.markers[this.callback][zoom] = markers[zoom];

                        this.working.hide();
                        if(this.map.getZoom() == zoom) {
                          this.refresh();
                          this.loaded = 1;
                        }
                      } catch(e){ 
                        if(console){
                          console.log(e);
                          for(var i in e)
                            console.log('%s: %s', i, e[i]);
                        }
                        this.working.hide();
                      }
                    },
  addMarkers:       function(){
                      map = this.map;
                      $.each(this.inBounds, function(){ map.addOverlay(this); });
                      this.working.hide();
                    },
  move:             function(){
                      var bounds    = this.getMarkerBounds();
                      var map       = this.map;

                      var newOut    = $.map(this.inBounds, function(marker, index){
                        return (!$.inArray(marker.getPoint(), bounds)) ? marker : null;
                      });

                      $.each(newOut, function(){ $.map.removeOverlay(this); });
                      
                      if(newOut.length > 0) {
                        this.inBounds = $.map(this.inBounds, function(marker){
                          return (!$.inArray(marker, newOut)) ? marker : null;
                        });
                      }

                      var remExtras = new Array();
                      for(var i = 0; i < this.extras.length; i++){
                        if(bounds.contains(this.hashCenter(this.extras[i].hash))){
                          marker = this.createMarker(this.extras[i].positions);
                          this.map.addOverlay(marker);
                          this.inBounds.push(marker);
                          remExtras.push(i);
                        }
                      }
                      
                      this.extras = $.map(this.extras, function(extra, i){ return (remExtras.indexOf(i) == -1) ? extra : null; });
                      
                      var newIn  = $.map(this.outBounds, function(marker, i){ 
                        return (bounds.contains(marker.getPoint())) ? marker : null;
                      });
                      
                      $.each(newIn, function(){ map.addOverlay(this); });
                      
                      if(newIn.length > 0) {
                        this.outBounds = $.map(this.outBounds, function(marker, i){
                          return (!$.inArray(this, newIn)) ? marker : null;
                        });
                      }

                      this.inBounds  = $.merge(this.inBounds, newIn);
                      this.outBounds = $.merge(this.outBounds, newOut);

                    },
  getMarkerBounds:  function(){
                      var mapBounds = this.map.getBounds();
                      return MapUtils.resizeBounds(mapBounds, 2);
                    }
});

MapWorkingControl = Class.extend({
  init:         function(loadingImage, map){
                  this.loading     = document.createElement("img");
                  this.loading.src = loadingImage;
                  
                  this.container = document.createElement('div');
                  this.container.appendChild(this.loading);
                  
                  this.map         = map;
                  with(this.container){
                    style.border   = '1px #666666 solid';
                    style.position = 'absolute';
                    style.display  = 'none';
                    style.zIndex   = '99';
                  }
                  this.map.getContainer().appendChild(this.container);
                },
  reposition:   function(){
                  var height       = $(this.map.getContainer()).height();
                  var width        = $(this.map.getContainer()).width();
                  var left         = Math.round((width - (this.loading.width + 2)) / 2);
                  var top          = Math.round((height - (this.loading.height + 2)) / 2);
                  
                  this.container.style.left = left.toString()+'px';
                  this.container.style.top  = top.toString()+'px';
                },
  show:         function(){
                  this.reposition();
                  this.container.style.display = 'block';
                },
  hide:         function(){
                  this.container.style.display = 'none';
                }
});


StreetviewControl = Class.extend({
  init:          function(target, map, container){
                    this.target    = target;
                    this.container = $(container);
                    this.map       = map;
                    this.client    = new GStreetviewClient();
                    
                    var obj        = this;
                    
                    this.client.getNearestPanorama(target, function(data){ obj.setup(data); });
                  },
  setup:          function(data){
                    if(data.code != 200)
                      return;
                    
                    var obj    = this;
                    var latlng = data.location.latlng;
                    
                    this.latlng   = latlng;
                    this.yaw      = this.bearing(this.target, latlng);
                    
                    this.marker   = new GMarker(latlng, { icon: this.compassIcon(this.direction(this.yaw)), draggable: true });
                    this.map.addOverlay(this.marker);
                    
                    GEvent.addListener(this.marker, 'dragend', function(){ obj.resetView() });
                    
                    this.panorama = new GStreetviewPanorama(this.container[0]);
                    
                    GEvent.addListener(this.panorama, "error", function(errorCode){ obj.handleError(errorCode); });
                    GEvent.addListener(this.panorama, "initialized", function(location){ obj.panoMove(location); });
                    GEvent.addListener(this.panorama, "yawchanged", function(){ obj.panoYaw(); });
                    GEvent.addListener(this.map, "click", function(overlay, latlng){ obj.positionMarker(); });
                    
                    this.panorama.setLocationAndPOV(latlng, { yaw: this.yaw });
                  },
  bearing:        function(target, origin){
                    var x = Math.abs(origin.lng() - target.lng());
                    var y = Math.abs(origin.lat() - target.lat());
                    
                    var z = Math.atan(y / x);
                    
                    pos  = (target.lat() > origin.lat()) ? 'n' : 's';
                    pos += (target.lng() > origin.lng()) ? 'e' : 'w';
                    
                    switch(pos){
                      case "ne":
                        z  = (0.5 * Math.PI) - z;
                        break;
                      case "se":
                        z += 0.5 * Math.PI;
                        break;
                      case "sw":
                        z  = ((0.5 * Math.PI) - z) + Math.PI;
                        break;
                      case "nw":
                        z += 1.5 * Math.PI;
                        break
                    }
                    
                    var a = z * (180 / Math.PI);
                    
                    return a;
                  },
  direction:      function(yaw){
                    while(yaw > 360)
                      yaw -= 360;
                    quadrent = Math.ceil((Math.floor(yaw / 11.25) + 1) / 2);
                    return ['n', 'nne', 'ne', 'ene', 'e', 'ese', 'se', 'sse', 's', 'ssw', 'sw', 'wsw', 'w', 'wnw', 'nw', 'nnw', 'n'][quadrent];
                  },
  positionMarker: function(overlay, latlng){
                    if((!latlng || !latlng.lat()) && arguments.length > 2) // marker was clicked instead of map
                      latlng = arguments[2];
                    
                    if(latlng && latlng.lat()){
                      this.marker.setLatLng(latlng);
                      this.resetView();
                    }
                  },
  resetView:      function(){
                    var obj = this;
                    this.client.getNearestPanorama(this.marker.getLatLng(), function(data){ obj.finishReset(data); });
                  },
  finishReset:    function(data){
                    if(data.code != 200){
                      alert('No data could be found for that location!');
                      this.marker.setLatLng(this.latlng);
                      return;
                    }
                    var latlng = data.location.latlng;
                    this.panorama.setLocationAndPOV(latlng, { yaw: this.yaw });
                    this.marker.setLatLng(latlng);
                    this.latlng = latlng;
                    this.marker.setImage(this.compassImage(this.direction(this.yaw)));
                  },
  panoMove:       function(location){
                    this.marker.setLatLng(location.latlng);
                    this.map.setCenter(location.latlng);
                    this.latlng = location.latlng;
                    this.yaw = location.pov.yaw;
                    this.marker.setImage(this.compassImage(this.direction(this.yaw)));
                  },
  panoYaw:        function(){
                    var pov = this.panorama.getPOV();
                    this.yaw = pov.yaw;
                    this.marker.setImage(this.compassImage(this.direction(this.yaw)));
                  },
  handleError:    function(errorCode){
                    if(errorCode == 603){
                      alert("Your browser does not appear to support flash. The flash plugin is required to use Google Street View.");
                      return;
                    }
                  },
  compassImage:   function(pos){
                    return "/images/ljhooker/mapicons/compass/"+pos.toLowerCase()+".png";
                  },
  compassIcon:    function(pos){
                    var icon                 = new GIcon();
                    icon.image               = this.compassImage(pos);
                    icon.printImage          = this.compassImage(pos);
                    icon.mozPrintImage       = this.compassImage(pos);
                    icon.iconSize            = new GSize(40, 40);
                    icon.iconAnchor          = new GPoint(20, 20);
                    icon.infoWindowAnchor    = new GPoint(20, 20);
                    return icon;
                  }
});