/*
 * 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.preprocess;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import com.scytl.ov.mixing.commons.beans.IntPair;
import com.scytl.ov.mixing.commons.tools.IntPairCalculator;

public class Partitioner {

    private static final int CERTAINTY = 100;

    private final int minimumBatchSize;
    
    /**
     * Constructor.
     * 
     * @param minimumBatchSize
     */
    public Partitioner(int minimumBatchSize) {
        if (minimumBatchSize <= 0 || minimumBatchSize == Integer.MAX_VALUE) {
            throw new IllegalArgumentException(
                "The given minimum size must be higher or equal than one and lower than the maximum value of an "
                        + "integer (int).");
        }
        this.minimumBatchSize = minimumBatchSize;
    }

    /**
     * Computes the partitions of {@code n} according to the minimum batch size.
     *
     * @param number
     *            The number to be partitioned.
     * @return a map with the computed partitions: [ partition size , number of partitions ]
     */
    public List<Integer> generatePartitionsOfMinimumSize(final int number) {

        if (number < 0) {
            throw new IllegalArgumentException("The given number to be partitioned must be higher or equal than one.");
        }

        final List<Integer> partitions = new ArrayList<>();

        if (number < (2 * minimumBatchSize)) {

            partitions.add(number);

        } else {

            final int fullPartitions = getFullPartitions(number);

            for (int i = 0; i < fullPartitions; i++) {
                partitions.add(minimumBatchSize);
            }

            final int partialPartition = getPartialPartition(number);

            if (partialPartition != 0) {

                final BigInteger bi = BigInteger.valueOf((long)minimumBatchSize + partialPartition);

                final int partitionsSize = partitions.size();

                if (bi.isProbablePrime(CERTAINTY)) {

                    final IntPairCalculator twoAddendsCalculator = new IntPairCalculator();

                    final List<IntPair> twoAddendPartitions =
                        twoAddendsCalculator.computeTwoAddendsCombinations(partialPartition);

                    int left = 0;
                    int right = 0;

                    for (final IntPair partition : twoAddendPartitions) {

                        if (partitionIsNotPrimeWhenAddedToTheMinimumSize(partition)) {

                            left = partition.getLeft();

                            right = partition.getRight();

                        }
                    }

                    partitions.set(partitionsSize - 1, minimumBatchSize + left);
                    partitions.set(partitionsSize - 2, minimumBatchSize + right);

                } else {

                    partitions.set(partitionsSize - 1, minimumBatchSize + partialPartition);

                }
            }
        }

        return partitions;

    }

    private boolean partitionIsNotPrimeWhenAddedToTheMinimumSize(final IntPair partition) {
        return !isPrimeWhenAddedToTheMinimumSize(partition.getLeft())
            && !isPrimeWhenAddedToTheMinimumSize(partition.getRight());
    }

    private boolean isPrimeWhenAddedToTheMinimumSize(final Integer value) {

        return BigInteger.valueOf(minimumBatchSize + Long.valueOf(value)).isProbablePrime(CERTAINTY);
    }

    private int getPartialPartition(final int number) {
        return number % minimumBatchSize;
    }

    private int getFullPartitions(final int number) {
        return (int) Math.floor((double) number / minimumBatchSize);
    }

}
