package meerkat.crypto.secretsharing.shamir;

import meerkat.crypto.utils.Arithmetic;
import meerkat.crypto.utils.concrete.Fp;

import java.math.BigInteger;
import java.util.Random;

/**
 * Created by Tzlil on 1/27/2016.
 * an implementation of Shamire's secret sharing scheme
 */
public class SecretSharing{
    /**
     * threshold
     */
    protected final int t;
    /**
     * number of shares
     */
    protected final int n;
    /**
     * a large prime
     */
    protected final BigInteger q;
    /**
     * random polynomial of degree s.t polynomial.evaluate(0) = secret
     */
    protected final Polynomial polynomial;

    /**
     * constructor
     * @param q      a large prime.
     * @param t      threshold. Any t+1 share holders can recover the secret,
     *               but any set of at most t share holders cannot
     * @param n      number of share holders
     * @param zi      secret, chosen from Zq
     * @param random use for generate random polynomial
     */
    public SecretSharing(int t, int n, BigInteger zi, Random random, BigInteger q) {
        this.q = q;
        this.t = t;
        this.n = n;
        this.polynomial = generateRandomPolynomial(zi,random);
    }

    /**
     * @param x
     * @param random
     * @return new Polynomial polynomial of degree t ,such that
     *         1. polynomial(0) = x
     *         2. polynomial coefficients randomly chosen from Zq (except of coefficients[0] = x)
     */
    private Polynomial generateRandomPolynomial(BigInteger x, Random random) {
        BigInteger[] coefficients = new BigInteger[t + 1];
        coefficients[0] = x.mod(q);
        int bits = q.bitLength();
        for (int i = 1 ; i <= t; i++ ){
            coefficients[i] = new BigInteger(bits,random).mod(q);
        }
        return new Polynomial(coefficients,new Fp(q));
    }

    /**
     * @param i in range of [1,...n]
     *
     * @return polynomial.evaluate(i)
     */
    public Polynomial.Point getShare(int i){
        assert (i > 0 && i <= n);
        return new Polynomial.Point(BigInteger.valueOf(i), polynomial);
    }

    /**
     * @param shares - subset of the original shares
     *
     * @return  evaluate of interpolation(shares) at x = 0
     */
    public static BigInteger recoverSecret(Polynomial.Point[] shares, Arithmetic<BigInteger> arithmetic) {
        return recoverPolynomial(shares,arithmetic).evaluate(BigInteger.ZERO);
    }
    /**
     * @param shares - subset of the original shares
     *
     * @return  interpolation(shares)
     */
    public static Polynomial recoverPolynomial(Polynomial.Point[] shares, Arithmetic<BigInteger> arithmetic) {
        return Polynomial.interpolation(shares,arithmetic);
    }

    /**
     * getter
     * @return threshold
     */
    public int getT() {
        return t;
    }

    /**
     * getter
     * @return number of share holders
     */
    public int getN() {
        return n;
    }

    /**
     * getter
     * @return the prime was given in the constructor
     */
    public BigInteger getQ() {
        return q;
    }


    /**
     * getter
     * @return the polynomial was generated in constructor
     */
    public Polynomial getPolynomial() {
        return polynomial;
    }
}
