<?php
namespace Api\PhpUtils\Cache;

use Yaf\Application;
use Api\PhpUtils\Log\FileLog;

/**
 * ApcuUtil 工具类
 *
 * apcu扩展不支持前缀，在此类中做统一的前缀管理
 * 注:
 * 1,目前使用apcu4.0,5.0需php7支持(apcu_entry)
 * 2,所有实现的方法在原apcu函数基础上添加了prefix参数,并增加appid做为统一的前缀，具体请查看php手册
 * 3,因apcu_add及apcu_store等方法批量及单个操作参数完全不同,此处将其分离成两个函数
 *
 * @date 2021-03-29
 */
class ApcuUtil
{
    const PREFIX_NONE = '';
    
    const EXPIRE_NEVER              = 0;
    const EXPIRE_AFTER_FIVE_SECONDS = 5;
    const EXPIRE_AFTER_ONE_MINUTE   = 60;
    const EXPIRE_AFTER_FIVE_MINUTES = 300;
    const EXPIRE_AFTER_HALF_HOUR    = 1800;
    const EXPIRE_AFTER_ONE_HOUR     = 3600;
    const EXPIRE_AFTER_SIX_HOURS    = 21600;
    const EXPIRE_AFTER_ONE_DAY      = 86400;
    const EXPIRE_AFTER_THREE_DAYS   = 259200;
    const EXPIRE_AFTER_FIFTEEN_DAYS = 1296000;
    const EXPIRE_AFTER_ONE_MONTH    = 2592000;
    
    /**
     * 为指定key添加前缀
     *
     * @param [str] $prefix            
     * @param [mixed] $key            
     */
    public static function add_prefix($prefix, $key)
    {
        $appid = Application::app()->getConfig()->get("appid");
        $prefix = $appid . ':' . $prefix;
        
        if (is_array($key)) {
            foreach ($key as $key_key => $key_value) {
                $key[$key_key] = $prefix . $key_value;
            }
        } else {
            $key = $prefix . $key;
        }
        return $key;
    }

    /**
     * 移除apcu批量获取结果中key的前缀,当单个key时原样返回
     *
     * @param [str] $prefix
     * @param [mixed] $key
     * @param [mixed] $apc_result
     * @return [mixed]
     */
    public static function remove_result_prefix($prefix, $key, $apc_result)
    {
        $appid = Application::app()->getConfig()->get("appid");
        $prefix = $appid . ':' . $prefix;
        
        if (is_array($key)) {
            $result = array();
            if (! is_array($apc_result)) {
                return $apc_result;
            }
            foreach ($apc_result as $apc_key => $apc_value) {
                $result[substr($apc_key, strlen($prefix))] = $apc_value;
            }
            return $result;
        }
        return $apc_result;
    }

    /**
     * @link http://php.net/manual/zh/function.apcu-fetch.php
     * 
     * @return mixed The stored variable or array of variables on success; false on failure
     */
    public static function apcu_fetch($prefix, $key, &$success = true)
    {
        $perfix_key = self::add_prefix($prefix, $key);
        $apc_result = apcu_fetch($perfix_key, $success);
        return self::remove_result_prefix($prefix, $key, $apc_result);
    }

    /**
     * @link http://php.net/manual/zh/function.apcu-store.php
     */
    public static function apcu_store_one($prefix, $key, $value, $ttl = 0)
    {
        $perfix_key = self::add_prefix($prefix, $key);
        return apcu_store($perfix_key, $value, $ttl);
    }

    /**
     * @link http://php.net/manual/zh/function.apcu-store.php
     */
    public static function apcu_store_multi($prefix, $values, $unused = NULL, $ttl = 0)
    {
        if (! empty($values)) {
            $apc_to_set = array();
            foreach ($values as $key => $value) {
                $perfix_key = self::add_prefix($prefix, $key);
                $apc_to_set[$perfix_key] = $value;
            }
            return apcu_store($apc_to_set, $unused, $ttl);
        }
        return false;
    }

