/**
 * $$$ hugh - from Google example code:
 * http://code.google.com/intl/fr/apis/maps/articles/mvcfun/distancewidget.js
 *
 * A distance widget that will display a circle that can be resized and will
 * provide the radius in km.
 *
 * @param {Object} opt_options Options such as map, position etc.
 *
 * @constructor
 */
	
function DistanceWidget(opt_options) {
  var options = opt_options || {};

  this.setValues(options);

  if (!this.get('position')) {
    this.set('position', this.map.getCenter());
  }

  // Add a marker to the page at the map center or specified position
  /* $$$ hugh - we use marker from Fabrik element
  var marker = new google.maps.Marker({
    draggable: true,
    title: 'Move me!'
  });
  */

  this.marker.bindTo('map', this);
  this.marker.bindTo('zIndex', this);
  this.marker.bindTo('position', this);
  this.marker.bindTo('icon', this);

  // Create a new radius widget
  var radiusWidget = new RadiusWidget(options['distance'] || 50);

  // Bind the radius widget properties.
  radiusWidget.bindTo('center', this, 'position');
  radiusWidget.bindTo('map', this);
  radiusWidget.bindTo('zIndex', this.marker);
  radiusWidget.bindTo('maxDistance', this);
  radiusWidget.bindTo('minDistance', this);
  radiusWidget.bindTo('color', this);
  radiusWidget.bindTo('activeColor', this);
  radiusWidget.bindTo('sizerIcon', this);
  radiusWidget.bindTo('activeSizerIcon', this);

  // Bind to the radius widget distance property
  this.bindTo('distance', radiusWidget);
  // Bind to the radius widget bounds property
  this.bindTo('bounds', radiusWidget);
  this.bindTo('sizer_position', radiusWidget);
  this.bindTo('center', radiusWidget);
  // $$$ hugh - added active binding so we can see when move / drag is finished
  // and fire our 'change' events for the Fabrik map element(s)
  this.bindTo('active', radiusWidget);
  
  var me = this;
  google.maps.event.addListener(this.marker, 'dblclick', function() {
    // When a user double clicks on the icon fit to the map to the bounds
    this.map.fitBounds(me.get('bounds'));
  });
}
DistanceWidget.prototype = new google.maps.MVCObject();

/**
 * A radius widget that add a circle to a map and centers on a marker.
 *
 * @param {number} opt_distance Optional starting distance.
 * @constructor
 */
function RadiusWidget(opt_distance) {
  var circle = new google.maps.Circle({
    strokeWeight: 2
  });

  this.set('distance', opt_distance);
  this.set('active', false);
  this.bindTo('bounds', circle);

  circle.bindTo('center', this);
  circle.bindTo('zIndex', this);
  circle.bindTo('map', this);
  circle.bindTo('strokeColor', this);
  circle.bindTo('radius', this);

  this.addSizer_();
}
RadiusWidget.prototype = new google.maps.MVCObject();


/**
 * Add the sizer marker to the map.
 *
 * @private
 */
RadiusWidget.prototype.addSizer_ = function() {
  var sizer = new google.maps.Marker({
    draggable: true,
    title: 'Drag me!',
    raiseOnDrag: false
  });

  sizer.bindTo('zIndex', this);
  sizer.bindTo('map', this);
  sizer.bindTo('icon', this);
  sizer.bindTo('position', this, 'sizer_position');

  var me = this;
  google.maps.event.addListener(sizer, 'dragstart', function() {
    me.set('active', true);
  });

  google.maps.event.addListener(sizer, 'drag', function() {
    // Set the circle distance (radius)
    me.setDistance_();
  });

  google.maps.event.addListener(sizer, 'dragend', function() {
    me.set('active', false);
  });
};


/**
 * Update the radius when the distance has changed.
 */
RadiusWidget.prototype.distance_changed = function() {
  this.set('radius', this.get('distance') * 1000);
};

/**
 * Update the radius when the min distance has changed.
 */
RadiusWidget.prototype.minDistance_changed = function() {
  if (this.get('minDistance') &&
      this.get('distance') < this.get('minDistance')) {
    this.setDistance_();
  }
};


/**
 * Update the radius when the max distance has changed.
 */
RadiusWidget.prototype.maxDistance_changed = function() {
  if (this.get('maxDistance') &&
      this.get('distance') > this.get('maxDistance')) {
    this.setDistance_();
  }
};


/**
 * Update the stroke color when the color is changed.
 */
