<?php

/**
 * @see       https://github.com/laminas/laminas-cache for the canonical source repository
 * @copyright https://github.com/laminas/laminas-cache/blob/master/COPYRIGHT.md
 * @license   https://github.com/laminas/laminas-cache/blob/master/LICENSE.md New BSD License
 */

namespace Laminas\Cache\Storage\Adapter;

use Laminas\Cache\Exception;
use Laminas\Cache\Storage\Capabilities;
use stdClass;

abstract class AbstractZendServer extends AbstractAdapter
{
    /**
     * The namespace separator used on Zend Data Cache functions
     *
     * @var string
     */
    const NAMESPACE_SEPARATOR = '::';

    /* reading */

    /**
     * Internal method to get an item.
     *
     * @param  string  $normalizedKey
     * @param  bool $success
     * @param  mixed   $casToken
     * @return mixed Data on success, null on failure
     * @throws Exception\ExceptionInterface
     */
    protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
    {
        $namespace   = $this->getOptions()->getNamespace();
        $prefix      = ($namespace === '') ? '' : $namespace . self::NAMESPACE_SEPARATOR;

        $result = $this->zdcFetch($prefix . $normalizedKey);
        if ($result === false) {
            $result  = null;
            $success = false;
        } else {
            $success  = true;
            $casToken = $result;
        }

        return $result;
    }

    /**
     * Internal method to get multiple items.
     *
     * @param  array $normalizedKeys
     * @return array Associative array of keys and values
     * @throws Exception\ExceptionInterface
     */
    protected function internalGetItems(array & $normalizedKeys)
    {
        $namespace = $this->getOptions()->getNamespace();
        if ($namespace === '') {
            return $this->zdcFetchMulti($normalizedKeys);
        }

        $prefix       = $namespace . self::NAMESPACE_SEPARATOR;
        $internalKeys = [];
        foreach ($normalizedKeys as $normalizedKey) {
            $internalKeys[] = $prefix . $normalizedKey;
        }

        $fetch   = $this->zdcFetchMulti($internalKeys);
        $result  = [];
        $prefixL = strlen($prefix);
        foreach ($fetch as $k => & $v) {
            $result[substr($k, $prefixL)] = $v;
        }

        return $result;
    }

    /**
     * Internal method to test if an item exists.
     *
     * @param  string $normalizedKey
     * @return bool
     * @throws Exception\ExceptionInterface
     */
    protected function internalHasItem(& $normalizedKey)
    {
        $namespace = $this->getOptions()->getNamespace();
        $prefix    = ($namespace === '') ? '' : $namespace . self::NAMESPACE_SEPARATOR;
        return  ($this->zdcFetch($prefix . $normalizedKey) !== false);
    }

    /**
     * Internal method to test multiple items.
     *
     * @param  array $normalizedKeys
     * @return array Array of found keys
     * @throws Exception\ExceptionInterface
     */
    protected function internalHasItems(array & $normalizedKeys)
    {
        $namespace = $this->getOptions()->getNamespace();
        if ($namespace === '') {
            return array_keys($this->zdcFetchMulti($normalizedKeys));
        }

        $prefix       = $namespace . self::NAMESPACE_SEPARATOR;
        $internalKeys = [];
        foreach ($normalizedKeys as $normalizedKey) {
            $internalKeys[] = $prefix . $normalizedKey;
        }

        $fetch   = $this->zdcFetchMulti($internalKeys);
        $result  = [];
        $prefixL = strlen($prefix);
        foreach ($fetch as $internalKey => & $value) {
            $result[] = substr($internalKey, $prefixL);
        }

        return $result;
    }

