<?php

/**
 * Implements hook_hook_info().
 */
function entity_view_mode_hook_info() {
  $hooks = array(
    'entity_view_mode_info',
    'entity_view_mode_info_alter',
    'entity_view_mode_presave',
    'entity_view_mode_insert',
    'entity_view_mode_update',
    'entity_view_mode_delete',
  );

  return array_fill_keys($hooks, array('group' => 'entity'));
}

/**
 * Implements hook_permission().
 */
function entity_view_mode_permission() {
  return array(
    'administer entity view modes' => array(
      'title' => t('Add, edit and delete custom entity view modes.'),
    ),
  );
}

/**
 * Implements hook_entity_info_alter().
 */
function entity_view_mode_entity_info_alter(&$info) {
  static $invoking_hooks = FALSE;

  // Protect against recursion just in case any implementations of
  // hook_entity_view_mode_info() and hook_entity_view_mode_info_alter() from
  // calling entity_get_info().
  if ($invoking_hooks) {
    return;
  }

  // First get all the hook-provided view modes.
  $invoking_hooks = TRUE;
  $view_mode_info = module_invoke_all('entity_view_mode_info');
  drupal_alter('entity_view_mode_info', $view_mode_info);
  $invoking_hooks = FALSE;

  // Add in the variable entity view modes which override hook-provided.
  $view_mode_info = drupal_array_merge_deep($view_mode_info, variable_get('entity_view_modes', array()));

  // Because entity_get_info() merges in the default 'custom settings' value
  // before it invokes hook_entity_info_alter(), we need to make sure that the
  // custom view modes also have this value defined.
  foreach ($view_mode_info as $entity_type => $view_modes) {
    foreach (array_keys($view_modes) as $view_mode) {
      // @todo Should this default to TRUE?
      $view_mode_info[$entity_type][$view_mode] += array('custom settings' => FALSE);
    }
  }

  // Add in the combined custom entity view modes which override the existing
  // view modes in the entity information.
  foreach ($view_mode_info as $entity_type => $view_modes) {
    if (isset($info[$entity_type])) {
      if (!isset($info[$entity_type]['view modes'])) {
        $info[$entity_type]['view modes'] = array();
      }
      $info[$entity_type]['view modes'] = $info[$entity_type]['view modes'] + $view_modes;
    }
  }
}

/**
 * Implements hook_menu().
 */
function entity_view_mode_menu() {
  $items['admin/config/system/entity-view-modes'] = array(
    'title' => 'Entity view modes',
    'description' => 'Create new custom view modes for content.',
    'page callback' => 'entity_view_mode_list',
    'access arguments' => array('administer entity view modes'),
    'file' => 'entity_view_mode.admin.inc',
  );
  $items['admin/config/system/entity-view-modes/add/%'] = array(
    'title' => 'Add view mode',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('entity_view_mode_edit_form', 5),
    'access arguments' => array('administer entity view modes'),
    'file' => 'entity_view_mode.admin.inc',
  );
  $items['admin/config/system/entity-view-modes/edit/%/%'] = array(
    'title' => 'Edit view mode',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('entity_view_mode_edit_form', 5, 6),
    'access arguments' => array('administer entity view modes'),
    'file' => 'entity_view_mode.admin.inc',
  );
  $items['admin/config/system/entity-view-modes/delete/%/%'] = array(
    'title' => 'Edit view mode',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('entity_view_mode_delete_form', 5, 6),
    'access arguments' => array('administer entity view modes'),
    'file' => 'entity_view_mode.admin.inc',
  );

  return $items;
}

/**
 * Implements hook_menu_local_tasks_alter() {
 */
//function entity_view_mode_local_tasks_alter(&$data, $router_item, $root_path) {
//}

/**
 * Implements hook_form_FORM_ID_alter().
 */
//function entity_view_mode_field_ui_display_overview_form_alter(&$form, &$form_state) {
//}

/**
 * Load a custom entity view mode by entity type and machine name.
 */
