<?php

/**
 * @file
 * Contains islandora.module.
 *
 * This file is part of the Islandora Project.
 *
 * (c) Islandora Foundation
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @author Diego Pino Navarro <dpino@metro.org> https://github.com/diegopino
 */

use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\islandora\Form\IslandoraSettingsForm;
use Drupal\islandora\GeminiLookup;
use Drupal\node\NodeInterface;
use Drupal\media\MediaInterface;
use Drupal\file\FileInterface;
use Drupal\taxonomy\TermInterface;
use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Implements hook_help().
 */
function islandora_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    // Main module help for the islandora module.
    case 'help.page.islandora':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Islandora integrates Drupal with a Fedora repository.') . '</p>';
      return $output;

    default:
  }
}

/**
 * Implements hook_rdf_namespaces().
 */
function islandora_rdf_namespaces() {
  // Yes, it's amazing, rdf is not registered by default!
  return [
    'ldp'  => 'http://www.w3.org/ns/ldp#',
    'dc11' => 'http://purl.org/dc/elements/1.1/',
    'dcterms' => 'http://purl.org/dc/terms/',
    'nfo' => 'http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/v1.1/',
    'ebucore' => 'http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#',
    'fedora' => 'http://fedora.info/definitions/v4/repository#',
    'owl' => 'http://www.w3.org/2002/07/owl#',
    'ore' => 'http://www.openarchives.org/ore/terms/',
    'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
    'islandora' => 'http://islandora.ca/',
    'pcdm' => 'http://pcdm.org/models#',
    'use' => 'http://pcdm.org/use#',
    'iana' => 'http://www.iana.org/assignments/relation/',
    'premis' => 'http://www.loc.gov/premis/rdf/v1#',
    'premis3' => 'http://www.loc.gov/premis/rdf/v3/',
    'co' => 'http://purl.org/co/',
  ];
}

/**
 * Implements hook_node_insert().
 */
function islandora_node_insert(NodeInterface $node) {
  $utils = \Drupal::service('islandora.utils');

  // Execute index reactions.
  $utils->executeNodeReactions('\Drupal\islandora\Plugin\ContextReaction\IndexReaction', $node);
}

/**
 * Implements hook_node_update().
 */
function islandora_node_update(NodeInterface $node) {

  $utils = \Drupal::service('islandora.utils');
  if (!$utils->haveFieldsChanged($node, $node->original)) {
    return;
  };

  // Execute index reactions.
  $utils->executeNodeReactions('\Drupal\islandora\Plugin\ContextReaction\IndexReaction', $node);
}

/**
 * Implements hook_node_delete().
 */
function islandora_node_delete(NodeInterface $node) {
  $utils = \Drupal::service('islandora.utils');

  // Execute delete reactions.
  $utils->executeNodeReactions('\Drupal\islandora\Plugin\ContextReaction\DeleteReaction', $node);
}

/**
 * Implements hook_media_insert().
 */
function islandora_media_insert(MediaInterface $media) {
  $utils = \Drupal::service('islandora.utils');

  // Execute index reactions.
  $utils->executeMediaReactions('\Drupal\islandora\Plugin\ContextReaction\IndexReaction', $media);

  // If it has a parent node...
  $node = $utils->getParentNode($media);
  if ($node) {
    // Fire off derivative reactions for the Media.
    $utils->executeDerivativeReactions(
      '\Drupal\islandora\Plugin\ContextReaction\DerivativeReaction',
      $node,
      $media
    );
  }
}

/**
 * Implements hook_media_update().
 */
function islandora_media_update(MediaInterface $media) {
  $media_source_service = \Drupal::service('islandora.media_source_service');

  // Exit early if nothing's changed.
  $utils = \Drupal::service('islandora.utils');
  if (!$utils->haveFieldsChanged($media, $media->original)) {
    return;
  };

  // Execute index reactions.
  $utils->executeMediaReactions('\Drupal\islandora\Plugin\ContextReaction\IndexReaction', $media);

  // Does it have a source field?
  $source_field = $media_source_service->getSourceFieldName($media->bundle());
  if (empty($source_field)) {
    return;
  }

  // Exit early if the source file did not change.
  if ($media->get($source_field)->equals($media->original->get($source_field))) {
    return;
  }

  // If it has a parent node...
  $node = $utils->getParentNode($media);
  if ($node) {
    // Fire off derivative reactions for the Media.
    $utils->executeDerivativeReactions(
      '\Drupal\islandora\Plugin\ContextReaction\DerivativeReaction',
      $node,
      $media
    );
  }
}

/**
 * Implements hook_media_delete().
 */
function islandora_media_delete(MediaInterface $media) {
  $utils = \Drupal::service('islandora.utils');

  // Execute delete reactions.
  $utils->executeMediaReactions('\Drupal\islandora\Plugin\ContextReaction\DeleteReaction', $media);
}

/**
 * Implements hook_file_insert().
 */
function islandora_file_insert(FileInterface $file) {
  $utils = \Drupal::service('islandora.utils');

  // Execute index reactions.
  $utils->executeFileReactions('\Drupal\islandora\Plugin\ContextReaction\IndexReaction', $file);
}

