<?php

namespace Velis\Cache;

use Psr\SimpleCache\CacheInterface as PsrCacheInterface;
use Psr\SimpleCache\InvalidArgumentException;
use Velis\App;
use Velis\Arrays;
use Velis\Exception;

/**
 * Base class for different cache types
 *
 * @author Jan Małysiak <jan.malysiak@velis.pl>
 */
abstract class AbstractCache implements CacheInterface
{
    /**
     * @var PsrCacheInterface
     */
    protected $implementation;

    /**
     * @var bool
     */
    protected $debug;

    /**
     * @var array
     */
    protected $profiler = [];

    /**
     * Constructor
     *
     * @param PsrCacheInterface $implementation
     * @param bool $debug
     */
    public function __construct(PsrCacheInterface $implementation, $debug = false)
    {
        $this->implementation = $implementation;
        $this->debug = $debug;
    }

    /**
     * {@inheritDoc}
     */
    public function has($key)
    {
        return $this->implementation->has($key);
    }

    /**
     * {@inheritDoc}
     * @throws InvalidArgumentException
     * @throws Exception
     * @codeCoverageIgnore
     */
    public function exists($key): bool
    {
        Exception::raise('CacheInterface::exists() method is deprecated, use has() instead.');

        return $this->implementation->has($key);
    }

    /**
     * {@inheritDoc}
     */
    public function getMultiple($keys, $default = null)
    {
        $result = [];

        foreach ($keys as $key) {
            $result[$key] = $this->get($key, $default);
        }

        return $result;
    }

    /**
     * {@inheritDoc}
     */
    public function setMultiple($values, $ttl = null)
    {
        $totalResult = true;

        foreach ($values as $key => $value) {
            $singleResult = $this->set($key, $value, $ttl);

            if (!$singleResult) {
                $totalResult = false;
            }
        }

        return $totalResult;
    }

    /**
     * {@inheritDoc}
     * @throws Exception
     * @throws InvalidArgumentException
     * @codeCoverageIgnore
     */
    public function save($key, $value, $ttl)
    {
        Exception::raise('CacheInterface::save() method is deprecated, use set() instead.');

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

    /**
     * {@inheritDoc}
     */
    public function delete($key)
    {
        return $this->implementation->delete($key);
    }

    /**
     * {@inheritDoc}
     */
    public function deleteMultiple($keys)
    {
        return $this->implementation->deleteMultiple($keys);
    }

    /**
     * {@inheritDoc}
     */
    public function clear()
    {
        return $this->implementation->clear();
    }

    /**
     * {@inheritDoc}
     * @throws Exception
     * @codeCoverageIgnore
     */
    public function flush(): bool
    {
        Exception::raise('CacheInterface::flush() method is deprecated, use clear() instead.');

        return $this->implementation->clear();
    }

    /**
     * {@inheritDoc}
     * @throws InvalidArgumentException
     */
    #[\ReturnTypeWillChange]
    public function offsetGet($offset)
    {
        return $this->get($offset);
    }

    /**
     * {@inheritDoc}
     * @throws InvalidArgumentException
     */
    public function offsetSet($offset, $value): void
    {
        $this->set($offset, $value, App::CACHE_24H);
    }

    /**
     * {@inheritDoc}
     * @throws InvalidArgumentException
     */
    public function offsetExists($offset): bool
    {
        return $this->implementation->has($offset);
    }

    /**
     * {@inheritDoc}
     * @throws InvalidArgumentException
     */
    public function offsetUnset($offset): void
    {
        $this->implementation->delete($offset);
    }

    /**
     * {@inheritDoc}
     */
    public function getProfiler(): array
    {
        return $this->profiler;
    }

    /**
     * Create profiler array
     *
     * @param string $key
     * @param string $calledFrom
     * @return void
     */
    protected function initializeProfiler($key, $calledFrom): void
    {
        if ($this->debug) {
            if (!is_array($this->profiler)) {
                $this->profiler = [];
            }
            $this->profiler[$key] = [
                'func' => $calledFrom,
                'caller' => $this->getCaller(),
                'id' => $key,
                'startTime' => microtime(true),
            ];
        }
    }

    /**
     * @return array
     */
    private function getCaller(): array
    {
        $backtrace = debug_backtrace();
        $callerInfo = null;

        foreach ($backtrace as $key => $backtraceRow) {
            if ($backtraceRow['file'] != __FILE__ && strpos($backtraceRow['file'], 'Phalcon/Cache') === false) {
                $fileName = str_replace(ROOT_PATH, '', $backtraceRow['file']);

                if (!$callerInfo) {
                    $callerInfo = [
                        'file' => $fileName,
                        'line' => $backtraceRow['line'],
                        'func' => $backtrace[$key + 1]['function'],
                    ];
                } else {
                    $callerInfo['backtrace'][] = [
                        'file' => $fileName,
                        'line' => $backtraceRow['line'],
                        'func' => array_key_exists($key + 1, $backtrace) ? $backtrace[$key + 1]['function'] : '',
                    ];
                }
            }
        }

        return $callerInfo;
    }

    /**
     * Append execution time to profiler array
     * @param string $key
     */
    protected function concludeProfiler($key): void
    {
        if ($this->debug) {
            $this->profiler[$key]['execTime'] = microtime(true) - $this->profiler[$key]['startTime'];
        }
    }


    /**
     * @param $fromCache
     * @return mixed
     */
    protected function filterEmptyArray($fromCache)
    {
        if (is_array($fromCache) && $fromCache === Arrays::NULL_ARRAY) {
            return [];
        }
        return $fromCache;
    }
}
