/*
 * Copyright 2008-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.hasor.rsf.address.route.flowcontrol.speed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 描述：线程安全的令牌桶限流器，时间窗刷新误差为毫秒级。 定义有效的限流器需要满足：
 * <ol>
 * <li>速率rate > 0，单位为次数/秒
 * <li>时间窗timeWindow >= 1，单位为毫秒
 * <li>峰值peak >= rate * timeWindow / 1000.0
 * </ol>
 */
public class QoSBucket {
    protected            Logger        logger             = LoggerFactory.getLogger(getClass());
    private static final int           DEFAULT_RATE       = 50;
    private static final int           DEFAULT_PEAK       = 100;
    private static final int           DEFAULT_TIMEWINDOW = 1000;
    private              int           rate;                                                    // 稳态中，每秒允许的调用次数
    private              int           peak;                                                    // 突发调用峰值的上限，即令牌桶容量
    private              int           timeWindow;                                              // 令牌桶刷新最小间隔，单位毫秒
    private              AtomicInteger tokens;                                                  // 当前可用令牌数量
    private volatile     long          lastRefreshTime;                                         // 下一次刷新令牌桶的时间
    private volatile     double        leftDouble;

    public QoSBucket() {
        this(DEFAULT_RATE, DEFAULT_PEAK, DEFAULT_TIMEWINDOW);
    }

    public QoSBucket(int rate, int peak, int timeWindow) {
        this.rate = rate;
        this.peak = peak;
        this.timeWindow = timeWindow;
        double initialToken = rate * timeWindow / 1000d;
        //初始的token为零不合理， 改为1。
        this.tokens = initialToken >= 1 ? new AtomicInteger((int) initialToken) : new AtomicInteger(1);
        //增加此保存值，是为了double转int时候的不精确；如果不累及这个误差，累计的结果会非常大（修正参数）
        this.leftDouble = initialToken - Math.floor(initialToken);
        this.lastRefreshTime = System.currentTimeMillis();
    }

    /** 检查令牌前，首先更新令牌数量 */
    public boolean check() {
        long now = System.currentTimeMillis();
        if (now > lastRefreshTime + timeWindow) {// 尝试更新令牌数量
            int currentValue = tokens.get();
            double interval = (now - lastRefreshTime) / 1000d;
            double addedDouble = interval * rate;
            int added = (int) addedDouble; // 最大值为Integer.MAX_VALUE
            if (added > 0) {
                double addedPlusDouble = leftDouble + (addedDouble - added);
                int addPlus = (int) addedPlusDouble;
                added += addPlus;
                int newValue = currentValue + added;
                newValue = (newValue > currentValue && newValue < peak) ? newValue : peak;
                if (tokens.compareAndSet(currentValue, newValue)) {
                    lastRefreshTime = now;// 更新成功后，设置新的刷新时间
                    leftDouble = addedPlusDouble - addPlus;
                    if (logger.isDebugEnabled()) {
                        logger.debug("[QoSBucket] Updated done: [{}] -> [{}], refresh time: {}.", currentValue, newValue, now);
                    }
                }
            }
        }
        int value = tokens.get();// 尝试获得一个令牌
        boolean flag = false; // 是否获得到一个令牌
        while (value > 0 && !flag) {
            flag = tokens.compareAndSet(value, value - 1);
            value = tokens.get();
        }
        if (logger.isDebugEnabled() && !flag) {
            logger.debug("QoSBucket: get token failed, tokens[" + tokens.get() + "]");
        }
        return flag;
    }

    @Override
    public String toString() {
        return "QoSBucket [tokens=" + tokens + ", rate=" + rate + ", peak=" + peak + ", timeWindow=" + timeWindow + "]";
    }

    /**
     * 限流器有效性验证。限流器的配置必须满足以下条件：
     * <ol>
     * <li>速率rate、峰值peak配置为大于0
     * <li>时间窗timeWindow不小于1
     * <li>峰值不小于速率与时间窗的乘积
     * </ol>
     * @return true/false
     */
    public boolean validate() {
        if (rate <= 0 || peak <= 0 || timeWindow < 1) {
            return false;
        }
        if (peak < (rate * timeWindow / 1000F)) {
            return false;
        }
        return true;
    }
}