/* Copyright (C) 2007 Michael Kosowsky  All rights reserved. */

					//************************** GENERIC UI

var current = {
  magnetic: false,
  points: [],
  legs: [],
  total_dist: 0,
  latlon_mode: 0,
  show_grade: 0,
  use_metric: 0
};


var colors = [ 'ff0000', '00c000', '0000ff' ];

function backspace() {
  if (current.points.length == 0)
    return;
  if (current.points.length == 1) {
    clear();
    return;
  }
  var p = current.legs.pop();
  if (p) {
    map.removeOverlay(p.line);
    current.total_dist -= p.dist;
  }
  p = current.points.pop();
  map.removeOverlay(p.marker);
  write_list_div();
  //write_bearing_div();
}

function clear() {
  current.legs._foreach(function(p) { map.removeOverlay(p.line); });
  current.points._foreach(function(p) { map.removeOverlay(p.marker); });
  current.points = [];
  current.legs = [];
  current.total_dist = 0;
  write_list_div();
  //write_bearing_div();
}


function show_faq() {
  window.open('faq.html', '_faq', 'height=600,width=500,scrollbars');
}

function email_us() {
  location.href = 'mailto:comments-profiler@hey' + '' + 'what' + 'sthat.com';
}

function set_show_grade(x) {
  current.show_grade = x;
  document.getElementById('show_grade').checked = x;
  reset_current_points();
}

function set_latlon_mode(x) {
  current.latlon_mode = x;
   // same cookie as main site index.html ('df' is 'degrees_format')
  set_cookie('df', x);
  update_latlon_div();
  reset_current_points();
}

function set_metric(x) {
  current.use_metric = x;
  set_cookie('units', x);
  radio_set(units_radio, x);
  reset_current_points();
  redraw_contour_overlay();
}

function set_curvature(x) {
  current.curvature = x;
  radio_set(flat_earth_radio, x);
}


var set_lat_lon_timeout_id = 0;

function handle_map_click(overlay, point) {
  if (!point)
    return;
  add_point(point.y, point.x);
}

function set_points(a) {
  clear();
  a._foreach(function(x) { add_point(x.lat, x.lon, x.elev) });
}

function reset_current_points() {
  set_points(current.points);
}


function add_point(lat, lon, elev) {
  lat = round6(lat);
  lon = round6(lon);

  var latlng = new GLatLng(lat, lon);

  if (elev == null)
    elev = get_elev(lat, lon);


  if (current.points.length > 0) {
    var pp = current.points[current.points.length - 1];
    var color = '#' + colors[(current.points.length - 1) % colors.length];
    var b = bearing_and_range(pp.lat, pp.lon, lat, lon);
    current.total_dist += b[1];
    var rb = bearing_and_range(lat, lon, pp.lat, pp.lon);

    var start_elev;
    var prev_elev;
    if (current.points.length > 0) {
	start_elev = current.points[0].elev;
	prev_elev  = current.points[current.points.length - 1].elev;
    }
    var dist = (elev != null && prev_elev != null)? Math.sqrt(b[1] * b[1] + (elev - prev_elev) * (elev - prev_elev)) : b[1];

    var l = {line: new GPolyline([latlng, pp.marker.getPoint()], color, 5, .5),
	     dist: dist,
	     html:   '<tr align=right style="color: ' + color + '">'
                   + '<td title="reverse bearing ' + round0(adjust_for_declination(rb[0])) + '">'
                      + round0(adjust_for_declination(b[0])) + '&deg;</td>'
                   + '<td>' + miles_or_km1(dist) + '</td>'
		   + '<td>' + miles_or_km0_no_label(current.total_dist) + '&nbsp;total</td></tr>'
		   + (current.show_grade && elev != null && start_elev != null && prev_elev != null?
		          '<tr align=right style="color: ' + color + '">' 
                        + '<td>' + feet_or_meters(elev - prev_elev) + ' (' + percent1((elev - prev_elev)/b[1]) + ')</td>'
		        + '<td>' + feet_or_meters_no_label(elev - start_elev) + '&nbsp;total</td></tr>'
                      : '')
            };
    current.legs.push(l);
    map.addOverlay(l.line);
  }

  var p = {lat:  lat,
	   lon:  lon,
	   elev: elev,
	   html: point_html(lat, lon, elev),
	   marker: new GMarker(latlng, { icon: icon_plus, clickable: false, title: lat_lon_to_string(lat, lon, 0) })
	  };

  current.points.push(p);
  map.addOverlay(p.marker);

  write_list_div();
  //write_bearing_div();
}


