package com.bcxin.identify.api.common.util;

import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * 漏斗限流
 *
 * @author zxf
 * @create 2022-05-18 18:03
 */
@Component
public class FunnelRateLimiter {
    // 用一个 map 保存不同操作对应的漏斗
    private final Map<String, Funnel> funnels = new HashMap<>();

    private static Integer millis = 10;

    /**
     * 计算某个用户的某个动作是否被允许
     *
     * @param key      标识
     * @param capacity    漏斗容量
     * @param leakingRate 漏水速率
     * @return 是否被允许
     */
    public boolean isActionAllowed(String key, int capacity, float leakingRate) {
        Funnel funnel = funnels.get(key);
        // 如果对应的漏斗不存在，初始化漏斗
        if (funnel == null) {
            funnel = new Funnel(capacity, leakingRate);
            funnels.put(key, funnel);
        }
        // 向漏斗中灌水，单个请求占据容量为 1
        return funnel.watering(1);
    }

    public int changeMillis(int mil){
        millis = mil;
        return millis;
    }

    /**
     * 定义一个类，表示漏斗
     */
    static class Funnel {
        // 漏斗容量
        private int capacity;
        // 流出速率
        private float leakingRate;
        // 漏斗剩余空间
        private int leftQuota;
        // 上一次执行漏水的时间
        private long leakingTs;

        /**
         * 构造函数
         *
         * @param capacity    容量
         * @param leakingRate 漏水速率
         */
        public Funnel(int capacity, float leakingRate) {
            this.capacity = capacity;
            this.leakingRate = leakingRate;
            // 初始化时，剩余空间等于漏斗容量
            this.leftQuota = capacity;
            // 上一次漏水时间指定为初始化时间
            this.leakingTs = System.currentTimeMillis();
        }

        /**
         * 腾出漏斗空间（每一次灌水之前都要触发一次）
         */
        public void makeSpace() {
            long nowTs = System.currentTimeMillis();
            // 计算上一次漏水时间和当前时间的时间差
            long deltaTs = (nowTs - leakingTs)/millis;
            // 计算应该腾出的空间
            int deltaQuota = (int) (deltaTs * leakingRate);
            System.out.println(deltaTs+"-"+deltaQuota);
            // 间隔时间太长，整数数字过大溢出
            if (deltaQuota < 0) {
                this.leftQuota = capacity;
                this.leakingTs = nowTs;
                return;
            }
            // 腾出的空间太小，最小单位是 1
            if (deltaQuota < 1) {
                return;
            }
            // 更新剩余空间
            this.leftQuota += deltaQuota;
            // 更新上一次漏水时间为当前时间
            this.leakingTs = nowTs;
            if (this.leftQuota > this.capacity) {
                this.leftQuota = this.capacity;
            }
        }

        /**
         * 向漏斗中灌水
         *
         * @param quota 固定额度
         * @return 是否灌水成功
         */
        public boolean watering(int quota) {
            // 腾出空间
            makeSpace();
            // 如果剩余空间大于要灌入的空间，则更新剩余空间，灌水成功
            if (this.leftQuota >= quota) {
                this.leftQuota -= quota;
                return true;
            }
            // 否则灌水失败
            return false;
        }
    }
}
