<?php

/**
 * @file
 */

use Drupal\Core\Entity\ContentEntityInterface;

/**
 * @file
 * Provides a bulk room reservation entity type.
 */

use Drupal\Core\Render\Element;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;

/**
 * Implements hook_theme().
 */
function intercept_bulk_room_reservation_theme() {
  return [
    'bulk_room_reservation' => [
      'render element' => 'elements',
    ],
  ];
}

/**
 * Prepares variables for bulk room reservation templates.
 *
 * Default template: bulk-room-reservation.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the bulk room reservation information and any
 *     fields attached to the entity.
 *   - attributes: HTML attributes for the containing element.
 */
function intercept_bulk_room_reservation_preprocess_bulk_room_reservation(array &$variables) {
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 * Checks to see if a room is available for a date/time range.
 *
 * @param array $room
 *   A array of room node ids.
 * @param array $range
 *   Array with elements 'start', 'end'. Note that these each need to be
 *   formatted properly and adjusted to UTC:
 *     ->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)
 *
 * @return bool
 *   TRUE if the room is available for the parameters.
 */
function intercept_bulk_room_reservation_check_availability($room, array $range) {
  $available = FALSE;
  $keys = array_keys($range);
  $params = [
    // 'debug' => TRUE,
    'rooms' => $room,
    'start' => ($range[$keys[0]])->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT),
    'end' => ($range[$keys[1]])->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT),
  ];
  $reservationManager = \Drupal::service('intercept_core.reservation.manager');

  // Allow privileged users to make reservations with open hours conflicts.
  $user_is_privileged = FALSE;
  $privileged_roles = [
    'administrator',
    'intercept_system_admin',
    'intercept_room_manager',
  ];
  $current_user = \Drupal::currentUser();
  $roles = $current_user->getRoles();
  foreach ($privileged_roles as $privileged_role) {
    if (in_array($privileged_role, $roles) || $current_user->id() == 1) {
      $user_is_privileged = TRUE;
      break;
    }
  }

  if ($availability = $reservationManager->availability($params)) {
    foreach ($availability as $room_availability) {
      if (!($room_availability['user_exceeded_limit']
        || $room_availability['has_reservation_conflict']
        || ($room_availability['has_conflict'] && !$user_is_privileged)
        || ($room_availability['has_open_hours_conflict'] && !$user_is_privileged)
        || ($room_availability['has_max_duration_conflict'] && !$user_is_privileged)
        || $room_availability['is_closed'])) {
        $available = TRUE;
      }
      else {
        if ($room_availability['has_reservation_conflict'] ||
          ($room_availability['has_conflict'] && !$user_is_privileged)) {
          // Add a message with details about the conflict.
          $storage = \Drupal::entityTypeManager()->getStorage('node');
          $room_node = $storage->load(reset($params['rooms']));
          if (count($room_availability['dates']) >= 1) {
            foreach ($room_availability['dates'] as $conflict) {
              $conflict_date_string = $conflict['start'];
              $conflict_time = strtotime($conflict_date_string);
              $conflict_date_time = new \DateTime();
              $conflict_date_time->setTimestamp($conflict_time);
              $siteTimeZone = \Drupal::config('system.date')->get('timezone')['default'];
              $siteTimeZone = new \DateTimeZone($siteTimeZone);
              $conflict_date_time->setTimezone($siteTimeZone);
              $formatted_date = $conflict_date_time->format('m-d-Y');
              $message = $room_node->label() . ' has a conflict with your series on ' . $formatted_date . '.';
              \Drupal::messenger()->addWarning($message);
            }
          }
        }
        else {
          if ($room_availability['has_open_hours_conflict'] && !$user_is_privileged) {
            // Add a message with details about the room having an open hours
            // conflict.
            $storage = \Drupal::entityTypeManager()->getStorage('node');
            $room_node = $storage->load(reset($params['rooms']));
            $message = $room_node->label() . ' has an \'open hours\' conflict with your series.';
            \Drupal::messenger()->addWarning($message);
          }
          else {
            if ($room_availability['is_closed']) {
              // Add a message with details about the room being closed.
              $storage = \Drupal::entityTypeManager()->getStorage('node');
              $room_node = $storage->load(reset($params['rooms']));
              $conflict_date_string = $room_availability['dates'][0]['start'];
              $conflict_time = strtotime($conflict_date_string);
              $conflict_date_time = new \DateTime();
              $conflict_date_time->setTimestamp($conflict_time);
              $siteTimeZone = \Drupal::config('system.date')->get('timezone')['default'];
              $siteTimeZone = new \DateTimeZone($siteTimeZone);
              $conflict_date_time->setTimezone($siteTimeZone);
              $formatted_date = $conflict_date_time->format('m-d-Y');
              $storage = \Drupal::entityTypeManager()->getStorage('node');
              $room_node = $storage->load(reset($params['rooms']));
              $message = $room_node->label() . ' is closed on ' . $formatted_date;
              \Drupal::messenger()->addWarning($message);
            }
          }
        }
      }
    }
  }

  // Intercept can check if availability conflicts exist because of the current
  // event node. Here we'll check availability conflicts with the current bulk
  // room reservation.
  if (!$available) {
    $route_match = \Drupal::routeMatch();
    // Entity will be found in the route parameters.
    if (($route = $route_match->getRouteObject()) && ($parameters = $route->getOption('parameters'))) {
      // Determine if the current route represents an entity.
      foreach ($parameters as $name => $options) {
        if (isset($options['type']) && strpos($options['type'], 'entity:') === 0) {
          $entity = $route_match->getParameter($name);
          if ($entity instanceof ContentEntityInterface && $entity->hasLinkTemplate('canonical')) {
            break;
          }
        }
      }
    }
    // The entity-from-route-parameters bit fails on an add form - on which a
    // conflict is certainly not with the entity in the add form.
    if (empty($entity)) {
      return $available;
    }

    $storage = \Drupal::entityTypeManager()->getStorage('room_reservation');
    if (count($entity->field_related_room_reservations) == 0) {
      return $available;
    }
    foreach ($entity->field_related_room_reservations as $key => $room_reservation) {
      // Load this room_reservation and see if it conflicts. Define conflict as
      // having any overlap; see https://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap#answer-325964.
      $room_reservation_entity = $storage->load($room_reservation->target_id);

      $room_res = [
        'start' => DateTime::createFromFormat('Y-m-d\TH:i:s', $room_reservation_entity->field_dates->value)->getTimestamp(),
        'end' => DateTime::createFromFormat('Y-m-d\TH:i:s', $room_reservation_entity->field_dates->end_value)->getTimestamp(),
      ];
      if (($room_res['start'] <= DateTime::createFromFormat('Y-m-d\TH:i:s', $params['end'])->getTimestamp())
        && ($room_res['end'] >= DateTime::createFromFormat('Y-m-d\TH:i:s', $params['start'])->getTimestamp())
        && $room_reservation_entity->field_room->target_id == $room[0]) {
        // Treat overridden room reservations as though they are not part of
        // the series in determining availability.
        if (count($entity->field_overridden) > 0) {
          foreach ($entity->field_overridden as $overridden_id) {
            if ($overridden_id->target_id == $room_reservation->target_id) {
              $message = t('<a href=":room_reservation_edit">This overridden room reservation</a> is in conflict with your series, making your desired room unavailable for the series. You can <a href=":delete_overridden" target="_blank">delete the overridden room reservation</a>, <a href=":remove_override" target="_blank">remove the override</a> or change the dates or desired room for your series.',
              [
                ':room_reservation_edit' => \Drupal::request()->getSchemeAndHttpHost() . '/bulk-room-reservation/' . $room_reservation->target_id,
                ':delete_overridden' => \Drupal::request()->getSchemeAndHttpHost() . '/room-reservation/' . $room_reservation->target_id . '/delete',
                ':remove_override' => \Drupal::request()->getSchemeAndHttpHost() . '/bulk-room-reservation/' . $room_reservation->target_id . '/remove-override',
              ]);
              \Drupal::messenger()->addError($message);
              return FALSE;
            }
          }
        }
        $available = TRUE;
        break;
      }
    }
  }

  return $available;
}

