<?php

/**
 * @file
 * Provides Search API integration for Commerce Kickstart.
 */

/**
 * Implements hook_ctools_plugin_api().
 */
function commerce_search_api_ctools_plugin_api($module, $api) {
  if ($module == "facetapi" && $api == "facetapi_defaults") {
    return array("version" => 1);
  }
}

/**
 * Implements hook_menu().
 */
function commerce_search_api_menu() {
  $items['admin/commerce/config/commerce_search_api'] = array(
    'title' => 'Commerce Search API',
    'description' => 'Configure the generated product display index.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('commerce_search_api_admin_settings_form'),
    'access callback' => 'commerce_search_api_admin_settings_access',
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'includes/commerce_search_api.admin.inc',
  );
  return $items;
}

/**
 * Access calback for the index admin settings form.
 */
function commerce_search_api_admin_settings_access($permission) {
  return search_api_index_load('product_display') && user_access($permission);
}

/**
 * Implements hook_default_search_api_index().
 *
 * Create an automatic product display index.
 */
function commerce_search_api_default_search_api_index() {
  $server = search_api_server_load('frontend');
  if (empty($server)) {
    $servers = search_api_server_load_multiple(FALSE);
    if (empty($servers)) {
      return;
    }
    $server = reset($servers);
  }
  $items['product_display'] = commerce_search_api_generate_product_display_index($server, 'product_display');
  return $items;
}

/**
 * Generate a SearchApiIndex with common defaults configuration.
 *
 * @param SearchApiServer $server
 *   The server the generated index will resides on.
 *
 * @param $index_machine_name
 *   The machine name of the generated index.
 *
 * @return SearchApiIndex
 *   The generated SearchApiIndex.
 */
function commerce_search_api_generate_product_display_index(SearchApiServer $server, $index_machine_name) {
  $product_reference_fields = commerce_info_fields('commerce_product_reference', 'node');
  // We only generate an index if there are product display types.
  if (empty($product_reference_fields) || !isset($server->machine_name)) {
    return entity_create('search_api_index', array());
  }
  $title = 'title';
  // Check if we should index the translated title or not.
  if (module_exists('title') && $title_field = title_field_replacement_info('node', 'title')) {
    $title = $title_field['field']['field_name'];
  }
  // We prepend temp_ to the machine name to prevent Search API from caching
  // the wrong fields when calling the getFields() method.
  $values = array(
    'name' => t('Product display'),
    'machine_name' => 'temp_' . $index_machine_name,
    'server' => $server->machine_name,
    'item_type' => 'node',
    'enabled' => 1,
    'read_only' => 0,
    'options' => array(
      'index_directly' => 1,
      'cron_limit' => 50,
      'fields' => array(
        'nid' => array(
          'type' => 'integer',
        ),
        'search_api_language' => array(
          'type' => 'string',
        ),
        'type' => array(
          'type' => 'string',
        ),
        $title => array(
          'type' => 'string',
        ),
        'created' => array(
          'type' => 'date',
        ),
        'changed' => array(
          'type' => 'date',
        ),
        'status' => array(
          'type' => 'integer',
        ),
        'search_api_aggregation_1' => array(
          'type' => 'text',
        ),
      ),
      'data_alter_callbacks' => array(
        'commerce_search_api_product_display_filter' => array(
          'status' => 1,
          'weight' => -10,
        ),
        'commerce_search_api_alter_product_status' => array(
          'status' => 1,
          'weight' => -9,
        ),
        'search_api_alter_add_aggregation' => array(
          'status' => 1,
          'weight' => 0,
          'settings' => array(
            'fields' => array(
              'search_api_aggregation_1' => array(
                'name' => t('Title (Fulltext)'),
                'type' => 'fulltext',
                'fields' => array(
                  $title,
                ),
                'description' => t('A Fulltext aggregation of the title field.'),
              ),
            ),
          ),
        ),
      ),
    ),
  );
  // If a product reference field is called field_product, take it.
  if (isset($product_reference_fields['field_product'])) {
    $product_reference_field = $product_reference_fields['field_product'];
  }
  // Else, take the first one.
  else {
    $product_reference_field = reset($product_reference_fields);
  }
  $type = $product_reference_field['cardinality'] != 1 ? 'list<integer>' : 'integer';
  $values['options']['fields'] += array(
    $product_reference_field['field_name'] => array(
      'type' => $type,
      'entity_type' => 'commerce_product',
    ),
  );
  // Adding fields in the additional fields array  will alow us to browse them.
  $values['options']['additional fields'][$product_reference_field['field_name']] = $product_reference_field['field_name'];
  $values['options']['additional fields'][$product_reference_field['field_name'] . ':commerce_price'] = $product_reference_field['field_name'] . ':commerce_price';

  // Create a temporary node index that allows us to browse the fields.
  $index = entity_create('search_api_index', $values);
  $aggregation_index = 2;
  foreach ($index->getFields(FALSE) as $field_name => $field) {
    // Index term reference fields.
    if (isset($field['entity_type']) && $field['entity_type'] == 'taxonomy_term') {
      $values['options']['fields'] += array(
        $field_name => array(
          'type' => $field['type'],
          'entity_type' => $field['entity_type'],
        ),
        // We're also indexing the name property of the taxonomy term.
        $field_name . ':name' => array(
          'type' => search_api_nest_type('string', $field['type']),
        ),
      );
      // Check if the fulltext search is enabled for this taxonomy field.
      if (strpos($field_name, ':') === FALSE && variable_get('commerce_search_api_' . $field_name . '_fulltext', 1)) {
        $values['options']['data_alter_callbacks']['search_api_alter_add_aggregation']['settings']['fields'] += array(
          'search_api_aggregation_' . $aggregation_index => array(
            'name' => t('@field name (Fulltext)', array('@field' => $field['name'])),
            'type' => 'fulltext',
            'fields' => array(
              $field_name . ':name',
            ),
            'description' => t('Name property of @field as fulltext', array('@field' => $field['name'])),
          ),
        );
        $values['options']['fields']['search_api_aggregation_' . $aggregation_index] = array(
          'type' => 'text',
        );
      }
      $aggregation_index++;
    }
    else {
      // Index the price decimal property.
      if ($field_name == $product_reference_field['field_name'] . ':commerce_price:amount_decimal') {
        $values['options']['fields'] += array(
          $field_name => array(
            'type' => $field['type'],
          ),
        );
        // Ranges support, we're using the decimal amount (Useful for facets).
        if (search_api_is_list_type($field['type']) && module_exists('search_api_ranges')) {
          $prefix = str_replace(':', '_', $field_name);
          $values['options']['fields'][$prefix . '_asc'] = array(
            'type' => 'decimal',
          );
          $values['options']['fields'][$prefix . '_desc'] = array(
            'type' => 'decimal',
          );
          $values['options']['data_alter_callbacks'] += array(
            'search_api_ranges_alter' => array(
              'status' => 1,
              'weight' => 1,
              'settings' => array(
                'fields' => drupal_map_assoc(array($field_name)),
              ),
            ),
          );
        }
      }
    }
  }
  $index->options = $values['options'];
  $index->machine_name = $index_machine_name;
  return $index;
}