function entity_view_mode_load($entity_type, $machine_name) {
  $view_modes = variable_get('entity_view_modes', array());

  if (!empty($view_modes[$entity_type][$machine_name])) {
    // Ensure that the machine name is always available.
    return $view_modes[$entity_type][$machine_name] + array('machine_name' => $machine_name);
  }

  return FALSE;
}

/**
 * Save a custom entity view mode.
 */
function entity_view_mode_save($entity_type, $view_mode) {
  $existing_view_mode = !empty($view_mode['old_machine_name']) ? $view_mode['old_machine_name'] : $view_mode['machine_name'];
  $view_mode_name = $view_mode['machine_name'];

  // Load the original, unchanged view mode, if it exists.
  if ($original = entity_view_mode_load($entity_type, $existing_view_mode)) {
    $view_mode['original'] = $original;
    unset($view_mode['old_machine_name']);
  }

  // The key value for custom settings needs to be converted from
  // 'custom_settings' to 'custom settings' before saving.
  if (isset($view_mode['custom_settings'])) {
    $view_mode['custom settings'] = $view_mode['custom_settings'];
    unset($view_mode['custom_settings']);
  }

  // Determine if we will be inserting a new view mode.
  if (!isset($view_mode['is_new'])) {
    $view_mode['is_new'] = empty($view_mode['original']);
  }

  // Let modules modify the view mode before it is saved.
  module_invoke_all('entity_view_mode_presave', $view_mode, $entity_type);

  // Save the view mode.
  $view_modes = variable_get('entity_view_modes', array());
  unset($view_modes[$entity_type][$existing_view_mode]);
  $view_modes[$entity_type][$view_mode_name] = array_intersect_key($view_mode, drupal_map_assoc(array('label', 'custom settings')));
  $view_modes[$entity_type][$view_mode_name] += array('custom settings' => TRUE);
  variable_set('entity_view_modes', $view_modes);

  // Allow modules to respond after the view mode is saved.
  if ($view_mode['is_new']) {
    module_invoke_all('entity_view_mode_insert', $view_mode, $entity_type);
  }
  else {
    // If the view mode's machine name has changed, invoke a separate hook
    // before running the update hook.
    if ($view_mode_name != $existing_view_mode) {
      module_invoke_all('entity_view_mode_rename', $entity_type, $existing_view_mode, $view_mode_name);
    }
    module_invoke_all('entity_view_mode_update', $view_mode, $entity_type);
  }

  // Clear internal properties.
  unset($view_mode['original']);
  unset($view_mode['is_new']);

  // Clear the static entity info cache and rebuild the menu.
  entity_info_cache_clear();
  variable_set('menu_rebuild_needed', TRUE);
}

/**
 * Delete a custom entity view mode.
 */
function entity_view_mode_delete($entity_type, $machine_name) {
  if ($view_mode = entity_view_mode_load($entity_type, $machine_name)) {
    module_invoke_all('entity_view_mode_delete', $view_mode, $entity_type);

    $view_modes = variable_get('entity_view_modes', array());
    unset($view_modes[$entity_type][$machine_name]);
    variable_set('entity_view_modes', $view_modes);

    // Clear the static entity info cache and rebuild the menu.
    entity_info_cache_clear();
    variable_set('menu_rebuild_needed', TRUE);
  }
}

/**
 * Implements hook_entity_view_mode_insert() on behalf of core field module.
 */
function field_entity_view_mode_insert($view_mode, $entity_type) {
  field_entity_view_mode_update($view_mode, $entity_type);
}

/**
 * Implements hook_entity_view_mode_rename() on behalf of core field module.
 */
