<?php

/**
 * @file
 * Provides location based search functionality for the Search API.
 */

/**
 * Implements hook_search_api_data_type_info().
 */
function search_api_location_search_api_data_type_info() {
  return array(
    'location' => array(
      'name' => t('Latitude/longitude'),
      'fallback' => 'string',
    ),
  );
}

/**
 * Returns the location fields of an index (if it has any).
 *
 * @param SearchApiIndex $index
 *   The index for which the location fields should be found.
 * @param $reset
 *   Whether to reset the static cache for the function. Defaults to FALSE.
 *
 * @return array
 *   An array of fields with type "location", keyed by field name and containing
 *   the same options as returned by
 */
function search_api_location_get_location_fields(SearchApiIndex $index, $reset = FALSE) {
  $fields = &drupal_static(__FUNCTION__, array());
  if ($reset || empty($fields[$index->machine_name])) {
    $fields[$index->machine_name] = array();
    if (($server = $index->server()) && $server->supportsFeature('search_api_data_type_location')) {
      foreach ($index->getFields() as $key => $field) {
        if (!empty($field['real_type']) && search_api_extract_inner_type($field['real_type']) == 'location') {
          $fields[$index->machine_name][$key] = $field;
        }
      }
    }
  }
  return $fields[$index->machine_name];
}

/**
 * Returns an array of options for used distance units.
 */
function search_api_location_get_units() {
  $units = &drupal_static(__FUNCTION__);

  if (!isset($units)) {
    $units = array(
      '1' => t('Kilometers'),
      '1.60935' => t('Miles'),
    );
    drupal_alter('search_api_location_units', $units);
  }

  return $units;
}

/**
 * Helper function for validating a distance input.
 */
function search_api_location_validate_distance(array $element, array &$form_state) {
  $value = $element['#value'];
  if ($value != '' && (!is_numeric($value)) || ((float) $value) < 0) {
    form_error($element, t('%name must be a non-negative number.', array('%name' => $element['#title'])));
  }
}

/**
 * Implements hook_ctools_plugin_api().
 */
function search_api_location_ctools_plugin_api() {
  return array('version' => 1);
}

/**
 * Implements hook_ctools_plugin_dierctory().
 */
function search_api_location_ctools_plugin_directory($module, $plugin) {
  if ($module == 'search_api_location') {
    return 'plugins/' . $plugin;
  }
}

/**
 * Implements hook_ctools_plugin_type().
 */
function search_api_location_ctools_plugin_type() {
  return array(
    'location_input' => array(
      'use hooks' => TRUE,
    ),
  );
}

/**
 * Returns a list of location input plugins.
 *
 * @param $plugin
 *   (optional) The identifier of a specific plugin to return.
 *
 * @return array
 *   If $plugin is given, either the specified plugin or NULL. Otherwise a list
 *   of all available plugins, keyed by identifier.
 */
function search_api_location_get_input_plugins($plugin = NULL) {
  $plugins = &drupal_static(__FUNCTION__);

  if ($plugin) {
    if (isset($plugins)) {
      return isset($plugins[$plugin]) ? $plugins[$plugin] : NULL;
    }
    ctools_include('plugins');
    $plugin = ctools_get_plugins('search_api_location', 'location_input', $plugin);
    if (empty($plugin['title']) || empty($plugin['input callback']) || !function_exists($plugin['input callback'])) {
      // Don't return incomplete plugins.
      return NULL;
    }
    if (!empty($plugin['form callback']) && !function_exists($plugin['form callback'])) {
      // Unset form callback if it doesn't exist.
      unset($plugin['form callback']);
    }
    return $plugin;
  }

  if (!isset($plugins)) {
    ctools_include('plugins');
    $plugins = array();
    foreach (ctools_get_plugins('search_api_location', 'location_input') as $id => $plugin) {
      $plugin += array(
        'description' => NULL,
      );
      if (empty($plugin['title']) || empty($plugin['input callback']) || !function_exists($plugin['input callback'])) {
        // Filter out incomplete plugins.
        continue;
      }
      if (!empty($plugin['form callback']) && !function_exists($plugin['form callback'])) {
        // Unset form callback if it doesn't exist.
        unset($plugin['form callback']);
      }
      $plugins[$id] = $plugin;
    }
  }

  return $plugins;
}

/**
 * Returns all available location input plugins as an option list.
 *
 * @return array
 *   All available location_input plugins, with their identifiers mapped to
 *   their human-readable names.
 */
function search_api_location_get_input_plugin_options() {
  $options = &drupal_static(__FUNCTION__);

  if (!isset($options)) {
    $options = array();
    foreach (search_api_location_get_input_plugins() as $id => $plugin) {
      $options[$id] = check_plain($plugin['title']);
    }
  }

  return $options;
}

/**
 * Helper function for finding a key anywhere in an array.
 *
 * @param array $array
 *   The array to search through.
 * @param $key
 *   The key to find.
 *
 * @return
 *   The value mapped to the array key, or NULL if the key couldn't be found.
 */
function _search_api_location_array_get_nested_value(array $array, $key) {
  foreach (element_children($array) as $k) {
    $v = $array[$k];
    if ($key == $k) {
      return $v;
    }
    if (is_array($v)) {
      $nested[] = $v;
    }
  }
  if (!empty($nested)) {
    foreach ($nested as $array) {
      $value = _search_api_location_array_get_nested_value($array, $key);
      if ($value) {
        return $value;
      }
    }
  }
  return NULL;
}
