package org.gnunet.voting;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

/**
 * Miscellaneous helper functions.
 *
 * @author Florian Dold
 */
public class CryptoUtil {
    public static final Random random = new Random();


    /**
     * Return a random BigInteger not less than 'min' and not greater than 'max' with uniform distribution.
     *
     * @param min    the least value that may be generated
     * @param max    the greatest value that may be generated
     * @return a random BigInteger value in the range [min,max]
     */
    public static BigInteger createRandomInRange(BigInteger min,
                                                 BigInteger max) {
        int cmp = min.compareTo(max);
        if (cmp >= 0) {
            if (cmp > 0) {
                throw new IllegalArgumentException("'min' may not be greater than 'max'");
            }

            return min;
        }

        if (min.bitLength() > max.bitLength() / 2) {
            return createRandomInRange(BigInteger.ZERO, max.subtract(min)).add(min);
        }

        for (int i = 0; i < 1000; ++i) {
            BigInteger x = new BigInteger(max.bitLength(), random);
            if (x.compareTo(min) >= 0 && x.compareTo(max) <= 0) {
                return x;
            }
        }

        // fall back to a faster (restricted) method
        // (using only this distribution would lead to a non-uniform distribution, see the BigInteger constructor)
        return new BigInteger(max.subtract(min).bitLength() - 1, random).add(min);
    }



    /**
     * Evaluate a polynomial over Zp*. Uses Horner's scheme.
     *
     * @param coeffs coefficients of the polynomial, where coeffs[i] is the coefficient of x^i
     * @param x the polynomial is evaluated at this value
     * @param p what group are we operating in?
     * @return the result of evaluating the polynomial at x
     */
    public static BigInteger evaluatePolynomial(BigInteger[] coeffs, BigInteger x, BigInteger p) {
        BigInteger z = BigInteger.ZERO;
        for (int i = 0; i < coeffs.length; ++i) {
            // z <- zx + c
            z = z.multiply(x).add(coeffs[coeffs.length - i - 1]);
        }
        return z;
    }

    public static BigInteger hash(BigInteger... x) {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-512");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("no SHA-512 available");
        }

        for (BigInteger v : x) {
            md.update(v.toByteArray());
        }

        return new BigInteger(md.digest());
    }

}