function point_html(lat, lon, elev) {
  return           '<tr align=right><td>' + format_angle(lat, current.latlon_mode, 'N', 'S', 1) + '</td>'
                 + '<td>' + format_angle(lon, current.latlon_mode, 'E', 'W', 1) + '</td>'
		 + (elev != null? '<td>' + feet_or_meters(elev) + '</td>' : '')
	         + '</tr>';
//	   html:   '<tr align=right><td>' + format_angle(lat, current.latlon_mode, 'N', 'S', 1) + '</td>'
//                 + '<td>' + format_angle(lon, current.latlon_mode, 'E', 'W', 1) + '</td>'
//	         + '</tr>',
}


function handle_map_mousemove(latlng) {
  if (!latlng)
    return;
  update_latlon_div(latlng.lat(), latlng.lng());
}


var lastlat;
var lastlon;
function update_latlon_div(lat, lon) {
  if (lat != null && lon != null) {
    lastlat = lat;
    lastlon = lon;
  }
  if (lastlat != null && lastlon != null)
    map_latlon_div.innerHTML = lat_lon_to_string(lastlat, lastlon, 1);
}


function write_list_div() {
  set_list_div_style('300px', current.points.length > 12);

  if (!current.points.length) {
    list_div.innerHTML = '';
    return;
  }

  var s = '<table>';
  for (var i = 0; i < current.points.length - 1; i++)
    s += current.points[i].html + current.legs[i].html;
  s += current.points[current.points.length - 1].html + '</table>';
  list_div.innerHTML = s;
}


function add_location(s, callback) {
  geocode(s, function(l) { add_point(l.lat(), l.lng()); map.setCenter(l, 12); if (callback) callback(); });
}

function handle_location() {
  if (!document.f_location.location.value)
    return;
  add_location(document.f_location.location.value);
}

function get_route() {
  if (current.points.length < 2)
    return;
  var p  = current.points[current.points.length - 1];
  var pp = current.points[current.points.length - 2];
  wt_async_request_array_of_lines('bin/directions.cgi?max=30&elevs=1&q=from+' + pp.lat + ',' + pp.lon + '+to+' + p.lat + ',' + p.lon,
		'Directions', parse_directions);
}

function point_rectangle(p1, p2) {
  var lat0 = p1.lat;
  var lon0 = p1.lon;
  var lat1 = p2.lat;
  var lon1 = p2.lon;

  if (lat0 > lat1) { var x = lat0; lat0 = lat1; lat1 = x; }
  if (lon0 > lon1) { var x = lon0; lon0 = lon1; lon1 = x; }
  return new GLatLngBounds(new GLatLng(lat0, lon0), new GLatLng(lat1, lon1));
}

function parse_directions(a) {
  if (a.length > 100)
    return;
  backspace(); // directions end with this point
  a._foreach(function(b) { var c = b.split(' '); add_point(c[0], c[1], c[2]); });
  var r = point_rectangle(current.points[0], current.points[current.points.length - 1]);
  map.setCenter(r.getCenter(), map.getBoundsZoomLevel(r));
}


function checkpoint() {
  var s = '';
  var i = 1;
  current.points._foreach(function(a) { s += '&ll' + i + '=' + a.lat + ',' + a.lon; i++ });
		// use_metric and latlon_mode are saved in a cookie
  location.href = 'profiler.html'
		  + '?show_grade=' + current.show_grade
                  + '&curvature='  + current.curvature
                  + s;
}



					//***************************** CONTOURS
var contour_overlay;
var contour_widget;
var contour_listener;

				 // 0     1     2     3     4     5     6     7    8    9   10   11   12   13  14  15  16  17
var contour_interval_ft_array = [2500, 2500, 2500, 2500, 2500, 1000, 1000, 1000, 500, 250, 100, 100, 100, 100, 50, 25, 10];
var contour_interval_m_array  = [1000, 1000,  750,  750,  750,  250,  250,  250, 200, 100,  50,  50,  25,  25, 25, 10,  3];

function contour_interval_ft(z) {
  if (z >= contour_interval_ft_array.length)
    return contour_interval_ft_array[contour_interval_ft_array.length - 1];
  return contour_interval_ft_array[z];
}

