<?php

/**
 * @file
 * Field widget related code for Organic groups.
 */


/**
 * Implements hook_field_widget_info().
 */
function og_field_widget_info() {
  $widgets['og_complex'] = array(
    'label' => t('OG reference'),
    'description' => t('Complex widget to reference groups.'),
    'field types' => array('entityreference'),
  );

  return $widgets;
}


/**
 * Implements hook_field_widget_form().
 */
function og_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $entity_type = $instance['entity_type'];
  $entity = isset($element['#entity']) ? $element['#entity'] : NULL;

  if (!$entity) {
    return;
  }

  if ($field['settings']['handler'] != 'og') {
    $params = array('%label' => $instance['label']);
    form_error($form, t('Field %label is a group-audience but its Entity selection mode is not defined as "Organic groups" in the field settings page.', $params));
    return;
  }

  // Cache the processed entity, to make sure we call the widget only once.
  $cache = &drupal_static(__FUNCTION__, array());
  list($id,, $bundle) = entity_extract_ids($entity_type, $entity);
  $field_name = $field['field_name'];

  $identifier = $field_name . ':' . $entity_type . ':' . $id;
  if (isset($cache[$identifier])) {
    return;
  }
  $cache[$identifier] = TRUE;

  ctools_include('fields');

  $field_modes = array('default');
  $has_admin = FALSE;

  // The group IDs that might not be accessible by the user, but we need
  // to keep even after saving.
  $element['#other_groups_ids'] = array();
  $element['#element_validate'][] = 'og_complex_widget_element_validate';

  if (user_access('administer group')) {
    $has_admin = TRUE;
    $field_modes[] = 'admin';
  }

  // Get the "Other group" group IDs.
  $target_type = $field['settings']['target_type'];
  $entity_gids = og_get_entity_groups($entity_type, $entity);
  $entity_gids = !empty($entity_gids[$target_type]) ? $entity_gids[$target_type] : array();

  $user_gids = og_get_entity_groups();
  $user_gids = !empty($user_gids[$target_type]) ? $user_gids[$target_type] : array();

  $other_groups_ids = array();
  $full_diff = array_diff(array_merge($entity_gids, $user_gids), array_intersect($entity_gids, $user_gids));
  $other_groups_ids = array_values($full_diff);

  foreach ($field_modes as $field_mode) {
    $mocked_instance = og_get_mocked_instance($instance, $field_mode);
    $dummy_entity = clone $entity;

    if ($has_admin) {
      $mocked_instance['required'] = FALSE;
      $mocked_instance['label'] = $field_mode == 'default' ? t('Default') : t('Administrator');

      if ($id) {
        // The field might be required, and it will throw an exception
        // when we try to set an empty value, so change the wrapper's
        // info.
        $wrapper = entity_metadata_wrapper($entity_type, $dummy_entity, array('property info alter' => 'og_property_info_alter', 'field name' => $field_name));
        if ($field_mode == 'admin') {
          // Keep only the hidden group IDs on the entity, so they won't
          // appear again on the "admin" field, for example on an autocomplete
          // widget type.
          $valid_ids = $other_groups_ids ? entityreference_get_selection_handler($field, $mocked_instance, $entity_type, $dummy_entity)->validateReferencableEntities($other_groups_ids) : array();
          $valid_ids = $field['cardinality'] == 1 ? reset($valid_ids) : $valid_ids;
          $wrapper->{$field_name}->set($valid_ids ? $valid_ids : NULL);
        }
        else {
          // Keep only the groups that belong to the user and to the entity.
          $my_group_ids = array_values(array_intersect($user_gids, $entity_gids));
          $valid_ids = $my_group_ids ? entityreference_get_selection_handler($field, $mocked_instance, $entity_type, $dummy_entity)->validateReferencableEntities($my_group_ids) : array();

          $valid_ids = $field['cardinality'] == 1 ? reset($valid_ids) : $valid_ids;
          $wrapper->{$field_name}->set($valid_ids ? $valid_ids : NULL);
        }
      }
    }
    else {
      // Non-admin user.
      $mocked_instance_other_groups = $mocked_instance;
      $mocked_instance_other_groups['field_mode'] = 'admin';
      if ($other_groups_ids && $valid_ids = entityreference_get_selection_handler($field, $mocked_instance_other_groups, $entity_type, $dummy_entity)->validateReferencableEntities($other_groups_ids)) {
        foreach ($valid_ids as $id) {
          $element['#other_groups_ids'][] = array('target_id' => $id);
        }
      }
    }

    $dummy_form_state = $form_state;
    if (empty($form_state['rebuild'])) {
      // Form is "fresh" (i.e. not call from field_add_more_submit()), so
      // re-set the items-count, to show the correct amount for the mocked
      // instance.
      $dummy_form_state['field'][$field_name][LANGUAGE_NONE]['items_count'] = !empty($dummy_entity->{$field_name}[LANGUAGE_NONE]) ? count($dummy_entity->{$field_name}[LANGUAGE_NONE]) : 0;
    }

    $new_element = ctools_field_invoke_field($mocked_instance, 'form', $entity_type, $dummy_entity, $form, $dummy_form_state, array('default' => TRUE));
    $element[$field_mode] = $new_element[$field_name][LANGUAGE_NONE];

    if (in_array($mocked_instance['widget']['type'], array('entityreference_autocomplete', 'entityreference_autocomplete_tags'))) {
      // Change the "Add more" button name so it adds only the needed
      // element.
      if (!empty($element[$field_mode]['add_more']['#name'])) {
        $element[$field_mode]['add_more']['#name'] .= '__' . $field_mode;
      }

      foreach (array_keys($element[$field_mode]) as $delta) {
        if (!is_numeric($delta)) {
          continue;
        }

        $sub_element = &$element[$field_mode][$delta]['target_id'];

        // Rebuild the autocomplete path.
        $path = explode('/', $sub_element['#autocomplete_path']);
        $sub_element['#autocomplete_path'] = 'og/autocomplete';

        // Add autocomplete type
        $sub_element['#autocomplete_path'] .= "/$path[2]/$path[3]/$path[4]/$path[5]";

        // Add field mode.
        $sub_element['#autocomplete_path'] .= "/$field_mode";

        // Add the entity ID.
        $sub_element['#autocomplete_path'] .= "/$path[6]";
        if (!empty($path[7])) {
          // Add the text.
          $sub_element['#autocomplete_path'] .= "/$path[7]";
        }
      }
    }
  }

  $form['#after_build']['og'] = 'og_complex_widget_after_build';

  return $element;
}

