// these are essentially constants that should not be modified in the functions below
var n_bound     = 55.055;       // northern extent of full image (degrees latitude)
var s_bound     = 9.1733;       // southen extent of full image (degrees latitude)
var w_bound     = -126.1;       // western extent of full image (degrees longitude)
var e_bound     = -59.4527;     // eastern extent of full image (degrees longitude)
var v_tiles     = 11;           // number of tiles on vertical axis
var h_tiles     = 16;           // number of tiles on horizontal axis
var tile_size   = 500;          // size of the tiles in pixels
var tile_dir    = 'http://www.jshine.net/astronomy/dark_sky/tiles/';  // root directory of tile images

// these variables hold important objects
var map         = null;         // the map variable -- can't forget this
var mgr         = null;         // the marker manager: holds site markers for display on the map
var mkr		= null;		// the single draggable marker for adding a new site
var icon        = null;         // holds the custom icon to display existing sites

// these variables hold the current general state of the map display
var redraw      = false;        // whether or not to draw the pollution when the map is moved
var state       = 0;            // this variable holds the current state of the page (0=browsing, 1=adding)
var selected_id = -1;           // this variable holds the currently selected site (less than 0 = no selection)

// these are variables and constants realated to the light pollution overlay tiles
// "v_array" and "h_array" are constants whose values are defined in a function and should not be changed thereafter
var v_array     = new Array();  // the array of latitude break-points (v_tiles+1)  [CONSTANT]
var h_array     = new Array();  // the array of longitude break-points (h_tiles+1) [CONSTANT]
var disp        = new Array();  // is this tile displayed? (v_tiles x h_tiles) (value = 'overlays' index)
var overlays    = new Array();  // this array holds the light pollution overlay objects (one per displayed tile)


/* ----- End variable declarations, begin function declarations. ----- */


function setup_map(center_lat, center_lng, zoom, selected_id_init, pollution)
{
  if (GBrowserIsCompatible())
  {
    // draw the base map and add some controls
    map = new GMap2(document.getElementById("map"));
    map.setCenter(new GLatLng(44.02, -92.49), 6);
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl());

    // position map
    var center = new GLatLng(center_lat,center_lng);
    map.setCenter(center, zoom);

    // draw the initial light pollution layer
    define_tiles();
    if (pollution)
    {
      add_pollution();
    }

    // remember which site was selected at the beginning
    selected_id = selected_id_init;

    // add an event listener to the map to (possibly) redraw the pollution layer if the coordinate-bounds change
    GEvent.addListener(map, 'moveend', function() {freshen_tiles()});

    // populate the new map with markers from database
    mgr = new GMarkerManager(map);
    create_icon();
    fetch_markers(selected_id);

    // if the map is moved, add additional markers for the new location (ajax here!)
    GEvent.addListener(map, 'moveend', function() {fetch_markers(-1)});
  }
}


/* ----- These functions deal with the light pollution layer. ----- */


/* This function toggles the current state of the light pollution display.
 */
function toggle_pollution()
{
  if (redraw)
  {
    // pollution is on, turn it off
    clear_pollution();
  }
  else
  {
    // pollution is off, turn it on
    add_pollution();
  }
}

/* This function is called to add the light pollution layer to the map when no pollution layer
 * is being displayed yet.  It is called when the map is first loaded or when the "Add Pollution" 
 * button is pressed.
 */
function add_pollution()
{
  redraw = true;
  freshen_tiles();
}


/* This function will remove all light pollution tiles from the map.
 */
function clear_pollution()
{
  var index = null;
  for (var v = 0; v < v_tiles; v++)
  {
    for (var h = 0; h < h_tiles; h++)
    {
      // check if this tile is being displayed     
      if (disp[v][h] >= 0)
      {                  
        // hide overlay
        index = disp[v][h];
        overlays[index].hide();
      }
    }
  }
  // make sure we don't re-draw the tiles if the user just moves the map
  redraw = false;
}    


/* This function determines which tiles must be added to cover the current view and adds those 
   that are not already being displayed.
 */
