<?php

namespace Drupal\autoslave\Lock;

use Drupal\Core\Database\Connection;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Database\Database;

/**
 * Defines the database lock backend. This is the default backend in Drupal.
 */
class AutoSlaveLockBackend implements LockBackendInterface {

  /**
   * Array of acquired locks.
   *
   * @var array
   */
  protected $locks = [];

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $backend;

  /**
   * Constructs an AutoSlaveLockBackend object.
   *
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection.
   */
  public function __construct(Connection $database) {
    $config = \Drupal::config('autoslave.settings');
    $backend = $config->get('autoslave_lock_class') ?: 'Drupal\Core\Lock\DatabaseLockBackend';
    $this->backend = new $backend($database);
  }

  /**
   * Forces queries to use the master database.
   *
   * @param bool $value
   *   TRUE to force master, FALSE to allow slave.
   */
  private function forceMaster($value) {
    $conn = Database::getConnection();
    if ($conn->driver() == 'autoslave') {
      $conn->forceMaster($value);
    }
  }

  /**
   * Implements Drupal\Core\Lock\LockBackedInterface::acquire().
   */
  public function acquire($name, $timeout = 30.0) {
    $already_acquired = isset($this->locks[$name]);
    if ($result = $this->backend->acquire($name, $timeout)) {
      $this->locks[$name] = TRUE;
      // Force master.
      if (!$already_acquired) {
        $this->forceMaster(1);
      }
    }
    return $result;
  }

  /**
   * Implements Drupal\Core\Lock\LockBackedInterface::wait().
   */
  public function wait($name, $delay = 30) {
    return $this->backend->wait($name, $delay);
  }

  /**
   * Implements Drupal\Core\Lock\LockBackedInterface::lockMayBeAvailable().
   */
  public function lockMayBeAvailable($name) {
    return $this->backend->lockMayBeAvailable($name);
  }

  /**
   * Implements Drupal\Core\Lock\LockBackedInterface::release().
   */
  public function release($name) {
    if (isset($this->locks[$name])) {
      unset($this->locks[$name]);
      // Unforce master.
      $this->forceMaster(-1);
    }
    return $this->backend->release($name);
  }

  /**
   * Implements Drupal\Core\Lock\LockBackedInterface::releaseAll().
   */
  public function releaseAll($lock_id = NULL) {
    $this->locks = [];
    // Unfore master.
    $this->forceMaster(-count($this->locks));
    return $this->backend->releaseAll($lock_id);
  }

  /**
   * Implements Drupal\Core\Lock\LockBackedInterface::getLockId().
   */
  public function getLockId() {
    return $this->backend->getLockId();
  }

}