function field_entity_view_mode_rename($entity_type, $view_mode_old, $view_mode_new) {
  $entity_info = entity_get_info($entity_type);

  foreach (array_keys($entity_info['bundles']) as $bundle) {
    // Update all field bundle settings for the renamed view mode.
    $settings = field_bundle_settings($entity_type, $bundle);
    $changed = FALSE;
    if (isset($settings['view_modes'][$view_mode_old])) {
      $settings['view_modes'][$view_mode_new] = $settings['view_modes'][$view_mode_old];
      unset($settings['view_modes'][$view_mode_old]);
      $changed = TRUE;
    }
    if (isset($settings['extra_fields']['display'][$view_mode_old])) {
      $settings['extra_fields']['display'][$view_mode_new] = $settings['extra_fields']['display'][$view_mode_old];
      unset($settings['extra_fields']['display'][$view_mode_old]);
      $changed = TRUE;
    }
    if ($changed) {
      field_bundle_settings($entity_type, $bundle, $settings);
    }

    // Update all field instance display settings for the renamed view mode.
    $instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle));
    foreach ($instances as $instance) {
      // Remove the view mode settings from all configured field instances.
      if (isset($instance['display'][$view_mode_old])) {
        $instance['display'][$view_mode_new] = $instance['display'][$view_mode_old];
        field_update_instance($instance);
      }
    }
  }
}

/**
 * Implements hook_entity_view_mode_update() on behalf of core field module.
 */
function field_entity_view_mode_update($view_mode, $entity_type) {
  module_load_include('inc', 'field_ui', 'field_ui.admin');
  $entity_info = entity_get_info($entity_type);
  $view_mode_name = $view_mode['machine_name'];

  // If custom settings is set to false, then we should be disabling this view
  // view mode for all bundles.
  //if (empty($view_mode['custom settings'])) {
  //  $view_mode['enabled_bundles'] = array_fill_keys(array_keys($entity_info['bundles']), 0);
  //}

  if (!empty($view_mode['enabled_bundles'])) {
    foreach ($view_mode['enabled_bundles'] as $bundle => $value) {
      $bundle_settings = field_bundle_settings($entity_type, $bundle);

      // Display a message for each view mode newly configured to use custom
      // settings.
      $view_mode_settings = field_view_mode_settings($entity_type, $bundle);
      if (!empty($value) && empty($view_mode_settings[$view_mode_name]['custom_settings'])) {
        $path = _field_ui_bundle_admin_path($entity_type, $bundle) . "/display/$view_mode_name";
        drupal_set_message(t('The %view_mode @entity_type @bundle view mode now uses custom display settings copied from the default view mode. You might want to <a href="@url">configure them</a>.', array(
          '%view_mode' => $view_mode['label'],
          '@entity_type' => $entity_info['label'],
          '@bundle' => $entity_info['bundles'][$bundle]['label'],
          '@url' => url($path, array('query' => drupal_get_destination())),
        )));
        // Initialize the newly customized view mode with the display settings
        // from the default view mode.
        _field_ui_add_default_view_mode_settings($entity_type, $bundle, $view_mode_name, $bundle_settings);
      }

      // Save updated bundle settings.
      $bundle_settings['view_modes'][$view_mode_name]['custom_settings'] = !empty($value);
      field_bundle_settings($entity_type, $bundle, $bundle_settings);
    }
  }
}

/**
 * Implements hook_entity_view_mode_delete() on behalf of core field module.
 */
function field_entity_view_mode_delete($view_mode, $entity_type) {
  $entity_info = entity_get_info($entity_type);
  $view_mode_name = $view_mode['machine_name'];

  foreach (array_keys($entity_info['bundles']) as $bundle) {
    // Remove all field bundle settings for the deleted view mode.
    $settings = field_bundle_settings($entity_type, $bundle);
    if (isset($settings['view_modes'][$view_mode_name]) || isset($settings['extra_fields']['display'][$view_mode_name])) {
      unset($settings['view_modes'][$view_mode_name]);
      unset($settings['extra_fields']['display'][$view_mode_name]);
      field_bundle_settings($entity_type, $bundle, $settings);
    }

    // Remove all field instance display settings for the deleted view mode.
    $instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle));
    foreach ($instances as $instance) {
      // Remove the view mode settings from all configured field instances.
      if (isset($instance['display'][$view_mode_name])) {
        unset($instance['display'][$view_mode_name]);
        field_update_instance($instance);
      }
    }
  }
}