function freshen_tiles()
{
  if (redraw)
  {
    var index = null;
    var view = map.getBounds();
    var n = view.getNorthEast().lat();
    var s = view.getSouthWest().lat();
    var w = view.getSouthWest().lng();
    var e = view.getNorthEast().lng();

    // loop through over rows (latitude values) from north to south
    for (var v = 1; v <= v_tiles; v++)
    {
      // determine whether or not this latitude range overlaps with our viewing window
      if ((v_array[v] < n) && (v_array[v-1] > s))
      {
        // we will consider this latitude of tiles, now loop through from west to east
        for (var h = 1; h <= h_tiles; h++)
        {
          // determine whether or not this tile belongs on our chart
          if ((h_array[h] > w) && (h_array[h-1] < e))
          {
            // detemine whether or not this tile has already been fetched
            if (disp[v-1][h-1] < 0)
            {
              // tile has not been fetched, so fetch it and display
              add_tile(v,h);
            }
            else
            {
              // tile has been fetched, so be sure it's set to be displayed
              // note: we will do this whether the tile is already displayed or not!
              index = disp[v-1][h-1];
              overlays[index].show();
            }
          }
        }
      }
    }
  }
}


/* This function adds one tile to the map.  The tile should not already be there!
 */
function add_tile(vert,horiz)
{
  // build up the systematic filename of the tile
  var tile_url = tile_dir + '/' + vert + '_' + horiz + '.png';

  // determine the geographic boundaries of this tile
  var sw = new GLatLng(v_array[vert], h_array[horiz-1]);
  var ne = new GLatLng(v_array[vert-1], h_array[horiz]);

  // add this image as an overlay, given these boundaries
  var tile_bounds = new GLatLngBounds(sw, ne);
  var tile = new GGroundOverlay(tile_url, tile_bounds)
  map.addOverlay(tile);

  // store this overlay in the overlays vector so it can be changed later
  overlays.push(tile);

  // set the 'displayed' status of this tile to its index in the 'overlays' vector
  disp[vert-1][horiz-1] = overlays.length - 1;
}


/* This function performs some initial setup to define the tiles used in the light
 * pollution layer.  It only needs to be called once when the page is initially loaded.
 */
function define_tiles()
{
  // first, define the longitude & latitude boundaries of the tiles
  for (var v = 0; v <= v_tiles; v++)
  {
    v_array[v] = n_bound - v/v_tiles*(n_bound-s_bound);
  }
  for (var h = 0; h <= h_tiles; h++)
  {
    h_array[h] = h/h_tiles*(e_bound-w_bound)+w_bound;
  }

  // now, initialize the displayed status of all the tiles to false
  for (var v = 0; v < v_tiles; v++)
  {
    disp[v] = new Array();
    for (var h = 0; h < h_tiles; h++)
    {
      // disp[v][h] gives the index of the 'overlays' array that holds tile v,h
      // negative values (nonsensical indices) indicate that the tile is not displayed
      disp[v][h] = -1;
    }
  }
}


/* ----- These functions deal fetching & displaying sites from the database ----- */


/* This function fetches markers (via ajax) from within the current map boundaries.
 * It places markers at their proper location on the visible map.  All markers returned 
 * from the xml document are plotted; it is up to the php script to avoid returning a 
 * marker more than once!  If the marker whose id is "selected_id" appears within a 
 * query result, then its info window is opened as though it had been clicked.
 * Negative values may be uesed for "selected_id" if no marker selection is desired.
 */
function fetch_markers(selected_id)
{
  // first, figure out where the boundaries of the map are
  var selected  = null;
  var bounds    = map.getBounds();
  var se        = bounds.getSouthWest();
  var nw        = bounds.getNorthEast();
  var lat_min   = se.lat();
  var lat_max   = nw.lat();
  lng_min = Math.min(nw.lng(), se.lng());
  lng_max = Math.max(nw.lng(), se.lng());
  // now download an xml document containing site positions within the map boundaries
  var url = 'xml/fetch_coords.php?lat_min=' + lat_min.toString() + '&lat_max=' + lat_max.toString();
  url = url + '&lng_min=' + lng_min.toString() + '&lng_max=' + lng_max.toString();
  GDownloadUrl(url, function(data)
  {
    var xml = GXml.parse(data);
    var markers = xml.documentElement.getElementsByTagName("site");
    // parse xml document & add a marker at each location
    for (var i = 0; i < markers.length; i++)
    {
      var id    = parseFloat(markers[i].getAttribute('id'));
      var lat   = parseFloat(markers[i].getAttribute('lat'));
      var lng   = parseFloat(markers[i].getAttribute('lng'));
      if (id == selected_id)
      {
        selected = true;
      }
      else
      {
        selected = false;
      }
      create_marker(new GLatLng(lat, lng), id, 0, selected);
    }
  });
}


