<?php

/**
 * @file
 */

use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Drupal\Core\Session\AccountInterface;

/**
 * Implements hook_help().
 */
function alt_login_help($route_name, $route_match) {
  if ($route_name == 'help.page.alt_login') {
    return t("Allows login with various aliases, and formats the user's display name.");
  }
}

/**
 * Implements hook_user_format_name_alter().
 *
 * Manipulate the user display name according to settings. Since results of this
 * hook aren't cached, a static reduces calls to the token system.
 *
 * @note there's no easy way to dedupe accounts' display names.
 */
function alt_login_user_format_name_alter(&$name, AccountInterface $account) {
  if ($account->isAnonymous() or ($account instanceof UserInterface and $account->isNew())) {
    // This will be the configured 'anonymous' value
    return;
  }
  static $names = [];
  $uid = $account->id();
  if (!isset($names[$uid])) {
    // Anonymous users
    if (\Drupal::currentUser()->isAnonymous()) {
      $anon = \Drupal::config('alt_login.settings')->get('display_anon');
      $names[$uid] = \Drupal::token()
        ->replace($anon, ['user' => $account], ['clear' => true]);
    }
    // Do the replacement if the module is configured.
    elseif ($template = \Drupal::config('alt_login.settings')->get('display')) {
      $user = $account instanceOf UserInterface ? $account : User::load($uid);
      $names[$uid] = \Drupal::token()->replace(
        $template,
        ['user' => $user],
        ['clear' => TRUE]
      );
    }
    // If the above didn't produce a string, fall back to the username.
    if (empty($names[$uid])) {
      $names[$uid] = $account->getAccountName();
    }
  }
  $name = $names[$uid];
}

/**
 * Implements hook_module_implements_alter().
 *
 * Ensure that this is the last module to format the username.
 */
function alt_login_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'user_format_name_alter') {
    unset($implementations['alt_login']);
    $implementations['alt_login'] = FALSE;
  }
}

/**
 * Implements hook_form_alter().
 */
function alt_login_form_alter(&$form, $form_state, $form_id) {
  // Show all the login options on the user form.
  $user_forms = ['user_form', 'user_create_form', 'user_register_form', 'user_admin_form'];
  if (in_array($form_id, $user_forms)) {
    // Rewrite the username field to be clearer.
    $user = $form_state->getFormObject()->getEntity();
    $aliases = alt_login_get_aliases($user);
    $namefield = &$form['account']['name'];
    if (empty($aliases['username'])) {
      if ($user->isNew()) {
        $namefield['#access'] = FALSE;
        $namefield['#value'] = user_password(8);
      }
      elseif (count($aliases) == 1) {
        $namefield = [
          '#type' => 'markup',
          '#markup' => t("Login using '%name'", ['%name' => reset($aliases)]),
          '#description' => t('This is determined by @descriptions.', ['@descriptions' => implode(', ', alt_login_active_descriptions())]),
          '#weight' => -1
        ];
      }
      else {
        $namefield['#access'] = FALSE;
        $form['account']['aliases'] = [
          '#title' => t('Log in with these aliases'),
          '#theme' => 'item_list',
          '#items' => $aliases,
          '#weight' => -1
        ];
      }
    }
    else {// If the username plugin IS used.
      unset($aliases['username']);
      if (count($aliases)) {
        $namefield['#description'] = t('You can also login with: @alts', ['@alts' => implode(', ', $aliases)]);
      }
    }
    $form['account']['roles']['#weight'] = 2;
    $form['#validate'][] = 'alt_login_validate_dedupe_aliases';
  }
  // Add a validation callback to login fields to allow alternative login names
  elseif (in_array($form_id, ['user_login_form', 'user_login_block'])) {
    $aliases = alt_login_get_aliases(User::load(0));
    $form['name']['#title'] = implode(', ', $aliases);
    unset($form['name']['#description']);
    $form['name']['#element_validate'][] = 'alt_login_login_name_element_validate';
  }
}

/**
 * Element validation callback for login form name field.
 *
 * Look up the real user name from the given login id and replace it ready for
 * the form-level validation.
 */
function alt_login_login_name_element_validate(&$element, $form_state) {
  $name = alt_login_convert_alias($element['#value']);
  $form_state->setValue('name', $name);
}

/**
 * Utility
 *
 * Look up the given alias and return the real username for logging in.
 *
 * @param string $alias
 *
 * @return string | NULL
 */
function alt_login_convert_alias($alias) {
  foreach (\Drupal::service('alt_login.method_manager')->activePlugins() as $plugin) {
    if ($plugin->applies($alias)) {
      if ($user = $plugin->getUserFromAlias($alias)) {
        return $user->getAccountName();
      }
    }
  }
  // ' ' is an invalid username which will not break the sql query.
  return ' ';
}