/**
 * Implements hook_entity_view().
 *
 * @see entity_view_mode_preprocess()
 */
function entity_view_mode_entity_view($entity, $entity_type, $view_mode, $langcode) {
  // Add context and variables necessary to entity_view_mode_preprocess().
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  $entity->content['#entity_view_mode'] = array(
    'entity_type' => $entity_type,
    'id' => $id,
    'bundle' => $bundle,
    'view_mode' => $view_mode,
    'langcode' => $langcode,
    'has_bundles' => TRUE,
  );
  if ($entity_type == $bundle) {
    $info = entity_get_info($entity_type);
    if (empty($info['entity keys']['bundle'])) {
      $entity->content['#entity_view_mode']['has_bundles'] = FALSE;
    }
  }
}

/**
 * Implements hook_preprocess().
 *
 * Add template suggestions for an entity based on view modes if they do not
 * already exist.
 *
 * The eventual order of the entity's theme suggestions are:
 * - entity-type__bundle (lowest priority = first in the array)
 * - entity-type__bundle__view-mode
 * - entity-type__id
 * - entity-type__id__view-mode (highest priority = last in the array)
 *
 * @see entity_view_mode_entity_view()
 */
function entity_view_mode_preprocess(&$variables, $hook) {
  // Check for the context provided in entity_view_mode_entity_view().
  if (!empty($variables['elements']['#entity_view_mode'])) {

    // Sometimes a rendered entity is passed into another theme function, in
    // which case we should not process. But also account that #theme may be a
    // hook suggestion itself. For example. #theme = 'comment__node_type'
    // and $hook = 'comment'.
    if ($variables['elements']['#theme'] != $hook && strpos($variables['elements']['#theme'], $hook . '__') !== 0) {
      return;
    }

    extract($variables['elements']['#entity_view_mode']);
    $suggestions = &$variables['theme_hook_suggestions'];

    // Ensure the base suggestions exist and if not, add them.
    if ($has_bundles && !in_array("{$entity_type}__{$bundle}", $suggestions)) {
      // The entity-type__bundle suggestion is typically "first".
      array_unshift($suggestions, "{$entity_type}__{$bundle}");
    }
    if (!in_array("{$entity_type}__{$id}", $suggestions)) {
      // The entity-type__id suggestion is always "last".
      array_push($suggestions, "{$entity_type}__{$id}");
    }

    // Add view mode suggestions based on the location of the base suggestions.
    if ($has_bundles && !in_array("{$entity_type}__{$bundle}__{$view_mode}", $suggestions)) {
      $index = array_search("{$entity_type}__{$bundle}", $suggestions);
      array_splice($suggestions, $index + 1, 0, "{$entity_type}__{$bundle}__{$view_mode}");
    }
    if (!in_array("{$entity_type}__{$id}__{$view_mode}", $suggestions)) {
      $index = array_search("{$entity_type}__{$id}", $suggestions);
      array_splice($suggestions, $index + 1, 0, "{$entity_type}__{$id}__{$view_mode}");
    }
  }
}

function entity_view_mode_get_enabled_bundles($entity_type, $view_mode_name) {
  $bundles = array();

  $entity_info = entity_get_info($entity_type);
  foreach ($entity_info['bundles'] as $bundle => $bundle_info) {
    $view_mode_settings = field_view_mode_settings($entity_type, $bundle);
    if (!empty($view_mode_settings[$view_mode_name]['custom_settings'])) {
      $bundles[$bundle] = $bundle_info['label'];
    }
  }

  return $bundles;
}

function entity_view_mode_get_possible_bundles($entity_type) {
  $bundles = array();

  $entity_info = entity_get_info($entity_type);
  foreach ($entity_info['bundles'] as $bundle => $bundle_info) {
    $bundles[$bundle] = $bundle_info['label'];
  }

  return $bundles;
}