/* This function adds a marker to the map at the specified point.
 * It also adds an event handler to listen for clicks on that marker.
 * If a click is detected, show_info() is called to display information.
 * Input "movable" is true or false.  If true, it also updates the appropriate HTML form.
 * Input "id" is a database ID or for points not in the DB, it should be negative.
 * Points in the DB (id > 0) cannot be movable.  Input "selected" describes whether or
 * not this marker should have its' info window opened, as though it had been clicked.
 * This parameter is ignored if the marker is not in the database.
 */
function create_marker(point, id, movable, selected)
{
  if (id > 0)   // marker is in database
  {
    var marker = new GMarker(point, icon);
    GEvent.addListener(marker, "click", function () {show_info(id, marker)});
    mgr.addMarker(marker, rnd_zoom());
    if (selected)
    {
      show_info(id, marker)
    }
  }
  else          // marker is not in database
  {
    if (movable)        // marker should be movable
    {
      mkr = new GMarker(point, {draggable: true});
      GEvent.addListener(mkr, "drag", function() {update_form(mkr.getPoint())});
      map.addOverlay(mkr);
    }
    else                // marker is fixed
    {
      var marker = new GMarker(point, icon);
      mgr.addMarker(marker, 0);
    }
  }
}


/* This function opens up an information box above a given tree.
 * It populates the information box with data that it fetches from 
 * the database (another ajax step).
 */