function contour_interval_m(z) {
  if (z >= contour_interval_m_array.length)
    return contour_interval_m_array[contour_interval_m_array.length - 1];
  return contour_interval_m_array[z];
}

function contour_interval_with_units(z) {
  return current.use_metric? contour_interval_m(z) + ' m' : contour_interval_ft(z) + ' ft';
}

function contour_interval(z) {
  return current.use_metric? contour_interval_m(z) : contour_interval_ft(z) / feet_per_meter;
}

function redraw_contour_overlay() {
  if (contour_overlay) {
    show_contour_overlay();
    if (contour_widget)
      contour_widget.set_title();
  }
}

function show_contour_overlay() {
  if (contour_overlay)
    map.removeOverlay(contour_overlay);
  contour_overlay = new GTileLayerOverlay(TileLayer(0, 17, 'Contours (C) 2007', 'Michael Kosowsky',
	    function(point, zoom) {
	      // if (!contour_intervals[zoom])
	      //  return 'images/no-contour.png';
	      // force blue color if looking at anything other than 'map' type
		 return 'http://contour.heywhatsthat.com/bin/contour_tiles.cgi?x=' + point.x + '&y=' + point.y + '&zoom=' + zoom
		                           + '&interval=' + contour_interval(zoom)
					   + (map.getCurrentMapType().getName() != 'Map'? '&color=0000FF30' : '');
            }));
  map.addOverlay(contour_overlay);
}

function create_contour_widget(x, width) {
  WTGControl(map, x, width, 'Contours', '<b>Contours</b>',
    function() {
      return this.state?
		 'contour interval ' + contour_interval_with_units(map.getZoom())
               : 'Click to see contours';
    },
    function(i) {
      if (i) {
	show_contour_overlay();
      } else {
	if (contour_overlay)
          map.removeOverlay(contour_overlay);
	contour_overlay = null;
      }
    },
    function() {
      contour_widget = this;
    });

  contour_listener = GEvent.addListener(map, "zoomend", contour_zoom_callback);
}


function contour_zoom_callback(oldz, newz) {
  if (contour_widget)
    contour_widget.set_title();
}



	// Google Maps Bug: copyright stuff seems to only work for new map types, NOT for tile overlays ??
function TileLayer(min_zoom, max_zoom, copyright_prefix, copyright, url_function) {
  var cc = new GCopyrightCollection(copyright_prefix);
  cc.addCopyright(new GCopyright(2, new GLatLngBounds(new GLatLng(-54,-180), new GLatLng(60,180)), min_zoom, copyright));
  var t = new GTileLayer(cc, min_zoom, max_zoom);
  t.getTileUrl = url_function;
  return t;
}


					//***************************** TRANSECTS
var transect_widget;

function init_transect() {
    document.getElementById('transect_img').src = 'images/profile-hit-calculate.png';
}

function draw_transect(warn) {
  if (current.points.length < 2) {
    if (warn)
      alert("Click on at least two points,\nthen hit 'calculate'");
    return;
  }

  var s = '';
  var i = 0;

  current.points._foreach(function(a) { s += '&pt' + i + '=' + a.lat + ',' + a.lon + ',' + colors[i % colors.length ]; i++ });
  document.getElementById('transect_img').src = 'bin/profile.cgi?src=profiler&axes=1&curvature=' + current.curvature + '&metric=' + current.use_metric + s;
}


function clear_transect() {
  if (current.show_transect)
    document.getElementById('transect_img').src = 'images/no-profile.png';
}



					//***************************** CONTROLS

function WTControl(parent, n_states, enablef, disablef, innerHTMLf, titlef, clickf) {
  this.div        = document.createElement('div');
  parent.appendChild(this.div);
  this.style      = this.div.style;
  this.n_states   = n_states;
  this.enablef    = enablef;
  this.disablef   = disablef;
  this.innerHTMLf = innerHTMLf;
  this.clickf     = clickf;
  this.titlef     = titlef;
  this.state      = 0;
  this.enabled    = 0;
  this.enable();
  this.update();
}

WTControl.prototype.enable = function()  {
  this.enablef(this.div);
  var t = this;
  this.div.onclick = function() { t.onclick(); };
  this.enabled = 1;
  this.set_title();
}

WTControl.prototype.disable = function() {
  this.enabled = 0;
  this.disablef(this.div);
  this.div.onclick = null;
  this.set_title();
}

WTControl.prototype.show = function() {
  this.style.display = '';
}

