/**
 * RunTrackr - Add Route scripts
 *
 * @requires RouteMap, route-info-calculator.js
 */

/**
 * Custom map control that adds route editing buttons.
 */
function RouteEditControl() {}
RouteEditControl.prototype = new GControl();

/**
 * Sets up the GControl DOM element.
 */
RouteEditControl.prototype.initialize = function(map)
{
  var container = document.createElement("div");

  var controls = jQuery('.route-controls');
  jQuery(container).append(controls);

  map.getContainer().appendChild(container);
  return container;
}

/**
 * Returns the default position of the control on the map.
 */
RouteEditControl.prototype.getDefaultPosition = function()
{
  return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 30));
}

/**
 * Holds the {@link runtrackr.RouteMap} reference.
 *
 * @type runtrackr.RouteMap
 */
var routeMap;

/**
 * Flag to indicate whether to forward user after saving the route.
 *
 * @type Boolean
 */
var forwardToNextPage = false;

/**
 * Callbacks for RouteMap.setLocation().
 */
var setLocationCallbacks =
{
  success: setLocationSuccess,
  failure: setLocationFailure
};

/**
 * Callbacks for RouteMap.saveRoute();
 */
var saveRouteCallbacks =
{
  success: saveRouteSuccess,
  failure: saveRouteFailure
};

// Reduces memory leaks from circular references.
jQuery(document).unload(function()
{
  GUnload();
});

// Run when the DOM is ready.
jQuery(document).ready(function()
{
  // Check if browser supports GMaps.
  if (GBrowserIsCompatible())
  {
    // Initialize the map.
    routeMap = new runtrackr.RouteMap(
    {
      mapId: 'map',
      saveUrl: jQuery('#save-url').val(),
      updateRouteCallback: updateRouteCallback,
      draggableCursor: 'crosshair'
    });

    // Add custom route editing controls to the map.
    routeMap.getMap().addControl(new RouteEditControl());

    // Add a tooltip to the route/location search bar.
    runtrackr.Utility.addTooltip('#location-input');

    // Disable editing until a location is entered on the bar; focus the
    // search field.
    routeMap.disableEditing('Please search for a location before creating a route.');
    runtrackr.Utility.addDisablingOverlay(jQuery('#map-wrapper'));
    jQuery('#location-input').focus();

    // Bind some event handlers to map controls.
    jQuery('button.save').click(saveRouteAndForward);
    jQuery('a.save-continue-editing').click(saveRoute);
    jQuery('button.clear-route').click(clearRoute);
    GEvent.bindDom(
      jQuery('button.remove-last-point').get(0),
      'click',
      routeMap,
      routeMap.removeLastMarker
    );

    // Event handler for jumping to a location.
    jQuery('form.location-jump-to').submit(function(e)
    {
      e.preventDefault();

      // Necessary to blur() the input field if submitted via 'Enter'.
      jQuery('#location-input').blur();

      var locationValue = jQuery('#location-input').val();

      // Lookup routes and pass in callback functions.
      setLocation(locationValue, setLocationCallbacks);
    });

    // Set .notifications to be within the map.
    runtrackr.Utility.movePosition('.notifications', '#route-wrapper');

    // Show Ajax saving indicator and disable route editing while in progress.
    jQuery('.ajax-saving').ajaxSend(function()
    {
      // Show the saving activity indicator.
      jQuery(this).show();

      // Remove any previous notification messages.
      jQuery('.ajax-complete').empty().hide();

      // Disable editing.
      routeMap.disableEditing('Route is currently being saved...');
    });
    // Enable editing after Ajax operation is complete.
    jQuery('.ajax-saving').ajaxComplete(function()
    {
      // Re-enable editing after completion.
      routeMap.enableEditing();
    });

    // Set the starting location, if such information is available.
    var locationFromUriFragment = runtrackr.Utility.getUriFragment();
    var lastLocation = jQuery('#last_location');
    if (locationFromUriFragment !== "")
    {
      // Check the URI fragment for a specified location.
      setLocation(locationFromUriFragment, setLocationCallbacks);
      jQuery('#location-input').val(locationFromUriFragment).blur();
    }
    else if (lastLocation.size() != 0 && lastLocation.val() !== "")
    {
      // Check if the user has a default location.
      setLocation(lastLocation.val(), setLocationCallbacks);
      jQuery('#location-input').val(lastLocation.val()).blur();
    }

    // Hide the suggestion box when search field focused.
    jQuery('#location-input').focus(runtrackr.Utility.hideLocationMatches);
  }
});