/**
 * Generate and enable facetapi facets with some defaults.
 *
 * @param $index_machine_name
 *   The machine name of the index.
 *
 * @return array
 *   An array of facets exports with some default configurations.
 */
function commerce_search_api_generate_facets($index_machine_name) {
  $export = array();
  $searcher = 'search_api@' . $index_machine_name;
  $index = search_api_index_load($index_machine_name);
  $adapter = facetapi_adapter_load($searcher);
  if ((empty($adapter) || empty($index)) || (!empty($index) && !$index->server()->supportsFeature('search_api_facets'))) {
    return $export;
  }
  $product_attributes = commerce_info_fields('taxonomy_term_reference', 'commerce_product');
  $realm = facetapi_realm_load('block');
  $search_api_ranges_exists = module_exists('search_api_ranges');
  foreach (facetapi_get_facet_info($searcher) as $field_name => $facet_info) {
    $export_facet = FALSE;
    if (isset($facet_info['field type']) && $facet_info['field type'] == 'taxonomy_term') {
      $widget = 'facetapi_checkbox_links';
      $export_facet = TRUE;
    }
    if ($search_api_ranges_exists && strpos($field_name, ':amount_decimal') !== FALSE) {
      $widget = 'search_api_ranges_ui_slider';
      $export_facet = TRUE;
    }
    if ($export_facet) {
      $facet = $adapter->getFacetSettings($facet_info, $realm);
      if ($facet->export_type == EXPORT_IN_DATABASE) {
        continue;
      }
      $facet->settings['widget'] = $widget;
      $facet->settings['filters'] += array(
        'active_items' => array(
          'status' => 1,
          'weight' => -1,
        ),
      );
      // Adding extra filters to product attribute fields.
      if (!empty($product_attributes)) {
        if (strpos($field_name, ':') !== FALSE) {
          list($prefix, $suffix) = explode(':', $field_name, 2);
          if (isset($product_attributes[$suffix])) {
            $facet->settings['filters'] += array(
              'useless_searches' => array(
                'status' => 1,
                'weight' => 0,
              ),
              'hide_search_start' => array(
                'status' => 1,
                'weight' => 1,
              ),
            );
          }
        }
      }
      $facet->api_version = 1;
      $facet->disabled = FALSE;
      $facet->enabled = TRUE;
      $export[$facet->name] = $facet;
    }
  }
  return $export;
}