    /**
     * @link http://php.net/manual/zh/function.apcu-add.php
     */
    public static function apcu_add_one($prefix, $key, $value, $ttl = 0)
    {
        $perfix_key = self::add_prefix($prefix, $key);
        return apcu_add($perfix_key, $value, $ttl);
    }

    /**
     * @link http://php.net/manual/zh/function.apcu-add.php
     */
    public static function apcu_add_multi($prefix, $values, $unused = NULL, $ttl = 0)
    {
        if (! empty($values)) {
            $apc_to_add = array();
            foreach ($values as $key => $value) {
                $perfix_key = self::add_prefix($prefix, $key);
                $apc_to_add[$perfix_key] = $value;
            }
            return apcu_add($apc_to_add, $unused, $ttl);
        }
        return false;
    }

    /**
     * @link http://php.net/manual/zh/function.apcu-delete.php
     */
    public static function apcu_delete($prefix, $key)
    {
        $perfix_key = self::add_prefix($prefix, $key);
        return apcu_delete($perfix_key);
    }

    /**
     * @link http://php.net/manual/zh/function.apcu-exists.php
     */
    public static function apcu_exists($prefix, $key)
    {
        $perfix_key = self::add_prefix($prefix, $key);
        if (is_array($key)) {
            $apc_result = apcu_exists($perfix_key);
            $result = array();
            if (empty($apc_result)) {
                return $result;
            }
            return self::remove_result_prefix($prefix, $key, $apc_result);
        } else {
            return apcu_exists($perfix_key);
        }
    }

    /**
     * @link http://php.net/manual/zh/function.apcu-inc.php
     */
    public static function apcu_inc($prefix, $key, $step = 1, &$success = true, $ttl = 0)
    {
        $perfix_key = self::add_prefix($prefix, $key);
        return apcu_inc($perfix_key, $step, $success, $ttl);
    }

    /**
     * @link http://php.net/manual/zh/function.apcu-dec.php
     */
    public static function apcu_dec($prefix, $key, $step = 1, &$success = true, $ttl = 0)
    {
        $perfix_key = self::add_prefix($prefix, $key);
        return apcu_dec($perfix_key, $step, $success, $ttl);
    }

    public static function retry_apcu_store_one($prefix, $key, $value, $ttl = 0)
    {
        $retry = 0;
        do {
            $res = self::apcu_store_one($prefix, $key, $value, $ttl);
        } while (!$res && ++$retry < 3);
        return $res;
    }

    /**
     * @param $method mixed(array | object_method) 获取信息的方法
     */
    public static function period_get($key, $prefix, $ttl, $lock_prefix, $lock_time, $method)
    {
        $res = self::apcu_fetch($prefix, $key);
        if ($res !== false) {
            if (! isset($res['data']) || ! isset($res['expire_at'])) {
                FileLog::waring('get bad value from apcu', $key . ' ' . $prefix);
                return false;
            }
            
            if ($_SERVER['REQUEST_TIME'] >= $res['expire_at']) {
                // update
                if (self::apcu_add_one($lock_prefix, $key, 1, $lock_time)) {
                    $data = call_user_func($method, $key);
                    if ($data !== false) {
                        $value = array(
                            'data' => $data,
                            'expire_at' => $_SERVER['REQUEST_TIME'] + ceil($ttl / 2)
                        );
                        self::retry_apcu_store_one($prefix, $key, $value, $ttl);
                        return $data;
                    }
                }
            }
            return $res['data'];
        } else {
            // set
            if (self::apcu_add_one($lock_prefix, $key, 1, $lock_time)) {
                $data = call_user_func($method, $key);
                if ($data !== false) {
                    $value = array(
                        'data' => $data,
                        'expire_at' => $_SERVER['REQUEST_TIME'] + ceil($ttl / 2)
                    );
                    self::retry_apcu_store_one($prefix, $key, $value, $ttl);
                    return $data;
                }
            }
            return false;
        }
    }
}