/**
 * Property info alter; Change mocked field to be non-required.
 */
function og_property_info_alter($wrapper, $info) {
  $property_info = $wrapper->info();
  $field_name = $property_info['field name'];
  $info['properties'][$field_name]['required'] = FALSE;
  return $info;
}

/**
 * Helper function; Get the mocked instance.
 */
function og_get_mocked_instance($instance, $field_mode) {
  $mocked_instance = $instance;
  $widget_type = $instance['settings']['behaviors']['og_widget'][$field_mode]['widget_type'];
  $mocked_instance['widget']['type'] = $widget_type;

  // Set the widget's module.
  $widget_info = field_info_widget_types($widget_type);
  $mocked_instance['widget']['module'] = $widget_info['module'];
  $mocked_instance['widget']['settings'] = drupal_array_merge_deep($mocked_instance['widget']['settings'], $widget_info['settings']);

  // See OgSelectionHandler::buildEntityFieldQuery().
  $mocked_instance['field_mode'] = $field_mode;
  return $mocked_instance;
}

/**
 * Rebuild the element's values, using the default and admin if exists.
 */
function og_complex_widget_element_validate($element, &$form_state, $form) {
  $subform = drupal_array_get_nested_value($form_state['values'], $element['#array_parents']);
  $ids = $element['#other_groups_ids'];
  foreach (array('default', 'admin') as $type) {
    if (empty($subform[$type])) {
      continue;
    }

    foreach ($subform[$type] as $delta => $value) {
      if (!empty($value['target_id']) && is_numeric($value['target_id'])) {
        $ids[] = array('target_id' => $value['target_id']);
      }
    }
  }

  // Set the form values by directly using drupal_array_set_nested_values(),
  // which allows us to control the element parents. In this case we cut off the
  // last element that contains the delta 0, as $ids is already keyed with
  // deltas.
  drupal_array_set_nested_value($form_state['values'], array_slice($element['#parents'], 0, -1), $ids, TRUE);
}