/**
 * Implements hook_entity_property_info_alter().
 */
function commerce_search_api_entity_property_info_alter(&$info) {
  // Retrieve the product display from the product.
  $properties = &$info['commerce_product']['properties'];
  foreach (field_info_fields() as $field) {
    if ($field['module'] != 'commerce_product_reference') {
      continue;
    }
    $field_name = $field['field_name'];
    foreach ($field['bundles'] as $entity_type => $bundles) {
      $properties[$field_name . '_' . $entity_type] = array(
        'description' => t('A bridge to @entity_type referenced by @field_name', array(
          '@entity_type' => $entity_type,
          '@field_name' => $field_name,
        )),
        'getter callback' => 'commerce_search_api_backreference_getter',
        'field' => $field,
        'label' => t('A bridge to @entity_type referenced by @field_name', array(
          '@entity_type' => $entity_type,
          '@field_name' => $field_name,
        )),
        'target type' => $entity_type,
        'type' => 'list<' . $entity_type . '>',
      );
    }
  }
}

/**
 * Implements hook_search_api_alter_callback_info().
 */
function commerce_search_api_search_api_alter_callback_info() {
  $callbacks['commerce_search_api_product_display_filter'] = array(
    'name' => t('Product Display filter'),
    'description' => t("Exclude nodes that aren't product displays."),
    'class' => 'CommerceSearchApiProductDisplayFilter',
    // Filters should be executed first.
    'weight' => -10,
  );
  $callbacks['commerce_search_api_alter_product_status'] = array(
    'name' => t('Exclude unpublished products'),
    'description' => t("Exclude unpublished products."),
    'class' => 'CommerceSearchApiAlterProductStatus',
    // Filters should be executed first.
    'weight' => -9,
  );
  return $callbacks;
}

/**
 * Getter callback for retrieving related entities.
 */
function commerce_search_api_backreference_getter($entity, array $options, $name, $type, $info) {
  $entities_ids = array();
  // Retrieve the field.
  $field = $info['field'];
  $entity_target_type = $info['target type'];
  $entity_wrapper = entity_metadata_wrapper($type, $entity);
  $query = new EntityFieldQuery();
  $query->entityCondition('entity_type', $entity_target_type, '=')
    ->fieldCondition($field['field_name'], 'product_id', $entity_wrapper->getIdentifier(), '=');
  $result = $query->execute();
  if (isset($result[$entity_target_type])) {
    $entities_ids = array_keys($result[$entity_target_type]);
  }
  return $entities_ids;
}

/**
 * Implements hook_facetapi_filters().
 */
function commerce_search_api_facetapi_filters() {
  $filters = array(
    'useless_searches' => array(
      'handler' => array(
        'label' => t('Hide items that do not change search result.'),
        'class' => 'CommerceSearchApiSameSearch',
      ),
    ),
    'hide_search_start' => array(
      'handler' => array(
        'label' => t('Hide if no search is performed.'),
        'class' => 'CommerceSearchApiSearchStart',
      ),
    ),
  );
  return $filters;
}

/**
 * Implements hook_facetapi_widgets().
 */
function commerce_search_api_facetapi_widgets() {
  return array(
    'commerce_search_api_fancy' => array(
      'handler' => array(
        'label' => t('Fancy attributes'),
        'class' => 'CommerceSearchApiFancy',
        'query types' => array('term'),
      ),
    ),
  );
}

/**
 * Implements hook_theme().
 */
function commerce_search_api_theme() {
  return array(
    'commerce_search_api_fancy_attributes_color' => array(
      'variables' => array(
        'title' => '',
        'hex' => NULL,
      ),
    ),
  );
}

/**
 * Return a <span> tag representing the passed-in color.
 *
 * @param $variables
 *   Variables available for this theming function:
 *     - hex: Hex representation of the color to display.
 *     - title: The facet count.
 * @return string
 */
function theme_commerce_search_api_fancy_attributes_color($variables) {
    // Make sure the value is prefixed by #.
  if (!substr($variables['hex'], 0, 1) == '#') {
    $variables['hex'] = '#' . $variables['hex'];
  }
  $return = '<span style="background-color: ' . $variables['hex'] . ';" class="commerce-search-api-fancy-attributes-color">';
  if (!empty($variables['title'])) {
    $return .= '<span>' . $variables['title'] . '</span>';
  }
  $return .= '</span>';
  return $return;
}
