package mhtree;

import java.util.BitSet;
import java.util.function.BiPredicate;

public class Utils {

    /**
     * Returns index from {@code validIndices} with the maximum value in {@code distanceMatrix}.
     *
     * @param distanceMatrix a distance matrix
     * @param validIndices   valid indices in {@code distanceMatrix}
     * @return index from {@code validIndices} with the maximum value in {@code distanceMatrix}
     */
    public static int maxDistanceIndex(float[][] distanceMatrix, BitSet validIndices) {
        float maxDistance = Float.MIN_VALUE;
        int furthestIndex = validIndices.nextSetBit(0);

        while (true) {
            float[] distances = distanceMatrix[furthestIndex];
            int candidateIndex = maxDistanceIndex(distances, validIndices);

            if (!(distances[candidateIndex] > maxDistance)) {
                return furthestIndex;
            }

            maxDistance = distances[candidateIndex];
            furthestIndex = candidateIndex;
        }
    }

    /**
     * Returns index from {@code validIndices} with the minimal distance in {@code distances}.
     *
     * @param distances    an array of distances
     * @param validIndices valid indices in {@code distances}
     * @return index from {@code validIndices} with the minimal distance in {@code distances}
     */
    public static int minDistanceIndex(float[] distances, BitSet validIndices) {
        return getDistanceIndex(distances, validIndices, (minDistance, newDistance) -> minDistance > newDistance);
    }

    /**
     * Returns index from {@code validIndices} with the maximal distance in {@code distances}.
     *
     * @param distances    an array of distances
     * @param validIndices valid indices in {@code distances}
     * @return index from {@code validIndices} with the maximal distance in {@code distances}.
     */
    private static int maxDistanceIndex(float[] distances, BitSet validIndices) {
        return getDistanceIndex(distances, validIndices, (maxDistance, newDistance) -> maxDistance < newDistance);
    }

    /**
     * Returns index from {@code validIndices} with the specific distance in {@code distances},
     * where the distance is set by the {@code comparator}.
     *
     * @param distances    an array of distances
     * @param validIndices valid indices in {@code distances}
     * @param comparator   specifies when to update the value of distance and index
     * @return index from {@code validIndices} with the specific distance in {@code distances}
     */
    private static int getDistanceIndex(float[] distances, BitSet validIndices, BiPredicate<Float, Float> comparator) {
        int index = -1;
        float minDistance = Float.MAX_VALUE;

        for (int i = validIndices.nextSetBit(0); i >= 0; i = validIndices.nextSetBit(i + 1)) {
            if (index == -1 || comparator.test(minDistance, distances[i])) {
                minDistance = distances[i];
                index = i;
            }
        }

        return index;
    }
}
