/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.shenyu.loadbalancer.spi;

import org.apache.shenyu.loadbalancer.entity.Upstream;
import org.apache.shenyu.spi.Join;

import java.security.SecureRandom;
import java.util.List;

/**
 * random algorithm impl.
 */
@Join
public class RandomLoadBalancer extends AbstractLoadBalancer {

    private static final SecureRandom RANDOM = new SecureRandom();

    @Override
    public Upstream doSelect(final List<Upstream> upstreamList, final String ip) {
        int totalWeight = calculateTotalWeight(upstreamList);
        boolean sameWeight = isAllUpStreamSameWeight(upstreamList);
        if (totalWeight > 0 && !sameWeight) {
            return random(totalWeight, upstreamList);
        }
        // If the weights are the same or the weights are 0 then random.
        return random(upstreamList);
    }

    private boolean isAllUpStreamSameWeight(final List<Upstream> upstreamList) {
        boolean sameWeight = true;
        int length = upstreamList.size();
        for (int i = 0; i < length; i++) {
            int weight = getWeight(upstreamList.get(i));
            if (i > 0 && weight != getWeight(upstreamList.get(i - 1))) {
                // Calculate whether the weight of ownership is the same.
                sameWeight = false;
                break;
            }
        }
        return sameWeight;
    }

    private int calculateTotalWeight(final List<Upstream> upstreamList) {
        // total weight.
        int totalWeight = 0;
        for (Upstream upstream : upstreamList) {
            int weight = getWeight(upstream);
            // Cumulative total weight.
            totalWeight += weight;
        }
        return totalWeight;
    }

    private Upstream random(final int totalWeight, final List<Upstream> upstreamList) {
        // If the weights are not the same and the weights are greater than 0, then random by the total number of weights.
        int offset = RANDOM.nextInt(totalWeight);
        // Determine which segment the random value falls on
        for (Upstream upstream : upstreamList) {
            offset -= getWeight(upstream);
            if (offset < 0) {
                return upstream;
            }
        }
        return upstreamList.get(0);
    }

    private Upstream random(final List<Upstream> upstreamList) {
        return upstreamList.get(RANDOM.nextInt(upstreamList.size()));
    }
}
