Object.extend = function(dst, src) {
  for (var k in src) dst[k] = src[k];
  return src;
}

/**
 * All content copyright ... ah, screw it.  Do whatever you want with this.
 * If your enough of a douchebag to not give me credit, that's between you
 * and you're karma.  If you do want to use this somewhere, then something
 * like this would be nice:
 *
 * <a href="NPR Station Map by <a href="http://www.broofa.com">Broofa.com</a>
 */

/**
 * Initialize the map
 */
function load() {
  if (!GBrowserIsCompatible()) {
    return;
  }

  // Parse the lat/lon strings out of the URL
  var s = location.toString();
  var lat = /lat=(-?[.0-9]+)/.exec(s);
  var lng = /lng=(-?[.0-9]+)/.exec(s);
  var zoom = /zoom=([0-9]+)/.exec(s);
  lat = lat && !isNaN(lat = parseFloat(lat[1])) ? lat : 41;
  lng = lng && !isNaN(lng = parseFloat(lng[1])) ? lng : -96;
  zoom = zoom && !isNaN(zoom = parseInt(zoom[1])) ? zoom : 5;
  var target = new GLatLng(lat, lng);

  // Create the map
  var map = new GMap2(document.getElementById("map"));
  map.setCenter(target, zoom);
  map.addControl(new GSmallMapControl());
  map.addControl(new GMapTypeControl());
  
  GEvent.addListener(map, "zoomend", function() {handleMapChange(map,false);});
  GEvent.addListener(map, "moveend", function() {handleMapChange(map,true);});

  // Load the stations and set the center point (for distances)
  City.load(STATIONS);
  City.setCenter(map, target);

  // Force the UI to update
  handleMapChange(map, false);
  City.renderStationList();

  // Start rendering the city markers
  var cityRenderer = function() {
    // Create a batch of markers
    for (var i = 0; i < 10; i++) {
      var city = City._unrendered.pop();
      if (city) city.render(map);
    }
    if (City._unrendered.length) setTimeout(cityRenderer, 1);
  };
  cityRenderer();
}

/**
 * Reload the page so that the link, marker load order, and distance
 * calculations are relative to the current center of the map.
 */
function handleMapChange(map, updateCenter) {
  var el = document.getElementById('locationlink');
  var target = map.getCenter();
  var zoom = map.getZoom();

  if (updateCenter) {
    City.setCenter(map, target);
    City.renderStationList();
  }
  
  var s = location.toString();
  s = s.replace(/\?.*/, '');
  s += '?lat=' + target.lat() + '&lng=' + target.lng() + '&zoom=' + zoom;
  el.href = s;
  var lat = target.lat();
  var lng = target.lng();
  var prec = 1e4;
  lat = Math.round(lat*prec)/prec;
  lng = Math.round(lng*prec)/prec;
  var nearest = City.instances[0];
  el.innerHTML = 'NPR Stations near ' + nearest.place;

}

/**
 * City instances contain information about a radio stations in a particular
 * city/region
 */
function City(station) {
  this.infoEl = null;
  this.stations = [];
  this.maxKw = 0;
  this.place = station.city + ', ' + station.state;
  this.latlng = new GLatLng(station.lat, station.lon);

  City.instances.push(this);
  City._byPlace[this.place] = this;
  City._unrendered.push(this);
}

Object.extend(City, {
  INFO_URL_TEMPLATE: 'http://www.radio-locator.com/cgi-bin/finder?sr=Y&s=C&call=CALL&x=0&y=0',

  /** @var {Object} map of place name to city instance */
  _byPlace: {},

  /** @var {Array[City]} all City instances, sorted by distance to map centerpoint(?) */
  instances: [],

  /** @var {Array[City]} all unrendered City instances */
  _unrendered: [],

  distanceComparator: function(a, b) {
    return a.dist < b.dist ? -1 : (a.dist > b.dist ? 1 : 0);
  },

  /**
   * Get the power classification based on kilowatt transmission power
   * @function {Number} ?
   * @param {Number} kw
   */
  power: function(kw) {
    return kw >= 100 ? 5 : (kw >= 10 ? 4 : (kw >= 1 ? 3 : (kw >= .1 ? 2 : (kw >= .01 ? 1 : 0))));
  },

  /**
   * Get url for power icon
   * @function {String} ?
   * @param {Number} kw
   */
  powerImage: function(kw) {
    return '<img src="bars' + City.power(kw) + '_s.png">';
  },

  /**
   * Get (or create) a city object based on station
   * @function ?
   */
  find: function(station) {
    var place = station.city + ', ' + station.state;
    return City._byPlace[place] || new City(station);
  },

  /**
   * Load up all stations in the specified array
   */
  load: function(stations) {
    // Create Map icons
    City.icons = [];
    for (var jk = 0; jk <= 5; jk++) {
      var icon = new GIcon(G_DEFAULT_ICON);
      icon.image = 'bars' + jk + '.png'
      City.icons.push(icon);
    }

    // Object-ify the stations array
    for (var i = 0; i < stations.length; i++) {
      var station = stations[i];
      if (!station) continue;
      City.find(station).addStation(station);
    }
  },

  renderStationList: function() {
    var s = [];
    for (var i = 0; i < 10; i++) {
      s.push('<li class="city">');
      s.push(City.instances[i].toStationListString());
      s.push('</li>');
    }
    document.getElementById('stationlist').innerHTML = s.join('\n');
  },

  /**
   * Specify the center point for distance calculations
   */
  setCenter: function(map, target) {
    // Update distance caches
    for (var i = 0; i < City.instances.length; i++)
      City.instances[i].setCenter(target);

    City.instances.sort(City.distanceComparator);
    City._unrendered.sort(City.distanceComparator);
    City._unrendered.reverse(); // Since we're pop'ing these
  }
});

Object.extend(City.prototype, {
  addStation: function(station) {
    this.stations.push(station);
    this.maxKw = Math.max(this.maxKw, station.kw);
  },

  setCenter: function(target) {
      this.dist = Math.round(this.latlng.distanceFrom(target)/1609);
  },

  /**
   * Get station description
   */
  getInfoString: function(i) {
    var stn = this.stations[i];
    return City.powerImage(stn.kw) + ' <a target="_blank" href="'
      + City.INFO_URL_TEMPLATE.replace('CALL', stn.sign) + '">'
      + stn.sign + '</a> ' + stn.freq;
  },

  /** Get content to display in google map info panel */
  toInfoPanelString: function() {
    var s = [];
    for (var i = 0; i < this.stations.length; i++) {
      s.push(this.getInfoString(i));
    }
    return '<b>' + this.place + '</b><br>' + s.join('<br>');
  },

  /** Get content to display in our station list */
  toStationListString: function() {
    var stns = [];
    for (var i = 0; i < this.stations.length; i++) {
      stns.push(this.getInfoString(i));
    }
    var stn = this.stations[0];
    return this.dist +  'mi - ' + '<b>' + this.place + '</b>: ' + stns.join(', ');
  },

  render: function(map) {
    var city = this;
    var marker = new GMarker(this.latlng, {
      icon: City.icons[City.power(this.maxKw)]
    });

    GEvent.addListener(marker, "click", function() {
      marker.openInfoWindowHtml(city.toInfoPanelString());
    });
    map.addOverlay(marker);
  }
});