    /**
     * Get metadata for multiple items
     *
     * @param  array $normalizedKeys
     * @return array Associative array of keys and metadata
     *
     * @triggers getMetadatas.pre(PreEvent)
     * @triggers getMetadatas.post(PostEvent)
     * @triggers getMetadatas.exception(ExceptionEvent)
     */
    protected function internalGetMetadatas(array & $normalizedKeys)
    {
        $namespace = $this->getOptions()->getNamespace();
        if ($namespace === '') {
            $result = $this->zdcFetchMulti($normalizedKeys);
            return array_fill_keys(array_keys($result), []);
        }

        $prefix       = $namespace . self::NAMESPACE_SEPARATOR;
        $internalKeys = [];
        foreach ($normalizedKeys as $normalizedKey) {
            $internalKeys[] = $prefix . $normalizedKey;
        }

        $fetch   = $this->zdcFetchMulti($internalKeys);
        $result  = [];
        $prefixL = strlen($prefix);
        foreach ($fetch as $internalKey => $value) {
            $result[substr($internalKey, $prefixL)] = [];
        }

        return $result;
    }

    /* writing */

    /**
     * Internal method to store an item.
     *
     * @param  string $normalizedKey
     * @param  mixed  $value
     * @return bool
     * @throws Exception\ExceptionInterface
     */
    protected function internalSetItem(& $normalizedKey, & $value)
    {
        $options   = $this->getOptions();
        $namespace = $options->getNamespace();
        $prefix    = ($namespace === '') ? '' : $namespace . self::NAMESPACE_SEPARATOR;
        $this->zdcStore($prefix . $normalizedKey, $value, $options->getTtl());
        return true;
    }

    /**
     * Internal method to remove an item.
     *
     * @param  string $normalizedKey
     * @return bool
     * @throws Exception\ExceptionInterface
     */
    protected function internalRemoveItem(& $normalizedKey)
    {
        $namespace = $this->getOptions()->getNamespace();
        $prefix    = ($namespace === '') ? '' : $namespace . self::NAMESPACE_SEPARATOR;
        return $this->zdcDelete($prefix . $normalizedKey);
    }

    /* status */

    /**
     * Internal method to get capabilities of this adapter
     *
     * @return Capabilities
     */
    protected function internalGetCapabilities()
    {
        if ($this->capabilities === null) {
            $this->capabilityMarker = new stdClass();
            $this->capabilities     = new Capabilities(
                $this,
                $this->capabilityMarker,
                [
                    'supportedDatatypes' => [
                        'NULL'     => true,
                        'boolean'  => true,
                        'integer'  => true,
                        'double'   => true,
                        'string'   => true,
                        'array'    => true,
                        'object'   => 'object',
                        'resource' => false,
                    ],
                    'supportedMetadata'  => [],
                    'minTtl'             => 1,
                    'maxTtl'             => 0,
                    'staticTtl'          => true,
                    'ttlPrecision'       => 1,
                    'useRequestTime'     => false,
                    'lockOnExpire'       => ini_get('zend_datacache.lock_on_expire') ? 120 : 0,
                    'maxKeyLength'       => 0,
                    'namespaceIsPrefix'  => true,
                    'namespaceSeparator' => self::NAMESPACE_SEPARATOR,
                ]
            );
        }

        return $this->capabilities;
    }

    /* internal wrapper of zend_[disk|shm]_cache_* functions */

    /**
     * Store data into Zend Data Cache (zdc)
     *
     * @param  string $internalKey
     * @param  mixed  $value
     * @param  int    $ttl
     * @return void
     * @throws Exception\RuntimeException
     */
    abstract protected function zdcStore($internalKey, $value, $ttl);

    /**
     * Fetch a single item from Zend Data Cache (zdc)
     *
     * @param  string $internalKey
     * @return mixed The stored value or FALSE if item wasn't found
     * @throws Exception\RuntimeException
     */
    abstract protected function zdcFetch($internalKey);

    /**
     * Fetch multiple items from Zend Data Cache (zdc)
     *
     * @param  array $internalKeys
     * @return array All found items
     * @throws Exception\RuntimeException
     */
    abstract protected function zdcFetchMulti(array $internalKeys);

    /**
     * Delete data from Zend Data Cache (zdc)
     *
     * @param  string $internalKey
     * @return bool
     * @throws Exception\RuntimeException
     */
    abstract protected function zdcDelete($internalKey);
}