/**
 * Get all the alternative login strings for the given user
 *
 * @param Userinterface $user
 *
 * @return string[]
 */
function alt_login_get_aliases(UserInterface $user) {
  $alts = [];
  if ($user->isAuthenticated()) {
    foreach (\Drupal::service('alt_login.method_manager')->activePlugins() as $plugin_id => $plugin) {
      $alts[$plugin_id] = $plugin->getAlias($user);
    }
  }
  else {
    $labels = \Drupal::service('alt_login.method_manager')->getOptions();
    foreach (\Drupal::service('alt_login.method_manager')->activePlugins() as $plugin_id => $def) {
      $alts[$plugin_id] = $labels[$plugin_id];
    }
  }
  return $alts;
}

/**
 * Implements hook_tokens_alter().
 *
 * Replace the username with any aliases.
 */
function alt_login_tokens_alter(&$replacements, array $context, $bubbleable_metadata) {
  if ($context['type'] == 'user' and isset($context['data']['user']) and isset($replacements['[user:name]'])) {
    $replacements['[user:name]'] = implode(t(' OR '), alt_login_get_aliases($context['data']['user']));
  }
}

/**
 * Implements hook_user_presave().
 */
function alt_login_user_presave(UserInterface $account) {
  if ($account->isNew()) {
    // Awkwardly, users migrating in are have isNew = true.
    if ($account->getCreatedTime() < Drupal::time()->getRequestTime()) {
      return;
    }
    // Contrive a username if the username isn't otherwise used.
    // use the first part of the email and extend with _1 in case of dupulicates.
    $plugins = \Drupal::Config('alt_login.settings')->get('aliases');
    if (!in_array('username', $plugins)) {
      $new_name = substr($account->getEmail(), 0, strpos($account->getEmail(), '@'));
      \Drupal::entityQuery('user')->condition('name', "$new_name%", 'LIKE')->execute();
      $lastname = \Drupal::database()->select('users_field_data', 'u')
        ->fields('u', ['name'])
        ->condition('name', "$new_name%", 'LIKE')
        ->range(0, 1)
        ->orderBy('name', 'DESC')
        ->execute()->fetchField();
      if ($lastname && preg_match('/^.*(_[0-9]+)*$/', $lastname, $matches)) {
        $inc=1;
        if (isset($matches[1])) {
          $inc = $matches[1]++;
        }
        $new_name .= '_'.$inc;
      }
      $account->setUsername($new_name);
      // Note that the the User RegisterForm logs user creation with another name.
      \Drupal::logger('Alt Login')->notice('New user name set to '.$new_name);
    }
  }
}

/**
 * User form validation callback.
 */
function alt_login_validate_dedupe_aliases($form, $form_state) {
  $user = $form_state->getFormObject()->buildEntity($form, $form_state);
  foreach (\Drupal::service('alt_login.method_manager')->activePlugins() as $plugin) {
    if ($field_name = $plugin->dedupeAlias($user)) {
      $form_state->setErrorByName($field_name, t('This alias is already taken.'));
    }
  }
}

/**
 * Utility
 *
 * Could be useful for exporting.
 *
 * @param UserInterface $user
 * @param string $plugin_id
 *
 * @return string
 */
function alt_login_get_alias(UserInterface $user, $plugin_id) {
  return \Drupal::service('alt_login.method_manager')
    ->createInstance($plugin_id)
    ->getAlias($user);
}

/**
 * Utility
 *
 * Load all the active plugins()
 *
 * @return AltLoginMethodInterface[]
 *
 * @deprecated
 */
function alt_login_active_plugins() {
  @trigger_error("alt_login_active_plugins() is deprecated. User Drupal\alt_login\AltLoginMethodManager::activePlugins()", E_USER_DEPRECATED);
  return \Drupal::service('alt_login.method_manager')->activePlugins();
}

function alt_login_active_descriptions() {
  $plugin_manager = \Drupal::service('alt_login.method_manager');
  foreach (\Drupal::Config('alt_login.settings')->get('aliases') as $plugin_id) {
    $def = $plugin_manager->getDefinition($plugin_id);
    $descriptions[] = $def['description'];
  }
  return $descriptions;
}

/**
 * Implements hook_alt_login_info_alter().
 *
 * Check for the address module and put username last
 */
function alt_login_alt_login_info_alter(&$definitions) {
  if (isset($definitions['username'])) {
    $def = $definitions['username'];
    unset($definitions['username']);
    $definitions['username'] = $def;
  }
  //this should really be implemented using the plugin annotation
  if (!\Drupal::moduleHandler()->moduleExists('address')) {
    unset($definitions['address_name']);
  }
}