<commit_before>console.log("start");
$(document).ready(startup);

/* The Big Nested Function
==========================*/

// Print to ensure file is loaded

function startup() {
  "use strict";
  console.log("trailhead.js");

  // Map generated in CfA Account
  var MAPBOX_MAP_ID = "codeforamerica.map-j35lxf9d";
  var AKRON = {
    lat: 41.1,
    lng: -81.5
  };

  // API_HOST: The API server. Here we assign a default server, then 
  // test to check whether we're using the Heroky dev app or the Heroku production app
  // and reassign API_HOST if necessary
  var API_HOST = "http://127.0.0.1:3000";
  // var API_HOST = "http://trailsyserver-dev.herokuapp.com";
  if (window.location.hostname.split(".")[0] == "trailsy-dev") {
    API_HOST = "http://trailsyserver-dev.herokuapp.com";
  } else if (window.location.hostname.split(".")[0] == "trailsy") {
    API_HOST = "http://trailsyserver-prod.herokuapp.com";
  }

  //  Near-Global Variables
  var METERSTOMILESFACTOR = 0.00062137;
  var MAX_ZOOM = 17;
  var MIN_ZOOM = 14;
  var SECONDARY_TRAIL_ZOOM = 13;
  var SHORT_MAX_DISTANCE = 2.0;
  var MEDIUM_MAX_DISTANCE = 5.0;
  var LONG_MAX_DISTANCE = 10.0;
  var SHOW_ALL_TRAILS = 1;
  var USE_LOCAL = 1; // Set this to a true value to preload/use a local trail segment cache
  var NORMAL_SEGMENT_COLOR = "#678729";
  var NORMAL_SEGMENT_WEIGHT = 3;
  var HOVER_SEGMENT_COLOR = "#678729";
  var HOVER_SEGMENT_WEIGHT = 6;
  var ACTIVE_TRAIL_COLOR = "#445617";
  var ACTIVE_TRAIL_WEIGHT = 9;
  var NOTRAIL_SEGMENT_COLOR = "#FF0000";
  var NOTRAIL_SEGMENT_WEIGHT = 3;
  var LOCAL_LOCATION_THRESHOLD = 100; // distance in km. less than this, use actual location for map/userLocation 

  var map;
  var trailData = {}; // all of the trails metadata (from traildata table), with trail ID as key
  // { *id*: { geometry: point(0,0), unused for now  
  //                  properties: { id: *uniqueID*,
  //                                length: *length of trail in meters*,
  //                                name: *name of trail*,
  //                                source: *whose data this info came from*,
  //                              }
  //                }
  // }

  var trailheads = []; // all trailheads (from trailsegments)
  // TODO: fix this--it's out of date!
  // [ {  marker: *Leaflet marker*,
  //      trails: *[array of matched trail IDs],
  //      popupContent: *HTML of Leaflet popup*,
  //      properties: { id: *uniqueID*,
  //                    distance: *from current location in meters*,
  //                    name: *name*,
  //                    source: *whose data this info came from*,
  //                    trail1: *trail at this trailhead*,
  //                    trail2: *trail at this trailhead*,
  //                    trail3: *trail at this trailhead*,
  //                    updated_at: *update time*,
  //                    created_at: *creation time*,
  //                    wkt: *original wkt for trailhead point*
  //                  }, 
  //   }[, ...}]
  // ]
  var trailSegments = [];
  var currentMultiTrailLayer = {}; // We have to know if a trail layer is already being displayed, so we can remove it
  var currentTrailLayers = [];
  var currentHighlightedTrailLayer = {};
  var currentUserLocation = {};
  var anchorLocation = {};
  var currentTrailheadLayerGroup;
  var currentFilters = {
    lengthFilter: [],
    activityFilter: [],
    searchFilter: ""
  };
  var orderedTrails = [];
  var currentDetailTrail = null;
  var currentDetailTrailhead = null;
  var userMarker = null;
  var allSegmentLayer = null;
  var closeTimeout = null;
  var openTimeout = null;
  var currentSegmentFeatureGroup = null;
  var currentWeightedSegment = null;
  var currentTrailPopup = null;
  var currentTrailhead = null;
  var orderedTrailIndex;
  var geoWatchId = null;

  // Trailhead Variables
  // Not sure if these should be global, but hey whatev

  var trailheadIconOptions = {
    iconSize: [26 * 0.60, 33 * 0.60],
    iconAnchor: [13 * 0.60, 33 * 0.60],
    popupAnchor: [0, -3]
  };

  var trailheadIcon1Options = $.extend(trailheadIconOptions, {
    iconUrl: 'img/icon_trailhead_active.png'
  });
  var trailheadIcon1 = L.icon(trailheadIcon1Options);
  var trailheadIcon2Options = $.extend(trailheadIconOptions, {
    iconUrl: 'img/icon_trailhead_active.png'
  });
  var trailheadIcon2 = L.icon(trailheadIcon2Options);

  // comment these/uncomment the next set to switch between tables
  var TRAILHEADS_TABLE = "summit_trailheads";
  var TRAILSEGMENTS_TABLE = "summit_trailsegments";
  var TRAILDATA_TABLE = "summit_traildata";

  // var TRAILHEADS_TABLE = "summit_trailheads_test";
  // var TRAILSEGMENTS_TABLE = "summit_trail_segments_test";
  // var TRAILDATA_TABLE = "summit_traildata_test";

  // =====================================================================//
  // UI events to react to

  $("#redoSearch").click(reorderTrailsWithNewLocation);
  $(document).on('click', '.trailhead-trailname', trailnameClick); // Open the detail panel!
  $(document).on('click', '.closeDetail', closeDetailPanel); // Close the detail panel!
  $(document).on('click', '.detailPanelControls', changeDetailPanel); // Shuffle Through Trails Shown in Detail Panel
  $(document).on('change', '.filter', filterChangeHandler);
  $(document).on('mouseover', '.leaflet-popup', function() {
    // console.log("popup mouseover");
    if (currentSegmentFeatureGroup) {
      currentSegmentFeatureGroup.fireEvent('mouseover');
    }
  });
  $(document).on('mouseout', '.leaflet-popup', function() {
    // console.log("popup mouseout");
    if (currentSegmentFeatureGroup) {
      currentSegmentFeatureGroup.fireEvent('mouseout');
    }
  });
  $(document).on('click', '.trail-popup-line-named', trailPopupLineClick);
  $(".search-key").keyup(function(e) {
    // if (e.which == 13) {
    //   console.log($('.search-key').val());
    processSearch(e);
    // }
  });

  $(".search-submit").click(processSearch);

  //  Detail Panel Navigation UI event
  $(".detailPanel").hover(toggleDetailPanelControls, toggleDetailPanelControls);

  //  Shouldn't the UI event of a Map Callout click opening the detail panel go here?



  // =====================================================================//
  // Kick things off

  initialSetup();

  // -----------------------------------

  //  Length Filtering
  //  Helper functions

  function filterTrailList() {
    // defining the function, not calling it
    // click events / activation of checkboxes
    // change currentFilters object
    // apply current Filters to trailData
    // initialSetup performs the mapping
  }


  // The next three functions perform trailhead/trail mapping
  // on a) initial startup, b) requested re-sort of trailheads based on the map, 
  // and c) a change in filter settings
  // They all call addTrailDataToTrailheads() as their final action 
  // --------------------------------------------------------------

  // on startup, get location, display the map,
  // get and display the trailheads, populate trailData, 
  // add trailData to trailheads

  function initialSetup() {
    console.log("initialSetup");
    setupGeolocation(function() {
      getOrderedTrailheads(currentUserLocation, function() {
        getTrailData(function() {
          addTrailDataToTrailheads(trailData);
          if (USE_LOCAL) {
            getTrailSegments(function() {
              // if we haven't added the segment layer yet, add it.
              if (map.getZoom() >= SECONDARY_TRAIL_ZOOM && !(map.hasLayer(allSegmentLayer))) {
                map.addLayer(allSegmentLayer);
              }
            });
          }
        });
      });
    });
  }

  // set currentUserLocation to the center of the currently viewed map
  // then get the ordered trailheads and add trailData to trailheads

  function reorderTrailsWithNewLocation() {
    setAnchorLocationFromMap();
    getOrderedTrailheads(anchorLocation, function() {
      addTrailDataToTrailheads(trailData);
    });
  }

  // =====================================================================//
  //  Filter function + helper functions, triggered by UI events declared above.

  function applyFilterChange(currentFilters, trailData) {
    var filteredTrailData = $.extend(true, {}, trailData);
    $.each(trailData, function(trail_id, trail) {
      if (currentFilters.activityFilter) {
        for (var i = 0; i < currentFilters.activityFilter.length; i++) {
          var activity = currentFilters.activityFilter[i];
          if (trail.properties[activity] && trail.properties[activity].toLowerCase().charAt(0) !== "y") {
            delete filteredTrailData[trail_id];
          }
        }
      }
      if (currentFilters.lengthFilter) {
        var distInclude = false;
        if (currentFilters.lengthFilter.length === 0) {
          distInclude = true;
        }
        for (var j = 0; j < currentFilters.lengthFilter.length; j++) {
          var distance = currentFilters.lengthFilter[j];
          var trailDist = trail.properties["length"];
          if ((distance.toLowerCase() == "short" && trailDist <= SHORT_MAX_DISTANCE) ||
            (distance.toLowerCase() == "medium" && trailDist > SHORT_MAX_DISTANCE && trailDist <= MEDIUM_MAX_DISTANCE) ||
            (distance.toLowerCase() == "long" && trailDist > MEDIUM_MAX_DISTANCE && trailDist <= LONG_MAX_DISTANCE) ||
            (distance.toLowerCase() == "verylong" && trailDist > LONG_MAX_DISTANCE)) {
            distInclude = true;
            break;
          }
        }
        if (!distInclude) {
          delete filteredTrailData[trail_id];
        }
      }
      if (currentFilters.searchFilter) {
        var index = trail.properties.name.toLowerCase().indexOf(currentFilters.searchFilter.toLowerCase());
        if (index == -1) {
          delete filteredTrailData[trail_id];
        }
      }
    });
    addTrailDataToTrailheads(filteredTrailData);
  }

  function filterChangeHandler(e) {
    var $currentTarget = $(e.currentTarget);
    var filterType = $currentTarget.attr("data-filter");
    var currentUIFilterState = $currentTarget.val();
    console.log(currentUIFilterState);
    updateFilterObject(filterType, currentUIFilterState);
  }

  function processSearch(e) {
    var $currentTarget = $(e.currentTarget);
    var filterType = "searchFilter";
    var currentUIFilterState = ($currentTarget.val());
    console.log($currentTarget);
    console.log(currentUIFilterState);
    if (($currentTarget).hasClass('search-key')) {
      console.log("search key");
      updateFilterObject(filterType, currentUIFilterState);
    }
    if (($currentTarget).hasClass('search-submit')) {
      if ($currentTarget.val() !== "") {
        console.log("search submit");
        updateFilterObject(filterType, currentUIFilterState);
      }
    }
    // if the event target has a class search-key
    // see if it is keycode 13
    //  if true, call updatefilterobject
    //  with filtertype=searchFilter
    //  contents/value of searchbox which we get via jquery
    //  if the event target has a class search-button
    //  check to see if the value does not equal empty string
    //  if it does not equal empty string, call updatefilterobject with filtertype=search filter & contents of box.
  }

  function updateFilterObject(filterType, currentUIFilterState) {
    console.log(currentUIFilterState);
    var matched = 0;
    if (filterType == "activityFilter") {
      var filterlength = currentFilters.activityFilter.length;
      for (var i = 0; i < currentFilters.activityFilter.length; i++) {
        var activity = currentFilters.activityFilter[i];
        if (activity === currentUIFilterState) {
          currentFilters.activityFilter.splice(i, 1);
          matched = 1;
          break;
        }
      }
      if (matched === 0) {
        currentFilters.activityFilter.push(currentUIFilterState);
      }
    }

    if (filterType == "lengthFilter") {
      console.log("length");
      console.log(currentFilters.lengthFilter.length);
      var filterlength = currentFilters.lengthFilter.length;
      for (var j = 0; j < filterlength; j++) {
        console.log("j");
        console.log(j);
        var lengthRange = currentFilters.lengthFilter[j];
        if (lengthRange == currentUIFilterState) {
          console.log("match");
          currentFilters.lengthFilter.splice(j, 1);
          matched = 1;
          break;
        }
      }
      if (matched === 0) {
        currentFilters.lengthFilter.push(currentUIFilterState);
      }
    }

    if (filterType == "searchFilter") {
      console.log("searchFilter");
      currentFilters.searchFilter = currentUIFilterState;
    }
    // currentFilters[filterType] = currentUIFilterState;
    console.log(currentFilters);
    applyFilterChange(currentFilters, trailData);
  }

  function setAnchorLocationFromMap() {
    anchorLocation = map.getCenter();
  }

  function setupGeolocation(callback) {
    if (navigator.geolocation) {
      // setup location monitoring
      var options = {
        enableHighAccuracy: true,
        timeout: 10000,
        maximumAge: 30000
      };
      geoWatchId = navigator.geolocation.watchPosition(
        function(position) {
          handleGeoSuccess(position, callback);
        },
        function(position) {
          handleGeoError(position, callback);
        },
        options
      );
    }
    // for now, just returns Akron
    // should use browser geolocation,
    // and only return Akron if we're far from home base
    currentUserLocation = AKRON;
  }

  function handleGeoSuccess(position, callback) {
    currentUserLocation = new L.LatLng(position.coords.latitude, position.coords.longitude);
    var distanceToAkron = currentUserLocation.distanceTo(AKRON) / 1000;
    // if no map, set it up
    if (!map) {
      var startingMapLocation;
      var startingMapZoom;
      // if we're close to Akron, start the map and the trailhead distances from 
      // the current location, otherwise just use AKRON for both
      if (distanceToAkron < LOCAL_LOCATION_THRESHOLD) {
        anchorLocation = currentUserLocation;
        startingMapLocation = currentUserLocation;
        startingMapZoom = 12;
      } else {
        anchorLocation = AKRON;
        startingMapLocation = AKRON;
        startingMapZoom = 11;
      }
      map = createMap(startingMapLocation, startingMapZoom);
    }
    // always update the user marker, create if needed
    if (!userMarker) {
      userMarker = L.userMarker(currentUserLocation, {
        smallIcon: true,
        pulsing: true,
        accuracy: 0
      }).addTo(map);
    }
    console.log(currentUserLocation);
    userMarker.setLatLng(currentUserLocation);
    if (typeof callback == "function") {
      callback();
    }
  }

  function handleGeoError(error, callback) {
    console.log("handleGeoError");
    if (!map) {
      console.log("making map anyway");
      map = createMap(AKRON, 11);
    }
    if (map && userMarker && error.code === 3) {
      map.removeLayer(userMarker);
      userMarker = null;
    }
    if (typeof callback == "function") {
      callback();
    }
  }

  function createMap(startingMapLocation, startingMapZoom) {
    var map = L.map('trailMap', {
      zoomControl: false
    }).addControl(L.control.zoom({
      position: 'topright'
    }));
    L.tileLayer.provider('MapBox.' + MAPBOX_MAP_ID).addTo(map);
    map.setView(startingMapLocation, startingMapZoom);
    map.fitBounds(map.getBounds(), {
      paddingTopLeft: [450, 100]
    });
    map.on("zoomend", function(e) {
      console.log("zoomend");
      if (SHOW_ALL_TRAILS && allSegmentLayer) {
        if (map.getZoom() >= SECONDARY_TRAIL_ZOOM && !(map.hasLayer(allSegmentLayer))) {
          // console.log(allSegmentLayer);
          map.addLayer(allSegmentLayer);
        }
        if (map.getZoom() < SECONDARY_TRAIL_ZOOM && map.hasLayer(allSegmentLayer)) {
          if (currentTrailPopup) {
            map.removeLayer(currentTrailPopup);
          }
          map.removeLayer(allSegmentLayer);
        }
      }
    });
    return map;
  }



  // =====================================================================//
  // Getting trailhead data

  function getOrderedTrailheads(location, callback) {
    console.log("getOrderedTrailheads");
    var callData = {
      loc: location.lat + "," + location.lng,
      type: "GET",
      path: "/trailheads.json?loc=" + location.lat + "," + location.lng
    };
    makeAPICall(callData, function(response) {
      populateTrailheadArray(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }

  // get all trailhead info, in order of distance from "location"

  function getOrderedTrailheadsOld(location, callback) {
    console.log("getOrderedTrailheads");
    var nearest_trailhead_query = "select trailheads.*, " +
      "ST_Distance_Sphere(ST_WKTToSQL('POINT(" + location.lng + " " + location.lat + ")'), the_geom) distance " +
      "from " + TRAILHEADS_TABLE + " as trailheads " +
      "ORDER BY distance " + "";

    makeSQLQuery(nearest_trailhead_query, function(response) {
      populateTrailheadArray(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }


  // given the getOrderedTrailheads response, a geoJSON collection of trailheads ordered by distance,
  // populate trailheads[] with the each trailhead's stored properties, a Leaflet marker, 
  // and a place to put the trails for that trailhead.

  function populateTrailheadArray(trailheadsGeoJSON) {
    console.log("populateTrailheadArray");
    console.log(trailheadsGeoJSON);
    trailheads = [];
    for (var i = 0; i < trailheadsGeoJSON.features.length; i++) {
      var currentFeature = trailheadsGeoJSON.features[i];
      var currentFeatureLatLng = new L.LatLng(currentFeature.geometry.coordinates[1], currentFeature.geometry.coordinates[0]);
      // var newMarker = L.marker(currentFeatureLatLng, ({
      //   icon: trailheadIcon1
      // }));
      var newMarker = new L.CircleMarker(currentFeatureLatLng, {
        color: "#00adef",
        fillOpacity: .5,
        opacity: .8
      }).setRadius(4);
      // adding closure to call trailheadMarkerClick with trailheadID on marker click
      newMarker.on("click", function(trailheadID) {
        return function() {
          trailheadMarkerClick(trailheadID);
        };
      }(currentFeature.properties.id));

      var trailhead = {
        properties: currentFeature.properties,
        marker: newMarker,
        trails: [],
        popupContent: ""
      };
      trailheads.push(trailhead);
    }
  }

  // on trailhead marker click, this is invoked with the id of the trailhead
  // not used for anything but logging at the moment

  function trailheadMarkerClick(id) {
    console.log("trailheadMarkerClick");
    highlightTrailhead(id, 0);
    var trailhead = getTrailheadById(id);
    showTrailDetails(trailData[trailhead.trails[0]], trailhead);
  }

  // get the trailData from the API

  function getTrailData(callback) {
    console.log("getTrailData");
    var callData = {
      type: "GET",
      path: "/trails.json"
    };
    makeAPICall(callData, function(response) {
      populateTrailData(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }
<commit_msg>Added instant mouseover/mouseout menu to trailheads. Sent segments to back.<commit_after>console.log("start");
$(document).ready(startup);

/* The Big Nested Function
==========================*/

// Print to ensure file is loaded

function startup() {
  "use strict";
  console.log("trailhead.js");

  // Map generated in CfA Account
  var MAPBOX_MAP_ID = "codeforamerica.map-j35lxf9d";
  var AKRON = {
    lat: 41.1,
    lng: -81.5
  };

  // API_HOST: The API server. Here we assign a default server, then 
  // test to check whether we're using the Heroky dev app or the Heroku production app
  // and reassign API_HOST if necessary
  var API_HOST = "http://127.0.0.1:3000";
  // var API_HOST = "http://trailsyserver-dev.herokuapp.com";
  if (window.location.hostname.split(".")[0] == "trailsy-dev") {
    API_HOST = "http://trailsyserver-dev.herokuapp.com";
  } else if (window.location.hostname.split(".")[0] == "trailsy") {
    API_HOST = "http://trailsyserver-prod.herokuapp.com";
  }

  //  Near-Global Variables
  var METERSTOMILESFACTOR = 0.00062137;
  var MAX_ZOOM = 17;
  var MIN_ZOOM = 14;
  var SECONDARY_TRAIL_ZOOM = 13;
  var SHORT_MAX_DISTANCE = 2.0;
  var MEDIUM_MAX_DISTANCE = 5.0;
  var LONG_MAX_DISTANCE = 10.0;
  var SHOW_ALL_TRAILS = 1;
  var USE_LOCAL = 1; // Set this to a true value to preload/use a local trail segment cache
  var NORMAL_SEGMENT_COLOR = "#678729";
  var NORMAL_SEGMENT_WEIGHT = 3;
  var HOVER_SEGMENT_COLOR = "#678729";
  var HOVER_SEGMENT_WEIGHT = 6;
  var ACTIVE_TRAIL_COLOR = "#445617";
  var ACTIVE_TRAIL_WEIGHT = 9;
  var NOTRAIL_SEGMENT_COLOR = "#FF0000";
  var NOTRAIL_SEGMENT_WEIGHT = 3;
  var LOCAL_LOCATION_THRESHOLD = 100; // distance in km. less than this, use actual location for map/userLocation 

  var map;
  var trailData = {}; // all of the trails metadata (from traildata table), with trail ID as key
  // { *id*: { geometry: point(0,0), unused for now  
  //                  properties: { id: *uniqueID*,
  //                                length: *length of trail in meters*,
  //                                name: *name of trail*,
  //                                source: *whose data this info came from*,
  //                              }
  //                }
  // }

  var trailheads = []; // all trailheads (from trailsegments)
  // TODO: fix this--it's out of date!
  // [ {  marker: *Leaflet marker*,
  //      trails: *[array of matched trail IDs],
  //      popupContent: *HTML of Leaflet popup*,
  //      properties: { id: *uniqueID*,
  //                    distance: *from current location in meters*,
  //                    name: *name*,
  //                    source: *whose data this info came from*,
  //                    trail1: *trail at this trailhead*,
  //                    trail2: *trail at this trailhead*,
  //                    trail3: *trail at this trailhead*,
  //                    updated_at: *update time*,
  //                    created_at: *creation time*,
  //                    wkt: *original wkt for trailhead point*
  //                  }, 
  //   }[, ...}]
  // ]
  var trailSegments = [];
  var currentMultiTrailLayer = {}; // We have to know if a trail layer is already being displayed, so we can remove it
  var currentTrailLayers = [];
  var currentHighlightedTrailLayer = {};
  var currentUserLocation = {};
  var anchorLocation = {};
  var currentTrailheadLayerGroup;
  var currentFilters = {
    lengthFilter: [],
    activityFilter: [],
    searchFilter: ""
  };
  var orderedTrails = [];
  var currentDetailTrail = null;
  var currentDetailTrailhead = null;
  var userMarker = null;
  var allSegmentLayer = null;
  var closeTimeout = null;
  var openTimeout = null;
  var currentSegmentFeatureGroup = null;
  var currentWeightedSegment = null;
  var currentTrailPopup = null;
  var currentTrailhead = null;
  var orderedTrailIndex;
  var geoWatchId = null;

  // Trailhead Variables
  // Not sure if these should be global, but hey whatev

  var trailheadIconOptions = {
    iconSize: [26 * 0.60, 33 * 0.60],
    iconAnchor: [13 * 0.60, 33 * 0.60],
    popupAnchor: [0, -3]
  };

  var trailheadIcon1Options = $.extend(trailheadIconOptions, {
    iconUrl: 'img/icon_trailhead_active.png'
  });
  var trailheadIcon1 = L.icon(trailheadIcon1Options);
  var trailheadIcon2Options = $.extend(trailheadIconOptions, {
    iconUrl: 'img/icon_trailhead_active.png'
  });
  var trailheadIcon2 = L.icon(trailheadIcon2Options);

  // comment these/uncomment the next set to switch between tables
  var TRAILHEADS_TABLE = "summit_trailheads";
  var TRAILSEGMENTS_TABLE = "summit_trailsegments";
  var TRAILDATA_TABLE = "summit_traildata";

  // var TRAILHEADS_TABLE = "summit_trailheads_test";
  // var TRAILSEGMENTS_TABLE = "summit_trail_segments_test";
  // var TRAILDATA_TABLE = "summit_traildata_test";

  // =====================================================================//
  // UI events to react to

  $("#redoSearch").click(reorderTrailsWithNewLocation);
  $(document).on('click', '.trailhead-trailname', trailnameClick); // Open the detail panel!
  $(document).on('click', '.closeDetail', closeDetailPanel); // Close the detail panel!
  $(document).on('click', '.detailPanelControls', changeDetailPanel); // Shuffle Through Trails Shown in Detail Panel
  $(document).on('change', '.filter', filterChangeHandler);
  $(document).on('mouseover', '.leaflet-popup', function() {
    // console.log("popup mouseover");
    if (currentSegmentFeatureGroup) {
      currentSegmentFeatureGroup.fireEvent('mouseover');
    }
  });
  $(document).on('mouseout', '.leaflet-popup', function() {
    // console.log("popup mouseout");
    if (currentSegmentFeatureGroup) {
      currentSegmentFeatureGroup.fireEvent('mouseout');
    }
  });
  $(document).on('click', '.trail-popup-line-named', trailPopupLineClick);
  $(".search-key").keyup(function(e) {
    // if (e.which == 13) {
    //   console.log($('.search-key').val());
    processSearch(e);
    // }
  });

  $(".search-submit").click(processSearch);

  //  Detail Panel Navigation UI event
  $(".detailPanel").hover(toggleDetailPanelControls, toggleDetailPanelControls);

  //  Shouldn't the UI event of a Map Callout click opening the detail panel go here?



  // =====================================================================//
  // Kick things off

  initialSetup();

  // -----------------------------------

  //  Length Filtering
  //  Helper functions

  function filterTrailList() {
    // defining the function, not calling it
    // click events / activation of checkboxes
    // change currentFilters object
    // apply current Filters to trailData
    // initialSetup performs the mapping
  }


  // The next three functions perform trailhead/trail mapping
  // on a) initial startup, b) requested re-sort of trailheads based on the map, 
  // and c) a change in filter settings
  // They all call addTrailDataToTrailheads() as their final action 
  // --------------------------------------------------------------

  // on startup, get location, display the map,
  // get and display the trailheads, populate trailData, 
  // add trailData to trailheads

  function initialSetup() {
    console.log("initialSetup");
    setupGeolocation(function() {
      getOrderedTrailheads(currentUserLocation, function() {
        getTrailData(function() {
          addTrailDataToTrailheads(trailData);
          if (USE_LOCAL) {
            getTrailSegments(function() {
              // if we haven't added the segment layer yet, add it.
              if (map.getZoom() >= SECONDARY_TRAIL_ZOOM && !(map.hasLayer(allSegmentLayer))) {
                map.addLayer(allSegmentLayer);
              }
            });
          }
        });
      });
    });
  }

  // set currentUserLocation to the center of the currently viewed map
  // then get the ordered trailheads and add trailData to trailheads

  function reorderTrailsWithNewLocation() {
    setAnchorLocationFromMap();
    getOrderedTrailheads(anchorLocation, function() {
      addTrailDataToTrailheads(trailData);
    });
  }

  // =====================================================================//
  //  Filter function + helper functions, triggered by UI events declared above.

  function applyFilterChange(currentFilters, trailData) {
    var filteredTrailData = $.extend(true, {}, trailData);
    $.each(trailData, function(trail_id, trail) {
      if (currentFilters.activityFilter) {
        for (var i = 0; i < currentFilters.activityFilter.length; i++) {
          var activity = currentFilters.activityFilter[i];
          if (trail.properties[activity] && trail.properties[activity].toLowerCase().charAt(0) !== "y") {
            delete filteredTrailData[trail_id];
          }
        }
      }
      if (currentFilters.lengthFilter) {
        var distInclude = false;
        if (currentFilters.lengthFilter.length === 0) {
          distInclude = true;
        }
        for (var j = 0; j < currentFilters.lengthFilter.length; j++) {
          var distance = currentFilters.lengthFilter[j];
          var trailDist = trail.properties["length"];
          if ((distance.toLowerCase() == "short" && trailDist <= SHORT_MAX_DISTANCE) ||
            (distance.toLowerCase() == "medium" && trailDist > SHORT_MAX_DISTANCE && trailDist <= MEDIUM_MAX_DISTANCE) ||
            (distance.toLowerCase() == "long" && trailDist > MEDIUM_MAX_DISTANCE && trailDist <= LONG_MAX_DISTANCE) ||
            (distance.toLowerCase() == "verylong" && trailDist > LONG_MAX_DISTANCE)) {
            distInclude = true;
            break;
          }
        }
        if (!distInclude) {
          delete filteredTrailData[trail_id];
        }
      }
      if (currentFilters.searchFilter) {
        var index = trail.properties.name.toLowerCase().indexOf(currentFilters.searchFilter.toLowerCase());
        if (index == -1) {
          delete filteredTrailData[trail_id];
        }
      }
    });
    addTrailDataToTrailheads(filteredTrailData);
  }

  function filterChangeHandler(e) {
    var $currentTarget = $(e.currentTarget);
    var filterType = $currentTarget.attr("data-filter");
    var currentUIFilterState = $currentTarget.val();
    console.log(currentUIFilterState);
    updateFilterObject(filterType, currentUIFilterState);
  }

  function processSearch(e) {
    var $currentTarget = $(e.currentTarget);
    var filterType = "searchFilter";
    var currentUIFilterState = ($currentTarget.val());
    console.log($currentTarget);
    console.log(currentUIFilterState);
    if (($currentTarget).hasClass('search-key')) {
      console.log("search key");
      updateFilterObject(filterType, currentUIFilterState);
    }
    if (($currentTarget).hasClass('search-submit')) {
      if ($currentTarget.val() !== "") {
        console.log("search submit");
        updateFilterObject(filterType, currentUIFilterState);
      }
    }
    // if the event target has a class search-key
    // see if it is keycode 13
    //  if true, call updatefilterobject
    //  with filtertype=searchFilter
    //  contents/value of searchbox which we get via jquery
    //  if the event target has a class search-button
    //  check to see if the value does not equal empty string
    //  if it does not equal empty string, call updatefilterobject with filtertype=search filter & contents of box.
  }

  function updateFilterObject(filterType, currentUIFilterState) {
    console.log(currentUIFilterState);
    var matched = 0;
    if (filterType == "activityFilter") {
      var filterlength = currentFilters.activityFilter.length;
      for (var i = 0; i < currentFilters.activityFilter.length; i++) {
        var activity = currentFilters.activityFilter[i];
        if (activity === currentUIFilterState) {
          currentFilters.activityFilter.splice(i, 1);
          matched = 1;
          break;
        }
      }
      if (matched === 0) {
        currentFilters.activityFilter.push(currentUIFilterState);
      }
    }

    if (filterType == "lengthFilter") {
      console.log("length");
      console.log(currentFilters.lengthFilter.length);
      var filterlength = currentFilters.lengthFilter.length;
      for (var j = 0; j < filterlength; j++) {
        console.log("j");
        console.log(j);
        var lengthRange = currentFilters.lengthFilter[j];
        if (lengthRange == currentUIFilterState) {
          console.log("match");
          currentFilters.lengthFilter.splice(j, 1);
          matched = 1;
          break;
        }
      }
      if (matched === 0) {
        currentFilters.lengthFilter.push(currentUIFilterState);
      }
    }

    if (filterType == "searchFilter") {
      console.log("searchFilter");
      currentFilters.searchFilter = currentUIFilterState;
    }
    // currentFilters[filterType] = currentUIFilterState;
    console.log(currentFilters);
    applyFilterChange(currentFilters, trailData);
  }

  function setAnchorLocationFromMap() {
    anchorLocation = map.getCenter();
  }

  function setupGeolocation(callback) {
    if (navigator.geolocation) {
      // setup location monitoring
      var options = {
        enableHighAccuracy: true,
        timeout: 10000,
        maximumAge: 30000
      };
      geoWatchId = navigator.geolocation.watchPosition(
        function(position) {
          handleGeoSuccess(position, callback);
        },
        function(position) {
          handleGeoError(position, callback);
        },
        options
      );
    }
    // for now, just returns Akron
    // should use browser geolocation,
    // and only return Akron if we're far from home base
    currentUserLocation = AKRON;
  }

  function handleGeoSuccess(position, callback) {
    currentUserLocation = new L.LatLng(position.coords.latitude, position.coords.longitude);
    var distanceToAkron = currentUserLocation.distanceTo(AKRON) / 1000;
    // if no map, set it up
    if (!map) {
      var startingMapLocation;
      var startingMapZoom;
      // if we're close to Akron, start the map and the trailhead distances from 
      // the current location, otherwise just use AKRON for both
      if (distanceToAkron < LOCAL_LOCATION_THRESHOLD) {
        anchorLocation = currentUserLocation;
        startingMapLocation = currentUserLocation;
        startingMapZoom = 12;
      } else {
        anchorLocation = AKRON;
        startingMapLocation = AKRON;
        startingMapZoom = 11;
      }
      map = createMap(startingMapLocation, startingMapZoom);
    }
    // always update the user marker, create if needed
    if (!userMarker) {
      userMarker = L.userMarker(currentUserLocation, {
        smallIcon: true,
        pulsing: true,
        accuracy: 0
      }).addTo(map);
    }
    console.log(currentUserLocation);
    userMarker.setLatLng(currentUserLocation);
    if (typeof callback == "function") {
      callback();
    }
  }

  function handleGeoError(error, callback) {
    console.log("handleGeoError");
    if (!map) {
      console.log("making map anyway");
      map = createMap(AKRON, 11);
    }
    if (map && userMarker && error.code === 3) {
      map.removeLayer(userMarker);
      userMarker = null;
    }
    if (typeof callback == "function") {
      callback();
    }
  }

  function createMap(startingMapLocation, startingMapZoom) {
    var map = L.map('trailMap', {
      zoomControl: false
    }).addControl(L.control.zoom({
      position: 'topright'
    }));
    L.tileLayer.provider('MapBox.' + MAPBOX_MAP_ID).addTo(map);
    map.setView(startingMapLocation, startingMapZoom);
    map.fitBounds(map.getBounds(), {
      paddingTopLeft: [450, 100]
    });
    map.on("zoomend", function(e) {
      console.log("zoomend");
      if (SHOW_ALL_TRAILS && allSegmentLayer) {
        if (map.getZoom() >= SECONDARY_TRAIL_ZOOM && !(map.hasLayer(allSegmentLayer))) {
          // console.log(allSegmentLayer);
          map.addLayer(allSegmentLayer);
          allSegmentLayer.bringToBack();
        }
        if (map.getZoom() < SECONDARY_TRAIL_ZOOM && map.hasLayer(allSegmentLayer)) {
          if (currentTrailPopup) {
            map.removeLayer(currentTrailPopup);
          }
          map.removeLayer(allSegmentLayer);
        }
      }
    });
    return map;
  }



  // =====================================================================//
  // Getting trailhead data

  function getOrderedTrailheads(location, callback) {
    console.log("getOrderedTrailheads");
    var callData = {
      loc: location.lat + "," + location.lng,
      type: "GET",
      path: "/trailheads.json?loc=" + location.lat + "," + location.lng
    };
    makeAPICall(callData, function(response) {
      populateTrailheadArray(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }

  // get all trailhead info, in order of distance from "location"

  function getOrderedTrailheadsOld(location, callback) {
    console.log("getOrderedTrailheads");
    var nearest_trailhead_query = "select trailheads.*, " +
      "ST_Distance_Sphere(ST_WKTToSQL('POINT(" + location.lng + " " + location.lat + ")'), the_geom) distance " +
      "from " + TRAILHEADS_TABLE + " as trailheads " +
      "ORDER BY distance " + "";

    makeSQLQuery(nearest_trailhead_query, function(response) {
      populateTrailheadArray(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }


  // given the getOrderedTrailheads response, a geoJSON collection of trailheads ordered by distance,
  // populate trailheads[] with the each trailhead's stored properties, a Leaflet marker, 
  // and a place to put the trails for that trailhead.

  function populateTrailheadArray(trailheadsGeoJSON) {
    console.log("populateTrailheadArray");
    console.log(trailheadsGeoJSON);
    trailheads = [];
    for (var i = 0; i < trailheadsGeoJSON.features.length; i++) {
      var currentFeature = trailheadsGeoJSON.features[i];
      var currentFeatureLatLng = new L.LatLng(currentFeature.geometry.coordinates[1], currentFeature.geometry.coordinates[0]);
      // var newMarker = L.marker(currentFeatureLatLng, ({
      //   icon: trailheadIcon1
      // }));
      var newMarker = new L.CircleMarker(currentFeatureLatLng, {
        color: "#00adef",
        fillOpacity: 0.5,
        opacity: 0.8
      }).setRadius(4);
      // adding closure to call trailheadMarkerClick with trailheadID on marker click
      newMarker.on("click", function(trailheadID) {
        return function() {
          trailheadMarkerClick(trailheadID);
        };
      }(currentFeature.properties.id));

      var trailhead = {
        properties: currentFeature.properties,
        marker: newMarker,
        trails: [],
        popupContent: ""
      };
      newMarker.on("mouseover", function(trailhead) {
        return function() {
          console.log(trailhead);
          var popup = new L.Popup({
            offset: [0, -12],
            autoPanPadding: [100, 100]
          }).setContent(trailhead.popupContent)
            .setLatLng(trailhead.marker.getLatLng())
            .openOn(map);
        };
      }(trailhead));
      newMarker.on("mouseout", function(trailhead) {
        return function() {
          map.closePopup();
        };
      }(trailhead));
      trailheads.push(trailhead);
    }
  }

  // on trailhead marker click, this is invoked with the id of the trailhead
  // not used for anything but logging at the moment

  function trailheadMarkerClick(id) {
    console.log("trailheadMarkerClick");
    highlightTrailhead(id, 0);
    var trailhead = getTrailheadById(id);
    showTrailDetails(trailData[trailhead.trails[0]], trailhead);
  }

  // get the trailData from the API

  function getTrailData(callback) {
    console.log("getTrailData");
    var callData = {
      type: "GET",
      path: "/trails.json"
    };
    makeAPICall(callData, function(response) {
      populateTrailData(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }

{'fraction_non_alphanumeric': 0.13696260493513102, 'fraction_numerical': 0.00862375985754261, 'mean_word_length': 2.694295648905178, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1823265', 'n_tokens_mistral': 11963, 'n_tokens_neox': 11493, 'n_words': 2705}