/**
 * UCHC namespace functions.  Requires the prototype library to be loaded.
 * To extend the namespace with more functions/public members:
 * 
 * Object.extend(UCHC, (function() {
 *   var privateMember = 'secret';
 *
 *   function privateMethod(arg) {
 *    // do something secret
 *   }
 * 
 *   return {
 *     publicMember: 'public',
 *     publicMethod: function(arg) {
 *       // do something public
 *     },
 *   };
 * })());
 */


 // define our namespace
if(!window.UCHC) var UCHC = {};

if(typeof Prototype != 'undefined') {
/* Bind public vars, constants and classes */
Object.extend(UCHC, {
  mapId: null,
  map: null,
  providers: new Array(),
  colors: null,
  highlightedAddress: null
});

// Define methods
UCHC.Methods = (function() {
  // Private methods
  /**
   * Return the best zoom level for 'address', less than or equal to the 
   * current zoom level, where none of the markers in 'providers' occlude
   * the target marker.  This function is used to focus on and zoom into
   * a target address/marker on the map.
   *
   * @param map {GMap2}
   * @param address {Object} with attributes 'latlng' {GLatLng} and 'formatted' {String}
   * @param providers {Array(Array(Object))} 'address' objects
   */
  function calculateBestZoom(map,address,providers) {
    var zoom  = 3;
    
    // cached result
    if(address.bestZoom) return address.bestZoom;
    
    var proj  = map.getCurrentMapType().getProjection();
    var providerIndex = 0,
        addressIndex = 0;
    var pt1,pt2;
    
    // descend through the zoom levels (3 -> 17)
    for(; zoom < 17; zoom++) {
      providerLoop:
      for(; providerIndex < providers.size(); providerIndex++) {
        for(addressIndex = 0; addressIndex < providers[providerIndex].addresses.size(); addressIndex++) {
          pt1 = proj.fromLatLngToPixel(address.latlng,zoom);
          pt2 = proj.fromLatLngToPixel(providers[providerIndex].addresses[addressIndex].latlng,zoom);
          // if the next marker is not this marker, and the next marker is below this marker
          //  by less than a certain distance in any direction, time to zoom in
          if(!address.latlng.equals(providers[providerIndex].addresses[addressIndex].latlng) && 
            providers[providerIndex].addresses[addressIndex].latlng.lat() <= address.latlng.lat() &&
            Math.abs(Math.sqrt(Math.pow(pt1.x-pt2.x,2)+Math.pow(pt1.y-pt2.y,2))) < 10) break providerLoop;
        }
      }
      // no markers occlude the target, this is the best zoom for it
      if(providerIndex >= providers.size() &&
         addressIndex  >= providers.last().addresses.size()) return address.bestZoom = zoom;
    }
    
    return address.bestZoom = zoom;
  }
  
  /**
   * Extend the given bounds to contain the marker plus any space needed for the
   * marker icon.  This is useful for setting the viewport of the map to include
   * the marker without cutting off the icon if it falls on the edge of the bounds.
   * Returns the bounds object so calls can be chained.  This function assumes the icon is anchored
   * at the bottom, middle so the bounds will be extended by at most +iconWidth/2
   * or -iconWidth/2 and/or -iconHeight/2 to accomodate the icon.
   * 
   * @param bounds {GLatLngBounds} the bounds to extend
   * @param marker {GMarker} the marker to extend the bounds by
   * @param map {GMap2} the map the marker will be added to
   */
  function extendBoundsByMarkerIcon(bounds,marker,map) {
    // extend the bounds to contain the given marker
    bounds.extend(marker.getLatLng());
    
    var icon = marker.getIcon();
    var projection = map.getMapTypes()[0].getProjection();
    var boundsZoom = map.getBoundsZoomLevel(bounds);
    
    var markerPixel = projection.fromLatLngToPixel(marker.getLatLng(),boundsZoom);
    var latLngNe = bounds.getNorthEast(),
        latLngSw = bounds.getSouthWest();
    
    var pixelNe = projection.fromLatLngToPixel(latLngNe,boundsZoom),
        pixelSw = projection.fromLatLngToPixel(latLngSw,boundsZoom);
    
    if(markerPixel.x + icon.iconSize.width/2 > pixelNe.x) {
      // extend the bounds to the right if needed
      latLngNe = projection.fromPixelToLatLng(new GPoint(markerPixel.x+icon.iconSize.width/2,
                                                         pixelNe.y),boundsZoom);
    } else if(markerPixel.x - icon.iconSize.width/2 < pixelSw.x) {
      // extend the bounds to the left if needed
      latLngSw = projection.fromPixelToLatLng(new GPoint(markerPixel.x-icon.iconSize.width/2,
                                                         pixelSw.y),boundsZoom);
    }
    if(markerPixel.y - icon.iconSize.height < pixelNe.y) {
      // extend the bounds upward if needed (maintaining the x position if it's been changed)
      latLngNe = projection.fromPixelToLatLng(new GPoint(projection.fromLatLngToPixel(latLngNe,boundsZoom).x,
                                                         markerPixel.y-icon.iconSize.height),boundsZoom);
    }
    
    bounds.extend(latLngSw);
    bounds.extend(latLngNe);
    
    return bounds;
  }

  return {
    // public methods.  These methods have access to the public members through 'this'
    
    /**
     * Initialize and create the map and related objects.  Center and zoom the
     * map based on the number of markers that will be created for the addresses
     * in this.providers.  Note that at this point the markers have not yet
     * been added to the map.
     */
    initializeMap:  function(id) {
      this.mapId = id;
      this.map = new GMap2($(id));
      this.map.addControl(new GMapTypeControl());
      this.map.addControl(new GLargeMapControl());
      this.map.enableContinuousZoom();
      
      var initialCenter;
      var initialZoom;
      if(this.providers.size() == 0) { 
        // case: no points - view of US
        initialCenter = new GLatLng(38.8225909761771, -95.9765625);
        initialZoom   =  3;
      } else if(this.providers.size() == 1 && this.providers.first().addresses.size() == 1) {
        // case: one point - center on it
        initialCenter = this.providers.first().addresses.first().latlng;
        initialZoom = 13;
      } else {
        // case: multiple points - center above them all, but don't zoom in too far if the points are close together
        var bounds = new GLatLngBounds();
        this.providers.each(function(provider) {
          provider.addresses.each(function(address){bounds.extend(address.latlng)});
        });
        initialCenter = bounds.getCenter();
        initialZoom = Math.min(this.map.getBoundsZoomLevel(bounds),13);
      }
        
      this.map.setCenter(initialCenter,initialZoom);
    },
    
    /**
     * Create and add markers for the addresses found in this.providers
     */
    displayProviders: function() {
      // chances are we won't have more than 26 address markers, but if we do,
      //  we will cycle back to 'A', which, chances are even better, will be
      //  in a different color.
      var addressIcon = new GIcon(G_DEFAULT_ICON);
      var addressIndex = 0;
      var map = this.map;
      var colors = this.colors;
      
      var bounds = map.getBounds();
      
      this.providers.each(function(provider,index) {
        provider.addresses.each(function(address){
          addressIcon.image = '/img/markers/'+colors[index]+'_Marker'+String.fromCharCode(65+addressIndex)+'.png';
          marker = new GMarker(address.latlng,{ icon:addressIcon,title:provider.name });
          marker.bindInfoWindowHtml(address.formatted);
          map.addOverlay(marker);
          address.marker = marker;
          address.markerLetter = String.fromCharCode(65+addressIndex);
          address.markerImage = addressIcon.image;
          addressIndex = (addressIndex+1)%26;
          
          // ensure we're not clipping any marker icons
          bounds = extendBoundsByMarkerIcon(bounds,marker,map);
        });
      });
      
      map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));
    },
    
    /**
     * Scroll down the page to the map if at least the top-60% of it is not
     * within the viewport.  Find the provider and address marker, and if we're
     * at a reasonable zoom level, pan over to it.  Otherwise, zoom in a bit
     * and pan over.  Open the marker's info window.
     */
    findAddress: function(providerIndex,addressIndex) {
      if(this.providers.size() == 0) return;
      if(!UCHC.withinViewport($(this.mapId),0.6)) $(this.mapId).scrollTo();
      var address = this.providers[providerIndex].addresses[addressIndex];
      
      var bestZoom = address.bestZoom || calculateBestZoom(this.map,address,this.providers);
      if(this.map.getZoom() < bestZoom) this.map.setCenter(address.latlng,bestZoom);
      else if(!this.map.getBounds().containsLatLng(address.latlng)) this.map.panTo(address.latlng);
      
      address.marker.openInfoWindowHtml(address.formatted);
    },
    
    /**
     * Scroll down the page to the map if at least the top-60% of it is not
     * within the viewport.  Find the provider and address marker, and if we're
     * at a reasonable zoom level, pan over to it.  Otherwise, zoom in a bit
     * and pan over.  Open the marker's info window.
     */
    highlightMainAddress: function(providerIndex,addressIndex) {
      if(this.ind_provider.size() == 0) return;
      if(!UCHC.withinViewport($(this.mapId),0.6)) $(this.mapId).scrollTo();
      var address = this.ind_provider[providerIndex].addresses[addressIndex];
      
      var bestZoom = address.bestZoom || calculateBestZoom(this.map,address,this.ind_provider);
      if(this.map.getZoom() < bestZoom) this.map.setCenter(address.latlng,bestZoom);
      else if(!this.map.getBounds().containsLatLng(address.latlng)) this.map.panTo(address.latlng);
      
      address.marker.openInfoWindowHtml(address.formatted);
    },
    
    /**
     * Returns true if the top-percentage% of el is visible within the viewport
     * The general idea of this approach was borrowed from 
     * http://thinkweb2.com/projects/prototype/ and is the reason we include
     * prototype 1.6 rc1
     */
    withinViewport: function(el,percent) {
      if(arguments.length == 1) percent = 1.0;
      var elOffset = el.cumulativeOffset(),
          vpOffset = document.viewport.getScrollOffsets(),
          elDim = el.getDimensions(),
          vpDim = document.viewport.getDimensions();
      // vpOffset[1]: y coordinate of viewport (origin is top-left)
      return elOffset[1] + elDim.height * percent < vpOffset[1] + vpDim.height;
    },
    
    /**
     * If the map is on within the current viewport and the marker given by
     * providerIndex, addressIndex is within the current map viewport, change
     * the marker's icon to a suitably highlighted one.
     */
    highlightAddress: function(providerIndex,addressIndex) {
      if(this.providers.size() == 0) return;
      var address = this.providers[providerIndex].addresses[addressIndex];
      if(UCHC.withinViewport($(this.mapId),0.01)) {
        if(this.highlightedAddress) this.highlightedAddress.marker.setImage(highlightedAddress.markerImage);
        this.highlightedAddress = address;
        this.highlightedAddress.marker.setImage('/img/markers/yellow_Marker'+address.markerLetter+'.png');
      }
    },
    
    /**
     * Unhighlight the currently highlighted marker; if there is one.
     */
    unhighlightAddress: function() {
      if(this.highlightedAddress) this.highlightedAddress.marker.setImage(this.highlightedAddress.markerImage);
      this.highlightedAddress = null;
    },
    
    toggleDrReportAddress: function(id) {
      var img = $$('.dr_'+id+'_address .contract').first();
      if(img.src.substring(img.src.length-19) == 'collapse_button.png')
        img.src='/img/expand_button.png';
      else img.src='/img/collapse_button.png';
      $$('.dr_'+id+'_address div').invoke('toggle');
      $$('.dr_'+id+'_address_cont').invoke('toggle');
    },
    
    toggleSearchAddresses: function(id) {
      var img = $('address_reveal_'+id).down(1);
      var span = img.next();
      if(img.src.substring(img.src.length-19) == 'collapse_button.png') {
        span.innerHTML = 'Show more addresses';
        img.src='/img/expand_button.png';
      } else {
        span.innerHTML = 'Hide addresses';
        img.src='/img/collapse_button.png';
      }
      $('extra_addresses_'+id).toggle();
    },
    
    getReportLink: function(key,random) {
        // add provider to queue
        $('add_provider_id_2').value=key;
        $('add_provider_rand_2').value=random;
        $('get_provider_report_form').submit();
    },
    
    reportProviderId: function(add,key,random) {
      if(add) {
        // add provider to queue
        $('add_provider_id').value=key;
        $('add_provider_rand').value=random;
        $('add_provider_form').submit();
      } else {
        // remove provider from queue
        $('remove_provider_id').value=key;
        $('remove_provider_form').submit();
      }
    },
    
    swapProviderInCart: function(removeId,addId,addRandom) {
      $('replace_provider_id').value = removeId;
      $('add_provider_id').value     = addId;
      $('add_provider_rand').value   = addRandom;
      $('add_provider_form').submit();
    },

	/**
	 * Create and add markers for the addresses found in this.providers
	 * just like displayProviders except that eery provider will have a dot_red instead of a big letter marker
	 */
	displayDotProviders: function() {
	  var addressIcon = new GIcon(G_DEFAULT_ICON);
	  var addressIndex = 0;
	  var map = this.map;
	  var colors = this.colors;
	  
	  var bounds = map.getBounds();

      addressIcon.image = '/img/markers/dot_red.png';
      addressIcon.shadow = null; 
      addressIcon.iconSize = new GSize(12,12);
      addressIcon.iconAnchor = new GPoint(6,6);
      
	  this.providers.each(function(provider,index) {
	    provider.addresses.each(function(address){
	      marker = new GMarker(address.latlng,{ icon:addressIcon,title:provider.name });
	      marker.bindInfoWindowHtml(address.formatted);
	      map.addOverlay(marker);
	      address.marker = marker;
	      address.markerLetter = String.fromCharCode(65+addressIndex);
	      address.markerImage = addressIcon.image;
	      addressIndex = (addressIndex+1)%26;
	      
	      // ensure we're not clipping any marker icons
	      bounds = extendBoundsByMarkerIcon(bounds,marker,map);
	    });
	  });
	  
	  map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));
	},

	/**
	 * If the map is on within the current viewport and the marker given by
	 * providerIndex, addressIndex is within the current map viewport, change
	 * the marker's icon to a suitably highlighted one.
	 */
	highlightDotAddress: function(providerIndex,addressIndex) {
	  if(this.providers.size() == 0) return;
	  var address = this.providers[providerIndex].addresses[addressIndex];
	  if(UCHC.withinViewport($(this.mapId),0.01)) {
	    if(this.highlightedAddress) this.highlightedAddress.marker.setImage(highlightedAddress.markerImage);
	    this.highlightedAddress = address;
	    this.highlightedAddress.marker.setImage('/img/markers/dot_yellow.png');
	  }
	},
	
	/**
	 * Unhighlight the currently highlighted marker; if there is one.
	 */
	unhighlightDotAddress: function() {
	  if(this.highlightedAddress) this.highlightedAddress.marker.setImage(this.highlightedAddress.markerImage);
	  this.highlightedAddress = null;
	},
	
	
	
	/**
	 * This function sets up the ind_provider array of locations withe the blue_MarkerDot icon
	 * Intended for use with individual pages which will have a pronounced marker (this one)
	 * for the provider that "owns" this page, and a smaller marker for the other local providers.
	 * (see /inc/dynamic/pharmacy/view_provider.php for usage example)
	 */
	displayLocalProviders: function() {
	  var addressIcon = new GIcon(G_DEFAULT_ICON);
	  var addressIndex = 0;
	  var map = this.map;
	  var colors = this.colors;
	  
	  var bounds = map.getBounds();

      addressIcon.image = '/img/markers/blue_MarkerDot.png';
      addressIcon.shadow = null; 
      addressIcon.iconSize = new GSize(20,34);
      
	  this.ind_provider.each(function(provider,index) {
	    provider.addresses.each(function(address){
	      marker = new GMarker(address.latlng,{ icon:addressIcon,title:provider.name });
	      map.setCenter(address.latlng);
	      marker.bindInfoWindowHtml(address.formatted);
	      map.addOverlay(marker);
	      address.marker = marker;
	      address.markerLetter = String.fromCharCode(65+addressIndex);
	      address.markerImage = addressIcon.image;
	      addressIndex = (addressIndex+1)%26;
	      
	      // ensure we're not clipping any marker icons
	      bounds = extendBoundsByMarkerIcon(bounds,marker,map);
	    });
	    
	  });
	  map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));
	}
	
  };
})();


	
// Bind public methods
Object.extend(UCHC, UCHC.Methods);
}

/**
 * Find all rel="external" links and add a target="_blank" attribute, so that
 * we can have standards-compliant links opening new windows
 */
Event.observe(window,'load',function() {
  if (!document.getElementsByTagName) return;
    var anchors = document.getElementsByTagName("a");
    for (var i=0; i<anchors.length; i++) {
      var anchor = anchors[i];
      if(anchor.getAttribute("href") &&
         anchor.getAttribute("rel") == "external")
        anchor.target = "_blank";
    }
  }
);