function show_info(id, marker)
{
  // remember which site we're viewing
  selected_id = id;
  // construct the URL of the XML document containing the data
  var url = 'xml/fetch_info.php?id=' + id.toString();
  // fetch xml document from server
  GDownloadUrl(url, function(data)
  {
    var info		= null;
    // perform initial parsing of xml document (more parsing steps below!)
    var xml 		= GXml.parse(data);
    var site_ele 	= xml.getElementsByTagName("site");
    var address_ele	= site_ele[0].getElementsByTagName("address");
    var name_ele	= site_ele[0].getElementsByTagName("name");
    var name_l_ele	= site_ele[0].getElementsByTagName("name_l");
    var affil_ele	= site_ele[0].getElementsByTagName("affil");
    var affil_l_ele	= site_ele[0].getElementsByTagName("affil_l");
    var owner_ele	= site_ele[0].getElementsByTagName("owner");
    var fee_ele		= site_ele[0].getElementsByTagName("fee");
    var access_ele	= site_ele[0].getElementsByTagName("access");
    var sky_ele		= site_ele[0].getElementsByTagName("sky");
    var wx_l_ele	= site_ele[0].getElementsByTagName("wx_l");
    var wx_type_ele     = site_ele[0].getElementsByTagName("wx_type");
    var pads_ele	= site_ele[0].getElementsByTagName("pads");
    var parking_ele	= site_ele[0].getElementsByTagName("parking");
    var b_rooms_ele	= site_ele[0].getElementsByTagName("b_rooms");
    var sleep_ele	= site_ele[0].getElementsByTagName("sleep");
    var notes_ele	= site_ele[0].getElementsByTagName("notes");

    if (address_ele.length > 0) document.getElementById('disp_address').innerHTML = address_ele[0].firstChild.nodeValue;
    else document.getElementById('disp_address').innerHTML = '';

    if (name_ele.length > 0)
    {
      if (name_l_ele.length > 0)
      {
        var name = name_ele[0].firstChild.nodeValue;
        var name_l = name_l_ele[0].firstChild.nodeValue;
        info = '<a href="' + name_l + '">' + name + '</a>';
        document.getElementById('disp_name').innerHTML = '<a href="' + name_l + '">' + name + '</a>';
      }
      else
      {
        info = name_ele[0].firstChild.nodeValue;
        document.getElementById('disp_name').innerHTML = info;
      }
    }
    else document.getElementById('disp_name').innerHTML = '';

    if (affil_ele.length > 0)
    {
      if (affil_l_ele.length > 0)
      {
        var affil = affil_ele[0].firstChild.nodeValue;
        var affil_l = affil_l_ele[0].firstChild.nodeValue;
        document.getElementById('disp_affil').innerHTML = '<a href="' + affil_l + '">' + affil + '</a>';
      }
      else
      {
        document.getElementById('disp_affil').innerHTML = affil_ele[0].firstChild.nodeValue;
      }
    }
    else document.getElementById('disp_affil').innerHTML = '';

    if (owner_ele.length > 0) document.getElementById('disp_owner').innerHTML = owner_ele[0].firstChild.nodeValue;
    else document.getElementById('disp_owner').innerHTML = '';

    if (fee_ele.length > 0) document.getElementById('disp_fee').innerHTML = fee_ele[0].firstChild.nodeValue;
    else document.getElementById('disp_fee').innerHTML = '';

    if (access_ele.length > 0) document.getElementById('disp_access').innerHTML = access_ele[0].firstChild.nodeValue;
    else document.getElementById('disp_access').innerHTML = '';

    if (sky_ele.length > 0) document.getElementById('disp_sky').innerHTML = sky_ele[0].firstChild.nodeValue;
    else document.getElementById('disp_sky').innerHTML = '';

    if (wx_l_ele.length > 0)
    {
      if (wx_type_ele[0].firstChild.nodeValue == 1)
      {
        document.getElementById('disp_wx_l').innerHTML = '<img src="' + wx_l_ele[0].firstChild.nodeValue + '">';
      }
    }
    else document.getElementById('disp_wx_l').innerHTML = '';

    if (pads_ele.length > 0) document.getElementById('disp_pads').innerHTML = pads_ele[0].firstChild.nodeValue;
    else document.getElementById('disp_pads').innerHTML = '';

    if (parking_ele.length > 0) document.getElementById('disp_parking').innerHTML = parking_ele[0].firstChild.nodeValue;
    else document.getElementById('disp_parking').innerHTML = '';

    if (b_rooms_ele.length > 0) document.getElementById('disp_b_rooms').innerHTML = b_rooms_ele[0].firstChild.nodeValue;
    else document.getElementById('disp_b_rooms').innerHTML = '';

    if (sleep_ele.length > 0) document.getElementById('disp_sleep').innerHTML = sleep_ele[0].firstChild.nodeValue;
    else document.getElementById('disp_sleep').innerHTML = '';

    if (notes_ele.length > 0) document.getElementById('disp_notes').innerHTML = notes_ele[0].firstChild.nodeValue;
    else document.getElementById('disp_notes').innerHTML = '';

    // display information string
    var pre_info = 'Site ' + id.toString();
    if (info != null)
    {
      info = pre_info + ': ' + info;
    }
    else
    {
      info = pre_info;
    }
    marker.openInfoWindowHtml(info);
  })
}


/* This function calculates a random minimum zoom level to prevent
 * over-crowded maps.
 */
function rnd_zoom()
{
  // n is the maximum zoom level assigned.  It is related to the total size
  // of the database and should be periodically monitored & updated.
  // It can be assigned automatically, but it's not worth the trouble.
  var n = 0;
  var x = Math.random();
  var zoom = Math.max(0, Math.floor(1+n+Math.log(x)/Math.log(4)));
  return zoom;
}


/* This function creates a custom icon to represent existing ginkgo trees.
 */
function create_icon()
{
  icon                  = new GIcon();
  icon.image            = 'http://www.jshine.net/astronomy/dark_sky/images/purple-dot.png';
  icon.shadow           = 'http://www.jshine.net/astronomy/dark_sky/images/shadow50new.png';
  icon.iconSize         = new GSize(32, 32);
  icon.shadowSize       = new GSize(42, 32);
  icon.iconAnchor       = new GPoint(16, 32);
  icon.infoWindowAnchor = new GPoint(16, 2);
  icon.infoShadowAnchor = new GPoint(25, 25);
}