WTControl.prototype.hide = function() {
  this.style.display = 'none';
}

WTControl.prototype.is_visible = function() {
  return this.style.display != 'none';
}

WTControl.prototype.update = function() {
  this.div.innerHTML = exec_or_value(this.innerHTMLf, this, this.state);
  this.set_title();
}

WTControl.prototype.callback = function() {
  if (this.clickf)
    this.clickf(this.state);
}

WTControl.prototype.onclick = function() {
  this.state++;
  if (this.state >= this.n_states)
    this.state = 0;
  this.update();
  this.callback();
}

//WTControl.prototype.clear_title = function() {
// this.div.title = null;
//}

WTControl.prototype.set_title = function() {
  if (this.titlef)
    this.div.title = exec_or_value(this.titlef, this);
}

WTControl.prototype.set_state = function(state) {
  this.state = state;
  this.update();
}

WTControl.prototype.trigger = function(state) {
  this.state = state;
  this.update();
  this.callback();
}

WTControl.prototype.reset = function() {
  this.trigger(0);
}



	// for these guys, the action happens when the map calls initialize
function WTGControl(map, x, width, text0, text1, titlef, onclick, oncreate) {
  var c = new GControl(0, 0);
  c.initialize = function(map) {
    var w = new WTControl(map.getContainer(), 2,
	          function(d) { s = d.style;
				s.border          = '1px solid black'
				s.padding         = '0px 3px';
				s.backgroundColor = 'white';
				s.color           = 'black';
				s.fontSize        = '12px';
				s.fontFamily      = 'Arial,sans-serif';
				s.cursor          = 'pointer';
				s.width           = width + 'px';
				s.textAlign       = 'center';
	          },
		  null,  // for disable, tried  function(d) { d.style.color = '#606060'; },
		  function(i) { return i? text1 : text0; },
		  titlef,
		  onclick
            );

    if (oncreate)
      oncreate.call(w);
    return w.div;
  };
  map.addControl(c, new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(x, 30)));
}

function answerbutton_enable(d) {
  d.className = 'answerbutton';
}


					//***************************** ICONS

	// plus sign is current cursor (e.g. lat/lon for next query)
var icon_plus = new GIcon();
icon_plus.image = "images/black-plus.png";
icon_plus.shadow = null;
icon_plus.iconSize = new GSize(24, 24);
icon_plus.shadowSize = new GSize(0, 0);
icon_plus.iconAnchor = new GPoint(12, 12);


					//**************************** ELEVATION DATA CACHE
var elevs = new Object;

function get_elev(lat, lon) {
  if (elevs[lat + ' ' + lon] != null)
    return elevs[lat + ' ' + lon];
  wt_async_request_array_of_lines('bin/points.cgi?lat0=' + lat + '&lon0=' + lon, 'Elevation', receive_elev);
  return null;
}

function receive_elev(a) {
  if (a.length > 100)
    return;
	// points.cgi returns lat/lon with 6 digits of precision, including trailing zeroes,
	// but round6() strips trailing zeroes and that's what we've stored in p.lat and p.lon 
  a._foreach(function(b) { var c = b.split(' '); if (c.length == 3) elevs[round6(c[0]) + ' ' + round6(c[1])] = c[2]; });
  check_elevs();
}

function check_elevs() {
  var got_one = 0;
  current.points._foreach(function(p) {
    var e;
    if (p.elev == null && (e = elevs[p.lat + ' ' + p.lon]) != null) {
      p.elev = e;
      p.html = point_html(p.lat, p.lon, e);
      got_one = 1; }
    });

  if (got_one)
		// BUG: just redo html of legs before and after this point, not the whole thing
		//      (if it's the start point redo the whole thing)
    if (current.show_grade)
      reset_current_points();
    else
      write_list_div();
}


					//***************************** MISCELLANEOUS

function miles_or_km0_no_label(x) {
  return round0(current.use_metric? x/1000 : x/1609.344);
}

function miles_or_km1(x) {
  return current.use_metric?  round1(x/1000).toFixed(1)     + '&nbsp;km'
	                    : round1(x/1609.344).toFixed(1) + '&nbsp;miles';
}

function pretty_degrees(x, pos, neg) {
    return round6(Math.abs(x)) + '&deg;' + (x >= 0? pos : neg);
}

function pretty_lat(x) {
    return pretty_degrees(x, 'N', 'S');
}

function pretty_lon(x) {
    return pretty_degrees(x, 'E', 'W');
}


