package com.sonic.common.ratelimit;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import com.google.common.base.Splitter;
import com.google.common.base.Strings;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.ToString;

public interface RateLimiter {
    /**
     * 尝试获得锁, 获取失败则返回Optional.empty()
     * 如果获取锁成功. 则返回Optional<Permit>. 同时计数器增加
     * Permit支持取消
     *
     * @param value 业务标识
     * @return
     */
    Optional<Permit> tryAcquire(Object value);

    /**
     * 获取窗口类型
     *
     * @return
     */
    WindowType getWindowType();

    class Permit {
        private List<Runnable> cancelRunners;

        @Builder
        public Permit(List<Runnable> cancelRunners) {
            Objects.requireNonNull(cancelRunners);
            this.cancelRunners = cancelRunners;
        }

        public void cancel() {
            if (!cancelRunners.isEmpty()) {
                cancelRunners.stream().forEach(e -> e.run());
            }
        }
    }

    @Getter
    @AllArgsConstructor
    enum WindowType {
        /**
         * 固定窗口, 窗口范围: start = visitTim, end = start + WindowDuration
         */
        FIXED("f"),
        /**
         * 固定窗口, 窗口范围: start = currentMillis/WindowDuration, end = currentMillis/WindowDuration
         */
        FIXED_BUCKET("fb"),
        /**
         * 滑动窗口, 窗口范围: start = currentMillis - WindowDuration, end = currentMillis
         */
        SLIDING("s");

        //减少redisKey长度
        private final String shortName;
    }

    /**
     * 限流规则
     * <pre>
     *     seconds: 窗口时长
     *     permits: 允许发放的令牌数量
     * </pre>
     */
    @Data
    @Builder
    @AllArgsConstructor
    @ToString
    class LimitRule {
        long seconds;
        int permits;

        public boolean isValid() {
            return seconds > 0 && permits > 0;
        }

        /**
         * 根据约定规则创建Rules
         * eg: express 10/1,20/1... 代表10秒1次 & 20秒1次...
         * note: seconds不可重复
         */
        public static List<LimitRule> fromExpression(String expression) {
            if (Strings.isNullOrEmpty(expression)) {
                return Collections.emptyList();
            }
            Map<String, String> rulesMap = Splitter.on(",")
                    .omitEmptyStrings()
                    .trimResults()
                    .withKeyValueSeparator("/")
                    .split(expression);

            return rulesMap.entrySet().stream()
                    .map(e -> LimitRule.builder()
                            .seconds(Long.parseLong(e.getKey()))
                            .permits(Integer.parseInt(e.getValue()))
                            .build())
                    .collect(Collectors.toList());
        }
    }
}