/* ----- These functions deal with the form and with misc dynamic page behavior ----- */


/* This function changes the page URL to a version that contains all the data necessary to return 
 * to the current view.
 */
function view_link()
{
  var lat  = map.getCenter().lat().toString();
  var lng  = map.getCenter().lng().toString();
  var zoom = map.getZoom().toString();
  var url = 'http://www.jshine.net/astronomy/dark_sky/index.php?lat=' + lat.toString() + '&lng=' + lng.toString();
  url = url + '&zoom=' + zoom.toString() + '&pollution=' + redraw.toString() + '&selected_id=' + selected_id.toString();
  location.href = url;
}


/* This function is called when the user clicks the "add site" button.  It hides
 * the displayed data regarding the currently selected site and exposes the data-entry form.
 * Also, a new moveable marker is added to the map.
 */
function add_site_click()
{
  // the first click brings up the form, the second click submits
  if (state == 0)
  {
    // close marker informational window
    map.closeInfoWindow();

    // remove data displayed on page
    set_vis('data_cell_', 13, false, 0);

    // expose the form
    set_vis('form_row_',  4,  true,  1);
    set_vis('form_cell_', 13, true,  2);

    // place movable marker at center of map
    var location = map.getCenter();
    create_marker(location, -1, 1, false);
    update_form(location);
    state = 1;
    alert('Please move the red "new site" marker to the location of your site and fill in the information form below the map.  Use the zoom-in control for more accurate placement.  Click "Add Site" again to submit.');
  }
  else if (state == 1)
  {
    // we have already displayed the form, so time to submit
    // do form validation here
    var lat  = map.getCenter().lat().toString();
    var lng  = map.getCenter().lng().toString();
    var zoom = map.getZoom().toString();
    document.new_site_form.action = 'add_site.php?lat=' + lat + '&lng=' + lng + '&zoom=' + zoom;
    document.new_site_form.submit();
  }
}


/* This function is called when the user wishes to cancel adding a new site.
 * It deletes the draggable marker, hides the data-entry form, and sets the "state" 
 * variable back to 0.
 */
function cancel_add()
{
  // only act if we currrently are in the "add site" state, otherwise do nothing
  if (state = 1)
  {
    // hide the draggable marker
    map.removeOverlay(mkr);

    // remove the form
    set_vis('form_row_',  4,  false,  0);
    set_vis('form_cell_', 13, false,  0);

    // replace the data-display cells
    set_vis('data_cell_', 13, true, 2);

    // set state back to 0
    state = 0;
  }
}


/* This function updates the position displayed in the HTML form.
 */
function update_form(pos)
{
  //document.new_site_form.lat.value = (Math.round(pos.lat()*1e5)/1e5).toString();
  //document.new_site_form.lng.value = (Math.round(pos.lng()*1e5)/1e5).toString();
  document.new_site_form.lat.value = pos.lat().toFixed(5);
  document.new_site_form.lng.value = pos.lng().toFixed(5);
}


/* This function prevents the form from submitting when the enter key is pressed.
 */
function key_press(event)
{
  if (event.keyCode == 13) return false
}


/* This function hides or displays a given element on the page.  It requires two classes
 * to be created in the style sheet.  The inputs are:
 * prefix: a string giving the prefix of the group of items, like "form_cell_"
 * max:    the maximum value of items to toggle -- an integer (the minimum is assumed to be 1)
 * show:   whether to show or hide this set of elements -- a boolean value
 * type:   what the type of the elements is.  The permissable values are:
 *         <=0:  type is irrelevent, use this when show = false
 *         1:    table row
 *         2:    table cell / column
 */
function set_vis(prefix, max, show, type)
{
  for (var i=1; i <= max; i++)
  {
    var elem = document.getElementById(prefix+i.toString());
    if (elem)
    {
      if (show)
      {
        elem.style.display = 'block';
        if (type == 1)
        {
          try {elem.style.display = 'table-row'} catch(err) {};
        }
        else if (type == 2)
        {
          try {elem.style.display = 'table-cell'} catch(err) {};
        }
      }
      else
      {
        elem.style.display = 'none';              
      }
    }
  }
}