/**
 * Implements hook_file_update().
 */
function islandora_file_update(FileInterface $file) {
  // Exit early if unchanged.
  if ($file->filehash['sha1'] == $file->original->filehash['sha1']) {
    return;
  }

  $utils = \Drupal::service('islandora.utils');

  // Execute index reactions.
  $utils->executeFileReactions('\Drupal\islandora\Plugin\ContextReaction\IndexReaction', $file);

  // Execute derivative reactions.
  foreach ($utils->getReferencingMedia($file->id()) as $media) {
    $node = $utils->getParentNode($media);
    if ($node) {
      $utils->executeDerivativeReactions(
        '\Drupal\islandora\Plugin\ContextReaction\DerivativeReaction',
        $node,
        $media
      );
    }
  }
}

/**
 * Implements hook_file_delete().
 */
function islandora_file_delete(FileInterface $file) {
  $utils = \Drupal::service('islandora.utils');

  // Execute delete reactions.
  $utils->executeFileReactions('\Drupal\islandora\Plugin\ContextReaction\DeleteReaction', $file);
}

/**
 * Implements hook_taxonomy_term_insert().
 */
function islandora_taxonomy_term_insert(TermInterface $term) {
  $utils = \Drupal::service('islandora.utils');

  // Execute index reactions.
  $utils->executeTermReactions('\Drupal\islandora\Plugin\ContextReaction\IndexReaction', $term);
}

/**
 * Implements hook_taxonomy_term_update().
 */
function islandora_taxonomy_term_update(TermInterface $term) {
  $utils = \Drupal::service('islandora.utils');

  // Execute index reactions.
  $utils->executeTermReactions('\Drupal\islandora\Plugin\ContextReaction\IndexReaction', $term);
}

/**
 * Implements hook_taxonomy_term_delete().
 */
function islandora_taxonomy_term_delete(TermInterface $term) {
  $utils = \Drupal::service('islandora.utils');

  // Execute delete reactions.
  $utils->executeTermReactions('\Drupal\islandora\Plugin\ContextReaction\DeleteReaction', $term);
}

/**
 * Implements hook_jsonld_alter_normalized_array().
 */
function islandora_jsonld_alter_normalized_array(EntityInterface $entity, array &$normalized, array $context) {
  $context_manager = \Drupal::service('context.manager');
  foreach ($context_manager->getActiveReactions('\Drupal\islandora\ContextReaction\NormalizerAlterReaction') as $reaction) {
    $reaction->execute($entity, $normalized, $context);
    foreach ($context_manager->getActiveContexts() as $context_config) {
      try {
        if ($context_config->getReaction($reaction->getPluginId())) {
          $context['cacheability']->addCacheTags($context_config->getCacheTags());
        };
      }
      catch (PluginNotFoundException $e) {
        // Squash :(.
      }
    }
  }
}

/**
 * Implements hook_entity_view_mode_alter().
 */
function islandora_entity_view_mode_alter(&$view_mode, EntityInterface $entity) {
  // Change the view mode based on user input from a 'view_mode_alter'
  // ContextReaction.
  $storage = \Drupal::service('entity_type.manager')->getStorage('entity_view_mode');
  $context_manager = \Drupal::service('context.manager');
  $current_entity = \Drupal::routeMatch()->getParameter('node');
  $current_id = ($current_entity instanceof NodeInterface) ? $current_entity->id() : NULL;
  if (isset($current_id) && $current_id == $entity->id()) {
    foreach ($context_manager->getActiveReactions('\Drupal\islandora\Plugin\ContextReaction\ViewModeAlterReaction') as $reaction) {
      // Construct the new view mode's machine name.
      $entity_type = $entity->getEntityTypeId();
      $mode = $reaction->execute();
      $machine_name = "$entity_type.$mode";

      // Try to load it.
      $new_mode = $storage->load($machine_name);

      // If successful, alter the view mode.
      if ($new_mode) {
        $view_mode = $mode;
      }
      else {
        // Otherwise, leave it be, but log a message.
        \Drupal::logger('islandora')
          ->info("EntityViewMode $machine_name does not exist.  View mode cannot be altered.");
      }
    }
  }
}

/**
 * Implements hook_preprocess_node().
 */
function islandora_preprocess_node(&$variables) {
  // Using alternate view modes causes on a node's canoncial page
  // causes the title to get printed out twice.  Once from the
  // fields themselves and again as a block above the main content.
  // Setting 'page' to TRUE gets rid of the title in the fields and
  // leaves the block.  This makes it look uniform with the 'default'
  // view mode.
  if (node_is_page($variables['elements']['#node'])) {
    $variables['page'] = TRUE;
  }
}

/**
 * Implements hook_entity_form_display_alter().
 */
