<?php

namespace Module\Vendor\Util;

use ModStart\Core\Dao\ModelUtil;
use ModStart\Core\Util\RandomUtil;
use ModStart\Core\Util\RedisUtil;
use Module\Vendor\Model\Atomic;

/**
 * Class AtomicUtil
 *
 * product once
 * consume some times
 *
 * example:
 * - captcha error count
 */
class AtomicUtil
{
    private static function autoCleanDB()
    {
        if (RandomUtil::percent(20)) {
            ModelUtil::model(Atomic::class)->where('expire', '<', time())->delete();
        }
    }


    /**
     * 生产一个原子值，生产后可以使用 consume 消费
     * @param $name string
     * @param $value int
     * @param $expire int
     */
    public static function produce($name, $value, $expire = 3600)
    {
        if (RedisUtil::isEnable()) {
            $hash = "Atomic:$name";
            RedisUtil::set($hash, $value);
            RedisUtil::expire($hash, $expire);
        } else {
            if (ModelUtil::exists(Atomic::class, ['name' => $name])) {
                ModelUtil::update(Atomic::class, ['name' => $name], ['value' => $value, 'expire' => time() + $expire]);
            } else {
                ModelUtil::insertIgnoreUnique(Atomic::class, ['name' => $name, 'value' => $value, 'expire' => time() + $expire]);
            }
            self::autoCleanDB();
        }
    }

    /**
     * 消费一个原子值
     * @param $name string
     * @return bool 是否成功
     */
    public static function consume($name)
    {
        if (RedisUtil::isEnable()) {
            $hash = "Atomic:$name";
            if (RedisUtil::decr($hash) >= 0) {
                return true;
            }
            return false;
        } else {
            self::autoCleanDB();
            ModelUtil::transactionBegin();
            $atomic = ModelUtil::getWithLock(Atomic::class, ['name' => $name]);
            if (empty($atomic)) {
                ModelUtil::transactionCommit();
                return false;
            }
            if ($atomic['expire'] < time() || $atomic['value'] < 0) {
                ModelUtil::delete(Atomic::class, ['name' => $name]);
                ModelUtil::transactionCommit();
                return false;
            }
            ModelUtil::update(Atomic::class, ['name' => $name], ['value' => $atomic['value'] - 1]);
            ModelUtil::transactionCommit();
            return true;
        }
    }

    /**
     * 移除一个原子值
     * @param $name
     */
    public static function remove($name)
    {
        if (RedisUtil::isEnable()) {
            $hash = "Atomic:$name";
            RedisUtil::delete($hash);
        } else {
            ModelUtil::delete(Atomic::class, ['name' => $name]);
        }
    }

    /**
     * 请求一个互斥锁
     * acquire 后必须 release
     * @param $name string
     * @param $expire int
     * @return bool 是否成功
     */
    public static function acquire($name, $expire = 30)
    {
        if (RedisUtil::isEnable()) {
            $key = "Atomic:$name";
            if (RedisUtil::setnx($key, time() + $expire)) {
                RedisUtil::expire($key, $expire);
                return true;
            }
            $ts = RedisUtil::get($key);
            if ($ts < time()) {
                RedisUtil::delete($key);
                return self::acquire($name, $expire);
            }
            return false;
        } else {
            self::autoCleanDB();
            ModelUtil::transactionBegin();
            $atomic = ModelUtil::getWithLock(Atomic::class, ['name' => $name]);
            $ts = time() + $expire;
            if (empty($atomic)) {
                ModelUtil::insert(Atomic::class, [
                    'name' => $name,
                    'value' => 1,
                    'expire' => $ts
                ]);
                ModelUtil::transactionCommit();
                return true;
            }
            if ($atomic['expire'] < time()) {
                ModelUtil::update(Atomic::class, ['name' => $name], [
                    'value' => 1,
                    'expire' => $ts
                ]);
                ModelUtil::transactionCommit();
                return true;
            }
            ModelUtil::transactionCommit();
            return false;
        }
    }

    /**
     * 释放一个互斥锁
     * @param $name string
     */
    public static function release($name)
    {
        if (RedisUtil::isEnable()) {
            $key = "Atomic:$name";
            RedisUtil::delete($key);
        } else {
            ModelUtil::delete(Atomic::class, ['name' => $name]);
        }
    }
}