var feet_per_meter = 3.2808399;

function feet_or_meters_no_label(x) {
  return current.use_metric? round0(x) : round0(x * feet_per_meter);
}

function feet_or_meters(x) {
  return current.use_metric? round0(x) + ' m' : round0(x * feet_per_meter) + ' ft';
}

function percent1(x) {
  return round1(x * 100) + '%';
}


function lat_lon_to_string(lat, lon, is_html) {
  return format_latlon(lat, lon, current.latlon_mode, is_html);
}


var DEGREES_TO_RADIANS = Math.PI / 180.;
var EARTH_RADIUS = 6367447;

function bearing_and_range(lat0, lon0, lat1, lon1) {
	// rotate so viewer at zero longitude
  lon1 -= lon0;

	// about 11cm.  prevents NaN values below
  if (Math.abs(lat1 - lat0) + Math.abs(lon1) < .000001)
     return [0, 0];

  lat0 *= DEGREES_TO_RADIANS;
  lat1 *= DEGREES_TO_RADIANS;
  lon1 *= DEGREES_TO_RADIANS;

  var sinlon1 = Math.sin(lon1);
  var coslon1 = Math.cos(lon1);
  var sinlat0 = Math.sin(lat0);
  var coslat0 = Math.cos(lat0);
  var sinlat1 = Math.sin(lat1);
  var coslat1 = Math.cos(lat1);

  var x = coslat1 * coslon1;
  var y = coslat1 * sinlon1;
  var z = sinlat1;

	// rotate about y axis so viewer at north pole
  var xx =  x * sinlat0 - z * coslat0;
  var yy =  y;
  var zz =  x * coslat0 + z * sinlat0;

  var theta = Math.PI - Math.atan2(yy, xx);
  var phi   = Math.acos(zz);
  return [ theta/DEGREES_TO_RADIANS, phi * EARTH_RADIUS];
}


function adjust_for_declination(d) {
  if (current.magnetic) {
    d -= current.declination;
    if (d < 0)
       d += 360;
    else if (d > 360)
       d -= 360;
  }
  return d;
}

function scroll_if_needed(e, div) {
  var i = e.offsetTop - div.scrollTop;
  if (i < 0)
    e.scrollIntoView();
  else if (i > parseInt(div.style.height) - 23)
    e.scrollIntoView(false);
}


/*
Trying to avoid scrollbars on the list_div
Due to silliness in CSS spec, on firefox (but interestingly
not on IE6 and IE7), once you ask for vertical scrollbar,
you have to specify a horizontal size. Further, layout gets
confused on firefox if you toggle the vertical scrollbar
on and off.
*/

function set_list_div_initial() {
  if (is_msie) {
    list_div.style.overflowY = 'auto';
  } else {
    list_div.style.overflow  = 'auto';
    list_div.style.height    = '500px';
  }
}

function set_list_div_style(min_width, show_vertical_scrollbar) {
  if (is_msie) {
    list_div.style.height    = show_vertical_scrollbar? '500px' : 'auto';
  } else {
    list_div.style.minWidth  = min_width;
  }
}


// hack to add overlay of feature data (until we implement it right)

var icon_data = new GIcon();
icon_data.image = "images/blue-plus.png";
icon_data.shadow = null;
icon_data.iconSize = new GSize(13, 13);
icon_data.shadowSize = new GSize(0, 0);
icon_data.iconAnchor = new GPoint(6, 6);

function getnamesdata() {
  var b = map.getCenter();
  wt_async_request_array_of_lines('bin/namesdata.cgi?lat0=' + b.lat() + '&lon0=' +  b.lng(),
	      'NAMES DATA', function(a) {
		a._foreach(function(s) {
	          var t = s.match(/(-?[\d\.]+) (-?[\d\.]+) ([\-\d]+) \S+ \S+ (.*)/);
		  if (t)
		    map.addOverlay(new GMarker(new GLatLng(t[1], t[2]),
				{ icon: icon_data, clickable: false, title: feet_or_meters(t[3]) + ' ' + t[4] }));
		});});
}


/**************************
// simplify adding hacks (cf. http://www.elsewhere.org/journal/gmaptogpx)
// just need a bookmark that looks like 'javascript:addon("hack.js")'

function addon(s) {
  var script = document.createElement('script');
  script.src = s;
  document.getElementsByTagName('head')[0].appendChild(script);
}
*****************************/