/**
 * {@inheritDoc}
 */
function _getDayOfWeek($date) {
  return date('w', strtotime($date));
}

/**
 * Converts a date to the storage format's timezone.
 *
 * @param object $date
 *   The DateTime object.
 * @param string $timezone
 *   PHP Timezone name.
 *
 * @return \Drupal\Core\Datetime\DrupalDateTime
 *   The converted DrupalDateTime object.
 */
function intercept_bulk_room_reservation_compensate($date, $timezone = 'default') {
  $converted = \Drupal::service('intercept_core.utility.dates')->convertTimezone($date, 'storage')
    ->format(\Drupal::service('intercept_core.utility.dates')->getStorageFormat());
  $new_date = \Drupal::service('intercept_core.utility.dates')->getDrupalDate($converted, 'default');
  return $timezone == 'default' ? $new_date : \Drupal::service('intercept_core.utility.dates')->convertTimezone($new_date, 'storage');
}

/**
 * Implements hook_entity_presave().
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity object.
 */
function intercept_bulk_room_reservation_room_reservation_presave(EntityInterface $entity) {
  // If this room reservation is related to a bulk room reservation, mark it as
  // overridden on that bulk room reservation, unless the user is in the process
  // of saving the bulk room reservation.
  $route = \Drupal::routeMatch()->getRouteName();
  if ($route == 'system.batch_page.json') {
    return;
  }

  $query = \Drupal::entityQuery('bulk_room_reservation')
    ->accessCheck(FALSE)
    ->condition('field_related_room_reservations', $entity->id(), 'IN');
  $result = $query->execute();
  if (!empty($result)) {
    $storage = \Drupal::entityTypeManager()->getStorage('bulk_room_reservation');
    $bulk_room_reservation = $storage->load(array_shift($result));

    // Add this room reservation as overridden if it isn't already.
    foreach ($bulk_room_reservation->field_overridden as $id) {
      if ($id->target_id == $entity->id()) {
        return;
      }
    }

    $bulk_room_reservation->field_overridden[] = [
      'target_id' => $entity->id(),
    ];
    $bulk_room_reservation->save();
  }
}

