package com.easy.api.utils.other;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.List;
import java.util.Random;

/**
 * @author muchi
 */
public class LoadBalancerUtils<T> {

    public static class WeightedItem<T> {
        T item;
        int weight;
        int currentWeight;

        public WeightedItem(T item, int weight) {
            this.item = item;
            this.weight = weight;
            this.currentWeight = 0;
        }

    }

    private final List<WeightedItem<T>> items;
    private int currentIndex = -1;
    private int currentWeight = 0;
    private final int maxWeight;
    private final int gcdWeight;
    private final Random random = new Random();
    private int totalWeight = 0;

    public LoadBalancerUtils(List<WeightedItem<T>> items) {
        this.items = items;
        this.maxWeight = getMaxWeight(items);
        this.gcdWeight = getGcdWeight(items);
        for (WeightedItem<T> item : items) {
            totalWeight += item.weight;
        }
    }

    // 1. 简单轮询
    public T roundRobin() {
        currentIndex = (currentIndex + 1) % items.size();
        return items.get(currentIndex).item;
    }

    // 2. 随机选择
    public T randomSelection() {
        int index = random.nextInt(items.size());
        return items.get(index).item;
    }

    // 3. 加权轮询
    /**
     * 加权轮询算法。
     * <p>该算法的思想是维护一个当前索引和当前权重,每次选择元素时,将当前索引加上 1,当前索引取模 items.size(),同时将当前权重减去 gcdWeight,
     * 当当前索引为 0 时,将当前权重设为 maxWeight,当前权重小于等于 0 时,将当前权重置为 maxWeight。
     * 如果当前元素的权重大于等于当前权重,则返回当前元素,否则继续选择下一个元素。
     *
     * @return 被选中的元素
     */
    public T weightedRoundRobin() {
        while (true) {
            currentIndex = (currentIndex + 1) % items.size();
            if (currentIndex == 0) {
                currentWeight -= gcdWeight;
                if (currentWeight <= 0) {
                    currentWeight = maxWeight;
                }
            }
            if (items.get(currentIndex).weight >= currentWeight) {
                return items.get(currentIndex).item;
            }
        }
    }

    // 4. 平滑加权轮询
    /**
     * 平滑加权轮询算法
     *
     * <p>该算法的思想是给每个元素添加一个 currentWeight 属性,每次选择元素时,将当前元素的 currentWeight 加上其 weight 属性,
     * 当 currentWeight 大于所有元素的最大 weight 时,找到最大的 currentWeight 对应的元素,并将其 currentWeight 减去所有元素的总 weight 值。
     * 这样可以使每个元素被选中的概率与其 weight 成正比。
     *
     * @return 返回被选中的元素,若没有找到,则返回 null
     */
    public T smoothWeightedRoundRobin() {
        WeightedItem<T> selected = null;
        int maxWeight = Integer.MIN_VALUE;
        for (WeightedItem<T> item : items) {
            item.currentWeight += item.weight;
            if (item.currentWeight > maxWeight) {
                maxWeight = item.currentWeight;
                selected = item;
            }
        }
        if (selected != null) {
            selected.currentWeight -= totalWeight;
            return selected.item;
        }
        return null;
    }
    private int getMaxWeight(List<WeightedItem<T>> items) {
        int max = 0;
        for (WeightedItem<T> item : items) {
            max = Math.max(max, item.weight);
        }
        return max;
    }

    private int getGcdWeight(List<WeightedItem<T>> items) {
        int gcd = items.get(0).weight;
        for (WeightedItem<T> item : items) {
            gcd = gcd(gcd, item.weight);
        }
        return gcd;
    }

    private int gcd(int a, int b) {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }


    @Data
    @AllArgsConstructor
    static class MxbcTest {
        private String id;
        private String token;
    }

    public static void main(String[] args) {

        List<WeightedItem<MxbcTest>> servers = List.of(
                new WeightedItem<>(new MxbcTest("1", "权重1"), 5),
                new WeightedItem<>(new MxbcTest("2", "权重2"), 1),
                new WeightedItem<>(new MxbcTest("3", "权重3"), 1)
        );

        LoadBalancerUtils<MxbcTest> loadBalancerUtils = new LoadBalancerUtils<>(servers);
        for (int i = 1; i < 8; i++) {
//            System.out.println("简单轮训第" + i +"次,"+ loadBalancerUtils.roundRobin());
            System.out.println("随机选择" + i +"次,"+ loadBalancerUtils.randomSelection());
//            System.out.println("加权轮训" + i +"次,"+ loadBalancerUtils.weightedRoundRobin());
//            System.out.println("平滑加权轮询" + i +"次,"+ loadBalancerUtils.smoothWeightedRoundRobin());
//            System.out.println("==================================================================");
        }
    }
}