/**
 * Callback function to set the map location after confirming the action.
 *
 * @param String locationValue the location to jump to.
 * @param Object callback the {success, failure} callbacks.
 */
function setLocation(locationValue, callback)
{
  // Check if there are current points on the map and if so, notify the
  // user that they will be cleared from the map if moving to a new location.
  if (routeMap.getMarkers().length != 0)
  {
    var confirm = window.confirm(
      "Jumping to a new location will remove the current route from the map. " +
      "Are you sure you want to start a new one?");
    if (!confirm)
      return;
  }

  routeMap.setLocation(locationValue, callback);
}

/**
 * Callback function triggered when map location successfully set.
 *
 * @param Placemark placemark the placemark object returned from the
 *        geocoder call.
 */
function setLocationSuccess(placemark)
{
  // Set hidden form fields with the current location's information.

  // Reset current field values.
  jQuery('#route input.location-field').val('');

  // Administrative area may not exist.
  var administrativeAreaName = runtrackr.Utility.getPropertyValue(placemark, 'AdministrativeAreaName');
  if (administrativeAreaName === false)
  {
    administrativeAreaName = '';
  }

  // TODO: Check if any these are false, and if so fall back to the `AddressLine`
  // property... (This means the geocoder did not include these, or they are in
  // an unexpected format)
  // - See `Whitfield, Mississippi`, `Concordia, New Jersey, USA` for examples.

  // Set form fields.
  jQuery('#CountryCode').val(runtrackr.Utility.getPropertyValue(placemark, 'CountryNameCode'));
  jQuery('#AdministrativeAreaName').val(administrativeAreaName);
  jQuery('#LocalityName').val(runtrackr.Utility.getPropertyValue(placemark, 'LocalityName'));
  jQuery('#LocalityAccuracy').val(runtrackr.Utility.getPropertyValue(placemark, 'Accuracy'));
  jQuery('#LocalityLat').val(placemark.Point.coordinates[1]);
  jQuery('#LocalityLng').val(placemark.Point.coordinates[0]);

  jQuery('#LocalityAddress').val(placemark.address);

  // Enable editing now that a location has been specified.
  routeMap.enableEditing();
  runtrackr.Utility.removeAllDisablingOverlays();
}

/**
 * Callback function triggered when the location supplied could not be
 * resolved with the geocoder.
 *
 * @param Number errorCode the error code returned.
 * @param Object data associated data returned by the caller.
 */
function setLocationFailure(errorCode, data)
{
  var errorMessage = '';

  if (errorCode == runtrackr.Map.ERROR.LOCATION_NOT_FOUND)
    errorMessage =  'Sorry, we could not locate the address. Please try again.';
  else if (errorCode == runtrackr.Map.ERROR.LOCATION_NOT_ACCURATE)
    errorMessage = 'Please be more specific.';
  else if (errorCode == runtrackr.Map.ERROR.LOCATION_MULTIPLE_MATCHES)
  {
    // Show the list of multiple matches/suggestions.
    runtrackr.Utility.showLocationMatches(data, setLocationFromMultipleCallback);

    // Stop further execution.
    return;
  }
  else
  {
    errorMessage = 'Something bad happened.';
  }

  window.alert(errorMessage);

  jQuery('#location-input').focus();
}

/**
 * Sets the location state of the map after user clicks on a location from the
 * suggestion list of multiple matches.
 */
function setLocationFromMultipleCallback(placemark)
{
  setLocationSuccess(placemark);
  routeMap.setLocationPlacemark(placemark);

  // Update the location in the search bar.
  jQuery('#location-input').val(placemark['address']);
}

/**
 * Callback function triggered when the route/map state is changed.
 *
 * This updates route information on screen that is not part of the map.
 */
function updateRouteCallback()
{
  // TODO: Possibly call this by allowing multiple event handlers to be attached
  // to the `updateRouteCallback` event.  This will remove this file's
  // dependence on `route-info-calculator.js` and make the dependence one-way.

  // Call each of the update methods to update information on screen.
  if (updateInfo && typeof(updateInfo) == 'object')
  {
    for (var i in updateInfo)
    {
      updateInfo[i]();
    }
  }
}

/**
 * Event handler for clearing the route.
 *
 * Adds a confirm dialog before carrying out the action.
 */
