<?php

/**
 * MemoryCache.php
 * 文件描述
 * Created On 2022/1/17
 * @author yuanb yuanbo0x@gmail.com
 */

namespace Library;

use Exception;
use Library\Common\Singletons;

class MemoryCache
{
    use Singletons;

    public const MAX_KEY = 2100000000;

    private string $errorInfo = "";

    protected array $shmList = array();

    private int $locked = 0;
    private $sem = null;

    public function initCache($cacheId): bool
    {
        if (array_key_exists($cacheId, $this->shmList)) {
            return true;
        }
        $shmId = shm_attach($cacheId);
        if ($shmId === false) {
            return false;
        }
        $this->shmList[$cacheId] = $shmId;
        return true;
    }

    public function createCache($cacheId, $size): bool
    {
        if (array_key_exists($cacheId, $this->shmList)) {
            return true;
        }
        $shmId = shm_attach($cacheId, $size, 0644);
        if ($shmId === false) {
            return false;
        }
        $this->shmList[$cacheId] = $shmId;
        return true;
    }

    public function closeCache($cacheId): bool
    {
        if (!array_key_exists($cacheId, $this->shmList)) {
            return false;
        }
        $shmId = $this->shmList[$cacheId];
        if (shm_detach($shmId) == false) {
            return false;
        }
        unset($this->shmList[$cacheId]);
        return true;
    }

    //删除共享内存
    public function removeCache($cacheId): bool
    {
        $shmId = shm_attach($cacheId);
        if ($shmId === false) {
            return false;
        }
        return shm_remove($shmId);
    }

    public function setCache($key, $value, $cacheId): bool
    {
        if ($key > self::MAX_KEY) {
            $this->errorInfo = $key . "  Key More than Max";
            return false;
        }
        if ($cacheId == 0) {
            $this->errorInfo = "cacheId null !";
            return false;
        }
        if (!array_key_exists($cacheId, $this->shmList)) {
            $this->errorInfo = "share memory cacheId error " . $cacheId;
            return false;
        }
        $shmId = $this->shmList[$cacheId];
        if (shm_put_var($shmId, $key, $value) == false) {
            $this->errorInfo = "share memory save Error cache" . $cacheId;
            return false;
        }
        return true;
    }

    public function getCache($key, $cacheId): ?array
    {
        if ($key > self::MAX_KEY) {
            $this->errorInfo = $key . "  Key More than Max";
            return null;
        }
        if ($cacheId == 0) {
            $this->errorInfo = "cacheId null !";
            return null;
        }
        $shmId = $this->shmList[$cacheId];
        $data = null;
        if (shm_has_var($shmId, $key) == false) {
            $this->errorInfo = sprintf("cacheId;[%s]  key:[%s] not exist!!", $cacheId, $key);
            return null;
        }
        try {
            $data = shm_get_var($shmId, $key);
        } catch (Exception $e) {
            $this->errorInfo = $key . "  Not Exist" . "exception: " . $e->getMessage();
        }
        return $data;
    }

    public function getLastError(): string
    {
        return $this->errorInfo;
    }

    //获取uid Index并加1
    public function getUidIndexWithLock($key, $cacheId): ?int
    {
        $shmId = $this->shmList[$cacheId];
        $data = null;
        if (shm_has_var($shmId, $key) == false) {
            $this->errorInfo = sprintf("cacheId;[%s]  key:[%s] not exist!!", $cacheId, $key);
            return null;
        }
        if ($this->lock() > 0) {
            $data = shm_get_var($shmId, $key);
            $index = $data["Index"] ?? 0;
            $index++;
            if ($index > 32768) {
                $index = 1;
            }
            $data["Index"] = $index;
            if (shm_put_var($shmId, $key, $data) == false) {
                $this->free();
                $this->errorInfo = "share memory save Error cache" . $cacheId;
                return null;
            }
            $this->free();
            return $index;
        }
        return null;
    }

    private function lock(): int
    {
        if ($this->locked == 0) {
            $result = sem_acquire($this->getSem());
            if (!$result) {
                return 0;
            }
        }
        return ++$this->locked;
    }

    private function free(): int
    {
        if ($this->locked == 1) {
            $result = sem_release($this->getSem());
            if (!$result) {
                return 0;
            }
        }
        return --$this->locked;
    }

    private static function getIdentifier(): int
    {
        //$project_id Project identifier. This must be a one character string.
        return ftok(__FILE__, "a");
    }

    private function getSem()
    {
        if (is_null($this->sem)) {
            $this->sem = sem_get(self::getIdentifier());
        }
        return $this->sem;
    }

    //出现异常后,释放锁
    public function releaseLock()
    {
        $this->free();
    }
}
