<?php declare(strict_types=1);
/**
 * Banker
 *
 * A Caching library implementing psr/cache (PSR 6) and psr/simple-cache (PSR 16)
 *
 * PHP version 7.4
 *
 * @package     Banker
 * @author      Timothy J. Warren <tim@timshomepage.net>
 * @copyright   2016 - 2020  Timothy J. Warren
 * @license     http://www.opensource.org/licenses/mit-license.html  MIT License
 * @version     3.0.0
 * @link        https://git.timshomepage.net/timw4mail/banker
 */
namespace Aviat\Banker;

use DateInterval;
use DateTimeInterface;
use Psr\Cache\CacheItemInterface;

use Aviat\Banker\Driver\DriverInterface;

/**
 * Base class for Cache items
 */
class Item implements CacheItemInterface {

	/**
	 * The driver for the current cache backend
	 *
	 * @var DriverInterface
	 */
	protected DriverInterface $driver;

	/**
	 * The key of the cache item
	 *
	 * @var string
	 */
	protected string $key;

	/**
	 * The expiration time
	 *
	 * @var int | null
	 */
	protected ?int $expiresAt;

	/**
	 * The time to live
	 *
	 * @var int | null
	 */
	protected ?int $ttl;

	/**
	 * The value to save in the cache
	 *
	 * @var mixed
	 */
	protected $value;

	/**
	 * Create a Cache Item object
	 *
	 * @param DriverInterface $driver
	 * @param string $key
	 */
	public function __construct(DriverInterface $driver, string $key)
	{
		$this->driver = $driver;
		$this->key = $key;

		$this->expiresAt = NULL;
		$this->ttl = NULL;
	}

	/**
	 * Returns the key for the current cache item.
	 *
	 * The key is loaded by the Implementing Library, but should be available to
	 * the higher level callers when needed.
	 *
	 * @return string
	 *   The key string for this cache item.
	 */
	public function getKey(): string
	{
		return $this->key;
	}

	/**
	 * Retrieves the value of the item from the cache associated with this object's key.
	 *
	 * The value returned must be identical to the value originally stored by set().
	 *
	 * If isHit() returns false, this method MUST return null. Note that null
	 * is a legitimate cached value, so the isHit() method SHOULD be used to
	 * differentiate between "null value was found" and "no value was found."
	 *
	 * @return mixed
	 *   The value corresponding to this cache item's key, or null if not found.
	 */
	public function get()
	{
		if ($this->isHit())
		{
			return $this->value ?? $this->driver->get($this->key);
		}

		return NULL;
	}

	/**
	 * Confirms if the cache item lookup resulted in a cache hit.
	 *
	 * Note: This method MUST NOT have a race condition between calling isHit()
	 * and calling get().
	 *
	 * @return bool
	 *   True if the request resulted in a cache hit. False otherwise.
	 */
	public function isHit(): bool
	{
		return isset($this->value) || $this->driver->exists($this->key);
	}

	/**
	 * Sets the value represented by this cache item.
	 *
	 * The $value argument may be any item that can be serialized by PHP,
	 * although the method of serialization is left up to the Implementing
	 * Library.
	 *
	 * @param mixed $value
	 *   The serializable value to be stored.
	 *
	 * @return Item
	 *   The invoked object.
	 */
	public function set($value): Item
	{
		$this->value = $value;
		return $this;
	}

	/**
	 * Sets the expiration time for this cache item.
	 *
	 * @param DateTimeInterface|null $expiration
	 *   The point in time after which the item MUST be considered expired.
	 *   If null is passed explicitly, a default value MAY be used. If none is set,
	 *   the value should be stored permanently or for as long as the
	 *   implementation allows.
	 *
	 * @return Item
	 *   The called object.
	 */
	public function expiresAt($expiration = NULL): Item
	{
		if ($expiration instanceof DateTimeInterface)
		{
			$expiration = $expiration->getTimestamp();
		}

		$this->expiresAt = (int) $expiration;

		return $this;
	}

	/**
	 * Sets the expiration time for this cache item.
	 *
	 * @param int|DateInterval|null $time
	 *   The period of time from the present after which the item MUST be considered
	 *   expired. An integer parameter is understood to be the time in seconds until
	 *   expiration. If null is passed explicitly, a default value MAY be used.
	 *   If none is set, the value should be stored permanently or for as long as the
	 *   implementation allows.
	 *
	 * @return Item
	 *   The called object.
	 */
	public function expiresAfter($time = NULL): Item
	{
		if ($time instanceof DateInterval)
		{
			$time = $time->format("%s");
		}

		$this->ttl = (int) $time;
		return $this;
	}

	/**
	 * Save the current value to the cache
	 *
	 * @return bool
	 */
	public function save(): bool
	{
		if ($this->expiresAt !== NULL && $this->expiresAt !== 0)
		{
			$setResult = $this->driver->set($this->key, $this->value);
			$expResult = $this->driver->expiresAt($this->key, $this->expiresAt);

			return $setResult && $expResult;
		}

		if ($this->ttl !== NULL && $this->ttl !== 0)
		{
			return (bool) $this->driver->set($this->key, $this->value, $this->ttl);
		}

		return (bool) $this->driver->set($this->key, $this->value);
	}
}