function clearRoute()
{
  // Confirm the user's action.
  var confirmClear = window.confirm(
    'Are you sure you want to clear the existing route from the map?'
  );
  if (!confirmClear)
    return;

  routeMap.clearRoute();
}

/**
 * Event handler for saving the route.
 */
function saveRoute()
{
  // Get route data from the DOM.
  var data = getRouteDataFromDom();

  // Stay on this page after saving.
  forwardToNextPage = false;

  // Save the route, passing in the additional data and callbacks.
  routeMap.saveRoute(data, saveRouteCallbacks);
}

/**
 * Event handler for saving the route and forwarding after.
 */
function saveRouteAndForward()
{
  var additionalData = getRouteDataFromDom();
  forwardToNextPage = true;
  routeMap.saveRoute(additionalData, saveRouteCallbacks);
}

/**
 * Gets the route data from the DOM.
 *
 * @return Object associative array of the route data in the DOM.
 */
function getRouteDataFromDom()
{
  // TODO: Make this more robust and able to handle different input elements.
  // TODO: Consider using jQuery's built-in functionality.

  // Collect route data stored in the DOM, i.e., not stored in the RouteMap
  // object. Currently, this only handles `text` and `textarea`.
  var data = {};
  var routeData = jQuery('#route .route-data');
  for (var i = 0; i < routeData.length; ++i)
  {
    var curElement = routeData.eq(i);
    var propertyName = curElement.attr('name');

    // Check if it's a checkbox or radio and continue if it is; checkboxes/radio
    // inputs are handled differently/separately.
    if ('checkbox' == curElement.attr('type') ||
      'radio' == curElement.attr('type'))
    {
      continue;
    }

    if (propertyName !== undefined) {
      var propertyValue = curElement.val();
      data[propertyName] = propertyValue;
    }
  }

  // TODO: Abstract this into a method if going to have to deal with data
  // this way.

  // Add checkbox/radio values if they are checked.
  var checkboxes = jQuery('#route input.route-data:checked');
  for (var i = 0; i < checkboxes.length; ++i)
  {
    var curElement = checkboxes.eq(i);
    var propertyName = curElement.attr('name');

    if (propertyName !== undefined)
    {
      var propertyValue = curElement.val();
      data[propertyName] = propertyValue;
    }
  }

  return data;
}

/**
 * Callback function triggered when the route was saved successfully.
 *
 * @param Object responseData the JSON response from the server.
 * @param String the status.
 */
function saveRouteSuccess(responseData, textStatus)
{
  // The Ajax operation failed on the server-side.  Though the Ajax request
  // completed successfully, the server did not save the data because it did
  // not validate.
  if (responseData['status'] == 'failed')
  {
    saveRouteFailure();
  }
  else
  {
    // Store the route id in a hidden field for later reference.
    jQuery('#RouteId').val(responseData['RouteId']);

    // Set notification message on screen.
    jQuery('.ajax-complete').empty().append(
      '<div class="ajax-success">Route Saved!</div>'
    );

    // If the user clicked the 'Done' button then forward them to the view of
    // their route after saving it.
    if (forwardToNextPage)
    {
      var routeId = responseData.slug;//jQuery('#RouteId').val();
      var viewUrl = jQuery('#view-url').val();

      // TODO: Make sure this works in all browsers/situations.
      var nextLocation = window.location.protocol +
        '//' + window.location.host + viewUrl + '/' + routeId + '?status=1';

      // Forward them to the view of the route.
      window.location = nextLocation;
    }

    // TODO: this suffers from 'queue up' problems, I.E., if the save button is
    // hit rapidly in succession faster than the fadeout time.
    // TODO: Is this creating unnecessary closures?
    // Remove the saving activity indicator, fade in the success message and
    // then fade it out.
    jQuery('.ajax-saving').fadeOut('slow', function()
    {
      jQuery('.ajax-complete').fadeIn('slow', function()
      {
        jQuery(this).fadeOut(3000);
      });
    });
  }
}

/**
 * Triggered when the Ajax request to save a route fails.
 */
function saveRouteFailure(XMLHttpRequest, textStatus, errorThrown)
{
  // Display error message in notification.
  jQuery('.ajax-complete').empty().append(
   '<div class="ajax-error">' +
   "Sorry! We couldn't save your route at this time; please try again." +
   '</div>'
  );

  // Remove the saving activity indicator and show/persist the error message.
  jQuery('.ajax-saving').hide();
  jQuery('.ajax-complete').show();
}