<?php

/**
 * CfCache - key-value cache library
 */
class CfCache
{
    private string $cacheDirectory = CACHE_DIRECTORY_PATH;
    private string $cacheFile;
    private int $cacheTimeInSeconds = CACHE_TIME_IN_SECONDS;

    private array $data = [];
    private int $time;

    public function __construct($filename, int|null $cacheTimeInSeconds = null)
    {

        if ($cacheTimeInSeconds !== null) {
            $this->cacheTimeInSeconds = $cacheTimeInSeconds;
        }

        // basename returns file name only, in case when the filename
        // contains dots and slashes it will skip them, to avoid going back
        // to the parent directory
        $this->cacheFile = $this->cacheDirectory . '/' . basename($filename);


        if (!is_file($this->cacheFile)) {
            // create cache file if not exists
            $this->saveCache(updateTime: true);
        }

        // additional check if the cache file is inside the cacheDirectory
        if (!str_starts_with(realpath($this->cacheFile), $this->cacheDirectory)) {
            throw new AccessDenyException(
                "You cannot access cache file that is outside of the cache directory."
            );
        }

        $this->loadCacheData();
        $this->verifyCacheTime();
    }

    /**
     * @return void
     */
    private function verifyCacheTime(): void
    {
        if (($this->time + $this->cacheTimeInSeconds) < time()) {
            // reset cache if the cache spoiled
            $this->data = [];
            $this->time = time();
            $this->saveCache();
        }
    }

    /**
     * @return void
     */
    public function loadCacheData(): void
    {
        ['data' => $this->data, 'time' => $this->time] = json_decode(
            file_get_contents($this->cacheFile),
            associative: true
        );
    }

    /**
     * @param $updateTime
     * @return $this
     */
    public function saveCache($updateTime = false): static
    {
        file_put_contents(
            $this->cacheFile,
            json_encode(
                [
                    'data' => $this->data,
                    'time' => $updateTime ? time() : $this->time
                ]
            )
        );
        return $this;
    }

    /**
     * @return $this
     */
    public function deleteCache(): static
    {
        $this->data = [];
        if (is_file($this->cacheFile)) {
            unlink($this->cacheFile);
        }
        return $this;
    }

    /**
     * @return array
     */
    public function getData(): array
    {
        return $this->data;
    }

    /**
     * @param $key
     * @return mixed
     */
    public function getValueByKey($key): mixed
    {
        return array_key_exists($key, $this->data) ? $this->data[$key] : null;
    }

    /**
     * @param $key
     * @param $value
     * @return $this
     */
    public function setValueByKey($key, $value): static
    {
        $this->data[$key] = $value;
        return $this;
    }

    public function getTime(): int
    {
        return $this->time;
    }
}