RadiusWidget.prototype.color_changed = function() {
  this.active_changed();
};


/**
 * Update the active stroke color when the color is changed.
 */
RadiusWidget.prototype.activeColor_changed = function() {
  this.active_changed();
};


/**
 * Update the active stroke color when the color is changed.
 */
RadiusWidget.prototype.sizerIcon_changed = function() {
  this.active_changed();
};


/**
 * Update the active stroke color when the color is changed.
 */
RadiusWidget.prototype.activeSizerIcon_changed = function() {
  this.active_changed();
};


/**
 * Update the center of the circle and position the sizer back on the line.
 *
 * Position is bound to the DistanceWidget so this is expected to change when
 * the position of the distance widget is changed.
 */
RadiusWidget.prototype.center_changed = function() {
  var sizerPos = this.get('sizer_position');
  var position;
  if (sizerPos) {
    position = this.getSnappedPosition_(sizerPos);
  } else {
    var bounds = this.get('bounds');
    if (bounds) {
      var lng = bounds.getNorthEast().lng();
      position = new google.maps.LatLng(this.get('center').lat(), lng);
    }
  }

  if (position) {
    this.set('sizer_position', position);
  }
};

/**
 * Update the center of the circle and position the sizer back on the line.
 */
RadiusWidget.prototype.active_changed = function() {
  var strokeColor;
  var icon;

  if (this.get('active')) {
    if (this.get('activeColor')) {
      strokeColor = this.get('activeColor');
    }

    if (this.get('activeSizerIcon')) {
      icon = this.get('activeSizerIcon');
    }
  } else {
    strokeColor = this.get('color');

    icon = this.get('sizerIcon');
  }

  if (strokeColor) {
    this.set('strokeColor', strokeColor);
  }

  if (icon) {
    this.set('icon', icon);
  }
};


/**
 * Set the distance of the circle based on the position of the sizer.
 * @private
 */
RadiusWidget.prototype.setDistance_ = function() {
  // As the sizer is being dragged, its position changes.  Because the
  // RadiusWidget's sizer_position is bound to the sizer's position, it will
  // change as well.
  var pos = this.get('sizer_position');
  var center = this.get('center');
  var distance = this.distanceBetweenPoints_(center, pos);

  if (this.get('maxDistance') && distance > this.get('maxDistance')) {
    distance = this.get('maxDistance');
  }

  if (this.get('minDistance') && distance < this.get('minDistance')) {
    distance = this.get('minDistance');
  }

  // Set the distance property for any objects that are bound to it
  this.set('distance', distance);

  var newPos = this.getSnappedPosition_(pos);
  this.set('sizer_position', newPos);
};


/**
 * Finds the closest left or right of the circle to the position.
 *
 * @param {google.maps.LatLng} pos The position to check against.
 * @return {google.maps.LatLng} The closest point to the circle.
 * @private.
 */
RadiusWidget.prototype.getSnappedPosition_ = function(pos) {
  var bounds = this.get('bounds');
  var center = this.get('center');
  var left = new google.maps.LatLng(center.lat(),
      bounds.getSouthWest().lng());
  var right = new google.maps.LatLng(center.lat(),
      bounds.getNorthEast().lng());

  var leftDist = this.distanceBetweenPoints_(pos, left);
  var rightDist = this.distanceBetweenPoints_(pos, right);

  if (leftDist < rightDist) {
    return left;
  } else {
    return right;
  }
};


/**
 * Calculates the distance between two latlng points in km.
 * @see http://www.movable-type.co.uk/scripts/latlong.html
 *
 * @param {google.maps.LatLng} p1 The first lat lng point.
 * @param {google.maps.LatLng} p2 The second lat lng point.
 * @return {number} The distance between the two points in km.
 * @private
 */
RadiusWidget.prototype.distanceBetweenPoints_ = function(p1, p2) {
  if (!p1 || !p2) {
    return 0;
  }

  var R = 6371; // Radius of the Earth in km
  var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
  var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
  var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
    Math.sin(dLon / 2) * Math.sin(dLon / 2);
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  var d = R * c;
  return d;
};

/**
 * $$$ hugh
 * 
 * had to add this callback to solve asyncronous loading issues with the main google maps code
 * in the Fabrik map element, which loads the maps code with a document.createElement('script') async load.
 * 
 * Should probably redo this so the callback func is a CGI arg, rather than hard coded, but for now this
 * will do.
 * 
 */

googleradiusloaded();