function islandora_entity_form_display_alter(&$form_display, $context) {
  // Change the form display based on user input from a 'form_display_alter'
  // ContextReaction.
  $storage = \Drupal::service('entity_type.manager')->getStorage('entity_form_display');
  $context_manager = \Drupal::service('context.manager');

  // Alter form display based on context.
  foreach ($context_manager->getActiveReactions('\Drupal\islandora\Plugin\ContextReaction\FormDisplayAlterReaction') as $reaction) {
    // Construct the new form display's machine name.
    $entity_type = $context['entity_type'];
    $bundle = $context['bundle'];
    $mode = $reaction->execute();
    $machine_name = "$entity_type.$bundle.$mode";

    // Try to load it.
    $new_display = $storage->load($machine_name);

    // If successful, alter the form display.
    if ($new_display) {
      $form_display = $new_display;
    }
    else {
      // Otherwise, leave it be, but log a message.
      \Drupal::logger('islandora')->info("EntityFormDisplay $machine_name does not exist.  Form display cannot be altered.");
    }
  }
}

/**
 * Implements hook_form_form_id_alter().
 */
function islandora_form_block_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Unset our custom conditions.  There's too many to use well within
  // the core block placement UI, and no other reasonable way to filter
  // them out.  See https://www.drupal.org/node/2284687.  Use
  // /admin/structure/context instead if you want to use these conditions
  // to alter block layout.
  unset($form['visibility']['content_entity_type']);
  unset($form['visibility']['parent_node_has_term']);
  unset($form['visibility']['node_had_namespace']);
  unset($form['visibility']['media_has_term']);
  unset($form['visibility']['file_uses_filesystem']);
  unset($form['visibility']['node_has_term']);
  unset($form['visibility']['node_has_parent']);
  unset($form['visibility']['media_uses_filesystem']);
  unset($form['visibility']['media_has_mimetype']);
  unset($form['visibility']['node_is_islandora_object']);
  unset($form['visibility']['media_is_islandora_media']);
}

/**
 * Implements hook_entity_extra_field_info().
 */
function islandora_entity_extra_field_info() {
  $config_factory = \Drupal::service('config.factory')->get(IslandoraSettingsForm::CONFIG_NAME);
  $extra_field = [];

  $pseudo_bundles = $config_factory->get(IslandoraSettingsForm::GEMINI_PSEUDO);

  if (!empty($pseudo_bundles)) {
    foreach ($pseudo_bundles as $key) {
      list($bundle, $content_entity) = explode(":", $key);
      $extra_field[$content_entity][$bundle]['display']['field_gemini_uri'] = [
        'label' => t('Fedora URI'),
        'description' => t('The URI to the persistent'),
        'weight' => 100,
        'visible' => TRUE,
      ];
    }
  }
  return $extra_field;
}

/**
 * Implements hook_entity_view().
 */
function islandora_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
  $route_match_item = \Drupal::routeMatch()->getParameters()->get($entity->getEntityTypeId());
  // Ensure the entity matches the route.
  if ($entity === $route_match_item) {
    if ($display->getComponent('field_gemini_uri')) {
      $gemini = \Drupal::service('islandora.gemini.lookup');
      if ($gemini instanceof GeminiLookup) {
        $fedora_uri = $gemini->lookup($entity);
        if (!is_null($fedora_uri)) {
          $build['field_gemini_uri'] = [
            '#type' => 'container',
            '#attributes' => [
              'id' => 'field-gemini-uri',
            ],
            'internal_label' => [
              '#type' => 'item',
              '#title' => t('Fedora URI'),
              'internal_uri' => [
                '#type' => 'link',
                '#title' => t("@url", ['@url' => $fedora_uri]),
                '#url' => Url::fromUri($fedora_uri),
              ],
            ],
          ];
        }
      }
    }
  }
}

/**
 * Implements hook_preprocess_views_view_table().
 *
 * Used for the integer-weight drag-n-drop. Taken almost
 * verbatim from the weight module.
 */
function islandora_preprocess_views_view_table(&$variables) {

  // Check for a weight selector field.
  foreach ($variables['view']->field as $field_key => $field) {
    if ($field->options['plugin_id'] == 'integer_weight_selector') {

      // Check if the weight selector is on the first column.
      $is_first_column = array_search($field_key, array_keys($variables['view']->field)) > 0 ? FALSE : TRUE;

      // Add the tabledrag attributes.
      foreach ($variables['rows'] as $key => $row) {
        if ($is_first_column) {
          // If the weight selector is the first column move it to the last
          // column, in order to make the draggable widget appear.
          $weight_selector = $variables['rows'][$key]['columns'][$field->field];
          unset($variables['rows'][$key]['columns'][$field->field]);
          $variables['rows'][$key]['columns'][$field->field] = $weight_selector;
        }
        // Add draggable attribute.
        $variables['rows'][$key]['attributes']->addClass('draggable');
      }
      // The row key identify in an unique way a view grouped by a field.
      // Without row number, all the groups will share the same table_id
      // and just the first table can be draggable.
      $table_id = 'weight-table-' . $variables['view']->dom_id . '-row-' . $key;
      $variables['attributes']['id'] = $table_id;

      $options = [
        'table_id' => $table_id,
        'action' => 'order',
        'relationship' => 'sibling',
        'group' => 'weight-selector',
      ];
      drupal_attach_tabledrag($variables, $options);
    }
  }
}