/**
 * Implements hook_entity_predelete().
 *
 * When deleting room_reservations, remove them from any bulk room reservation
 * to which they belong.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The RoomReservation entity to delete.
 */
function intercept_bulk_room_reservation_room_reservation_predelete(EntityInterface $entity) {
  $query = \Drupal::entityQuery('bulk_room_reservation')
    ->accessCheck(FALSE)
    ->condition('field_related_room_reservations', $entity->id(), 'IN');
  $result = $query->execute();
  if (!empty($result)) {
    $bulk_room_reservation = \Drupal::entityTypeManager()->getStorage('bulk_room_reservation')->load(array_shift($result));
    // Remove from field_related_room_reservations and field_overridden, as
    // appropriate.
    foreach (['field_related_room_reservations', 'field_overridden'] as $field) {
      foreach ($bulk_room_reservation->$field->getValue() as $key => $target_id) {
        if ($target_id['target_id'] == $entity->id()) {
          $bulk_room_reservation->get($field)->removeItem($key);
          $bulk_room_reservation->save();
          break;
        }
      }
    }
  }
}

/**
 * Returns an array of room reservation entity ids to be deleted.
 *
 * Invoked in BulkRoomReservation::preDelete().
 *
 * @param array $entities
 *   Array of BulkRoomReservation entities.
 *
 * @return array
 *   Array of room reservation entity ids.
 */
function intercept_bulk_room_reservation_delete_ids(array $entities) {
  $deleteIds = [];

  foreach ($entities as $key => $entity) {
    foreach ($entity->field_related_room_reservations->referencedEntities() as $room_reservation) {
      if (in_array($room_reservation, $entity->field_overridden->referencedEntities())) {
        // This room_reservation is overridden; don't delete.
        continue;
      }
      $deleteIds[] = $room_reservation->id();
    }
  }

  return $deleteIds;
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 *
 * @param array $suggestions
 * @param array $variables
 */
function intercept_bulk_room_reservation_theme_suggestions_date_recur_modular_alpha_widget_alter(array &$suggestions, array $variables) {
  if (isset($variables['widget']['#form_id']) && $variables['widget']['#form_id'] == 'bulk_room_reservation_add_form') {
    // This template will be added to the Gin theme via patch in composer.json.
    $suggestions[] = 'date_recur_modular_alpha_widget__bulk_room_reservation';
  }
}

/**
 * Implements hook_intercept_room_reservation_dates_alter().
 */
function intercept_bulk_room_reservation_intercept_room_reservation_dates_alter(&$dates, $params, $node) {
  // Unset dates previously marked as closed by the Intercept Location Closing module.
  // We don't want to do this in the case of blocking bulk room reservations.
  $current_path = \Drupal::service('path.current')->getPath();
  if (strstr($current_path, '/bulk-room-reservation')) {
    if (\Drupal::moduleHandler()->moduleExists('intercept_location_closing')) {
      if ($location = $node->field_location->entity) {
        if ($closings = \Drupal::service('intercept_location_closing.query')->locationClosings($location, $params['start'], $params['end'])) {
          foreach ($closings as $closing_id) {
            $closing = \Drupal::service('entity_type.manager')->getStorage('intercept_location_closing')->load($closing_id);
            unset($dates[$closing->uuid()]);
          }
        }
      }
    }
  }
}

/**
 * Implements hook_field_widget_single_element_WIDGET_TYPE_form_alter().
 */
function intercept_bulk_room_reservation_field_widget_single_element_date_recur_modular_alpha_form_alter(array &$elements, FormStateInterface $form_state, array $context) {
  // If this is part of a bulk room reservation, alter the field as
  // it is done in events module.
  $route = \Drupal::request()->attributes->get('_route');
  if ($route == 'entity.bulk_room_reservation.edit_form' || $route == 'entity.bulk_room_reservation.add_form') {
    if (\Drupal::moduleHandler()->moduleExists('intercept_event')) {
      \Drupal::service('intercept_event.recurring_manager')->dateRecurWidgetFormAlter($elements, $form_state, $context);
    }
  }
}

/**
 * Implements hook_form_alter().
 */
function intercept_bulk_room_reservation_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  switch ($form_id) {
    case 'bulk_room_reservation_add_form':
    case 'bulk_room_reservation_edit_form':
      $form['#attached']['library'][] = 'intercept_event/event_form_helper';
      // Hide revision log message & new revision fields.
      $form['revision_log']['#access'] = FALSE;
      $form['revision_information']['#access'] = FALSE;
      break;
  }
}
