/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.ov.mixing.commons.tools;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public final class LoopTools {

	/**
	 * Non-public constructor
	 */
	private LoopTools() {
	}
	
	/**
     * Distributes the indexes of an array of the given length in the specified number of segments. <br>
     * For instance, <br>
     * array length 6, amount of segments 2 produces<br>
     * segments [0, 3), [3, 6) <br>
     * array length 5, amount of segments 1 produces<br>
     * segments [0, 5)<br>
     * array length 7, amount of segments 3 produces<br>
     * segments [0, 3), [3, 5), [5, 7)<br>
     * <b>Note:</b> <i>If the array amount of segments is greater than the array length</i>, the method will act as if
     * arrayLength had been also the value given to the rangesCount parameter.<br>
     * That is, array length 3, amount of segments 5 produces list with <br>
     * the segments [0, 1), [1,2), [2,3)<br>
     * 
     * @param arrayLength
     * @param proposedRangesCount
     * @return a List of {@link Range} instances representing the <i>proposedRangesCount</i> indexes of the segments in
     *         the array with length <i>arrayLength</i>
     */
    public static List<Range> asRangesList(final int arrayLength, final int proposedRangesCount) {

        if (proposedRangesCount < 0) {
            throw new IllegalArgumentException("The amount of ranges cannot be negative");
        }

        if (arrayLength < 0) {
            throw new IllegalArgumentException("The length of the array cannot be negative");
        }

        if (proposedRangesCount == 0) {
            return Collections.<Range> emptyList();
        }

        if (arrayLength == 0) {
            return Collections.singletonList(new Range(0, 0));
        }

        int effectiveRangesCount = proposedRangesCount;
        if (arrayLength < proposedRangesCount) {
            effectiveRangesCount = arrayLength;
        }

        if (effectiveRangesCount == 1) {
            return Collections.singletonList(new Range(0, arrayLength));
        }

        final List<Range> ranges = new ArrayList<>(effectiveRangesCount);

        final int rangeMinLength = arrayLength / effectiveRangesCount;
        final int leftovers = arrayLength % effectiveRangesCount;

        for (int k = 0; k < leftovers; k++) {
            final int start = k * (rangeMinLength + 1);
            final int end = start + (rangeMinLength + 1);

            ranges.add(new Range(start, end));
        }

        int previousEnd = 0;
        if (leftovers > 0) {
            previousEnd = ranges.get(ranges.size() - 1).getEnd();
        }
        for (int k = 0; k < effectiveRangesCount - leftovers - 1; k++) {
            final int start = previousEnd + (k * rangeMinLength);
            final int end = start + rangeMinLength;

            ranges.add(new Range(start, end));
        }

        final int lastRangeStart = ranges.get(ranges.size() - 1).getEnd();
        ranges.add(new Range(lastRangeStart, arrayLength));
        return ranges;
    }

    public static class Range {

        final int start;

        final int end;

        /**
         * @param start
         * @param end
         */
        public Range(final int start, final int end) {
            super();
            this.start = start;
            this.end = end;
        }

        /**
         * @return Returns the start.
         */
        public int getStart() {
            return start;
        }

        /**
         * @return Returns the end. Typically used as an excluded upper bound.
         */
        public int getEnd() {
            return end;
        }

        /**
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return "[" + start + "," + end + ")";
        }
    }

}
