<?php

namespace Drupal\pagedesigner\Service;

use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\TempStore\SharedTempStoreFactory;

/**
 * Node locking class.
 *
 * Provides an interface to create and release locks on page nodes.
 */
class Locker {
  /**
   * The storage for the lock.
   *
   * @var \Drupal\user\SharedTempStore
   */
  protected $store = NULL;

  /**
   * The page key for the lock.
   *
   * @var string
   */
  protected $key = NULL;

  /**
   * Creates a new locker instance.
   *
   *   The shared temp store factory.
   */
  public function __construct(SharedTempStoreFactory $shared_tempstore) {
    $this->store = $shared_tempstore->get('pagedesigner.lock');
  }

  /**
   * Set the entity to create locks for.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity to set the lock key for.
   *
   * @return self
   *   The current instance.
   */
  public function setEntity(ContentEntityInterface $entity) {
    $this->key = 'lock:' . $entity->id() . ':' . $entity->language()->getId();
    return $this;
  }

  /**
   * Return the metda data of the current lock.
   *
   * @return object
   *   The metadata of the current lock.
   */
  public function getMetadata() {
    return $this->store->getMetadata($this->key);
  }

  /**
   * Return the tab identifier of the current lock.
   *
   * @return string
   *   The identifier of the current lock.
   */
  public function getIdentifier() {
    $identifier = $this->store->get($this->key);
    return $identifier;
  }

  /**
   * Check whether current user has a lock, false otherwise.
   *
   * @return bool
   *   Whether the current user has a lock.
   */
  public function hasLock() {
    if ($this->store->getIfOwner($this->key) !== NULL) {
      return TRUE;
    }
    return FALSE;
  }

  /**
   * Acquires a lock for the current user using the page information.
   *
   * Acquires a lock for the current user using the page information and
   * (optionally) an identifier. If the identifier is provided,
   * it must match the existing lock or no lock must be present.
   *
   * @param string $identifier
   *   The identifier to match.
   *
   * @return bool
   *   TRUE if the lock was acquired, FALSE otherwise.
   */
  public function acquire($identifier = NULL) {
    // Create if key does not exist or update the lock if user is owner.
    if ($this->createLock($identifier)) {
      return TRUE;
    }
    // User is not owner of the lock.
    // If lock has expired, delete the expired lock and create a new one.
    $updated = DrupalDateTime::createFromTimestamp($this->store->getMetadata($this->key)->getUpdated());
    $age = (new DrupalDateTime())->getTimestamp() - $updated->getTimestamp();

    if ($age >= 180) {
      $this->store->delete($this->key);
      return $this->createLock($identifier);
    }

    // Valid lock of other user found.
    return FALSE;
  }

  /**
   * Release the lock of the current user, optionally matching the identifier.
   *
   * @param string $identifier
   *   The identifier to match.
   *
   * @return bool
   *   TRUE if the lock was released,
   *   FALSE if key does not exist or identifier didn't match otherwise.
   */
  public function release($identifier = NULL) {
    $data = $this->store->getIfOwner($this->key);
    if ($data !== NULL) {
      if ($identifier !== NULL) {
        if ($data == $identifier) {
          return $this->store->deleteIfOwner($this->key);
        }
      }
      else {
        return $this->store->deleteIfOwner($this->key);
      }
    }
    return FALSE;
  }

  /**
   * Force release the lock.
   *
   * @return bool
   *   Whether the lock has been released.
   */
  public function forceRelease($key = NULL) {
    if (empty($key)) {
      $key = $this->key;
    }
    return $this->store->delete($key);
  }

  /**
   * Helper function to create locks.
   *
   * @param string $identifier
   *   The identifier to match.
   *
   * @return bool
   *   TRUE if the lock was created, FALSE otherwise.
   */
  protected function createLock($identifier) {
    $lockIdentifier = $this->store->getIfOwner($this->key);
    if ($lockIdentifier !== NULL && $lockIdentifier !== $identifier) {
      return FALSE;
    }
    if (!$this->store->setIfOwner($this->key, $identifier)) {
      return $this->store->setIfNotExists($this->key, $identifier);
    }
    return TRUE;
  }

}
