<?php


namespace Mainto\RpcServer\RpcUtil\Tool;


use Mainto\RpcServer\Exceptions\RpcLockException;
use Mainto\RpcServer\Exceptions\RpcRuntimeException;
use Throwable;

class RpcMutex {
    /**
     * @var string
     */
    private string $lockName;
    /**
     * @var null|int|bool
     */
    private $lockPassword = null;

    private static ?RpcMutexProviderInterface $mutexProvider = null;

    /**
     * RpcLock constructor.
     * @param $lockName
     */
    private function __construct (string $lockName) {
        if (self::$mutexProvider === null) {
            if (app()->has(RpcMutexProviderInterface::class)) {
                self::$mutexProvider = app(RpcMutexProviderInterface::class);
            } else {
                throw new RpcLockException("please register rpc mutex provider at first");
            }
        }

        $this->lockName = $lockName;
    }

    /**
     * 获取一个锁
     * @param $lockName
     * @return RpcMutex
     */
    public static function getLock ($lockName) {
        return new RpcMutex($lockName);
    }

    /**
     * 对接下来的调用进行锁定
     * @param callable $callback
     * @param int $expireTime
     * @return mixed
     * @throws Throwable
     */
    public function synchronized (callable $callback, int $expireTime = 600) {
        if ($this->tryLock($expireTime)) {
            try {
                return $callback();
            } catch (Throwable $e) {
                throw $e;
            } finally {
                $this->unlock();
            }
        } else {
            throw new RpcLockException("server busy, try it later", 0xF0106801);
        }
    }

    /**
     * 尝试加锁
     * @param int $expireTime 自动解锁时间
     * @return bool
     */
    public function tryLock (int $expireTime = 86400): bool {
        $lockPassword = self::$mutexProvider::getLock($this->lockName, $expireTime);
        if ($lockPassword === false) {
            return false;
        }

        $this->lockPassword = $lockPassword;
        return true;
    }

    /**
     * 对当前操作进行解锁
     */
    public function unlock () {
        if ($this->lockPassword === null) {
            throw new RpcRuntimeException("try to unlock of unlocked RpcMutex");
        }

        if (self::$mutexProvider::unlock($this->lockName, $this->lockPassword)) {
            $this->lockPassword = null;
        } else {
            throw new RpcRuntimeException("try to unlock of expired RpcMutex");
        }
    }
}