<?php

namespace Helpers\Cache;

use Helpers\Fichier\Fichier;
use Helpers\Log\LogSysteme;
use Psr\SimpleCache\CacheInterface;

class OPCache implements CacheInterface
{
    private string $directory;

    public function __construct(string $directory)
    {
        $this->directory = $directory;
        Fichier::touchDir($this->directory);
    }

    private static ?Cache $instance = null;

    public static function getInstance() :Cache
    {
        return self::$instance ?? (self::$instance = new Cache());
    }

//______________________________________________________________________________
    
    public function get($key, $default = null)
    {
        return @include_once $this->directory . DIRECTORY_SEPARATOR . $key;
    }

    public function set($key, $value, $ttl = 0)
    {
        // TODO : disallow special char in $key
        if ($ttl instanceof \DateInterval) {
            throw new \Exception('Not implemented');
        }

        $tmpFile = tempnam($this->directory, basename($key));
        $b = @file_put_contents(
            $tmpFile,
            '<?php return '
            . var_export($value, true)
            . ';'
        );
        if ($b === false) {
            LogSysteme::prefixErrorLog('file_put_contents failed on "'.$tmpFile.'"');
            return false;
        }
        
        $b = @rename($tmpFile, $this->getFilePath($key));
        if ($b === false) {
            LogSysteme::prefixErrorLog('rename failed on "'.$tmpFile.'" toward "'.$key.'"');
            return false;
        }

        return true;
    }

    public function delete($key)
    {
        $file = $this->getFilePath($key);
        if (!@unlink($file) && file_exists($file)) {
            LogSysteme::prefixErrorLog('Failed to delete cache file "'.$key.'"');
            return false;
        }
        return true;
    }

    public function clear()
    {
        if (! is_file($this->directory)) {
            return true;
        }
        $b = @rename($this->directory, $this->directory . '_garbage');
        if ($b === false) {
            LogSysteme::prefixErrorLog('rename failed on "'.$this->directory.'"');
            return false;
        }
        Fichier::touchDir($this->directory);
        return true;
    }

    /**
     * @param iterable<mixed> $keys
     * @return iterable<mixed>
     */
    public function getMultiple($keys, $default = null)
    {
        throw new \Exception('Not implemented');
    }

    /**
     * @param iterable<mixed> $values
     */
    public function setMultiple($values, $ttl = null)
    {
        throw new \Exception('Not implemented');
    }
    
    /**
     * @param iterable<mixed> $keys
     */
    public function deleteMultiple($keys)
    {
        return array_reduce(iterator_to_array($keys), function ($carry, $key) {
            return $carry && $this->delete($key);
        }, true);
    }

    public function has($key)
    {
        if (!is_string($key)) {
            throw new \Exception('!!!');
        }
        $file = $this->getFilePath($key);
        return opcache_is_script_cached($file) || file_exists($file);
    }

    private function getFilePath(string $key) :string
    {
        return $this->directory . DIRECTORY_SEPARATOR . $key;
    }
}