/**
 * After build; Remove the "Add more" button.
 *
 * @see field_multiple_value_form()
 * @see theme_field_multiple_value_form()
 */
function og_complex_widget_after_build($form, &$form_state) {
  foreach (og_get_group_audience_fields($form['#entity_type'], $form['#bundle']) as $field_name => $value) {
    if (empty($form[$field_name])) {
      continue;
    }
    unset($form[$field_name][LANGUAGE_NONE]['#theme']);
    unset($form[$field_name][LANGUAGE_NONE]['add_more']);
    unset($form[$field_name][LANGUAGE_NONE][0]['_weight']);

    if (!empty($form[$field_name][LANGUAGE_NONE][0]['admin'])) {
      // Wrap both elements with a fieldset.
      $form[$field_name][LANGUAGE_NONE]['#theme_wrappers'] = array('fieldset');
    }
  }

  return $form;
}

/**
 * Menu callback: autocomplete the label of an entity.
 *
 * @param $type
 *   The widget type (i.e. 'single' or 'tags').
 * @param $field_name
 *   The name of the entity-reference field.
 * @param $entity_type
 *   The entity type.
 * @param $bundle_name
 *   The bundle name.
 * @param $field_mode
 *   The field mode, "default" or "admin".
 * @param $entity_id
 *   Optional; The entity ID the entity-reference field is attached to.
 *   Defaults to ''.
 * @param $string
 *   The label of the entity to query by.
 *
 * @see entityreference_autocomplete_callback()
 */
function og_entityreference_autocomplete_callback($type, $field_name, $entity_type, $bundle_name, $field_mode, $entity_id = '', $string = '') {
  $field = field_info_field($field_name);
  $instance = field_info_instance($entity_type, $field_name, $bundle_name);
  $instance = og_get_mocked_instance($instance, $field_mode);
  $matches = array();

  if (!$field || !$instance || $field['type'] != 'entityreference' || !field_access('edit', $field, $entity_type)) {
    return MENU_ACCESS_DENIED;
  }

  $entity = NULL;
  if ($entity_id !== 'NULL') {
    $entity = entity_load_single($entity_type, $entity_id);
    if (!$entity || !entity_access('view', $entity_type, $entity)) {
      return MENU_ACCESS_DENIED;
    }
  }
  $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity);

  if ($type == 'tags') {
    // The user enters a comma-separated list of tags. We only autocomplete the last tag.
    $tags_typed = drupal_explode_tags($string);
    $tag_last = drupal_strtolower(array_pop($tags_typed));
    if (!empty($tag_last)) {
      $prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : '';
    }
  }
  else {
    // The user enters a single tag.
    $prefix = '';
    $tag_last = $string;
  }

  if (!empty($tag_last)) {
    // Get an array of matching entities.
    $entity_labels = $handler->getReferencableEntities($tag_last, $instance['widget']['settings']['match_operator'], 10);

    // Loop through the products and convert them into autocomplete output.
    foreach ($entity_labels as $entity_id => $label) {
      $key = "$label ($entity_id)";
      // Strip things like starting/trailing white spaces, line breaks and tags.
      $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($key)))));
      // Names containing commas or quotes must be wrapped in quotes.
      if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
        $key = '"' . str_replace('"', '""', $key) . '"';
      }
      $matches[$prefix . $key] = '<div class="reference-autocomplete">' . $label . '</div>';
    }
  }

  drupal_json_output($matches);
}

