/*
 * 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.products.ov.mixnet.commons.tools;

import java.math.BigInteger;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;

public class MultiExponentiationImplTest {

    private static MultiExponentiation exponentiation;

    private static BigInteger q;

    private static ZpSubgroup zp;

    @Rule
    public ExpectedException exceptionOverflow = ExpectedException.none();

    @BeforeClass
    public static void SetUp() {

        try {

            BigInteger p = new BigInteger(
                "19986688906046564128933146738777425322816749151206433824724375860389469435034879568326182196395883317406721876341318233130215508153050040685467970944519396129350470726378928164913650823455164473899748694259832050347468830817152804516782103295583819142853730959962721817438380688626082321959250632117349503096843585867899460166925097799705277683925210629909665148129203684418741591764606705334451744960738458683943883031295610463506458258688143314813257198398473299770874221419559691760260785949697462079048765650248257229066826691093946929368283224407370561895464211388203688827737767708478415837201744111067995296931");

            q = new BigInteger("58836608530550561361432815559143291405695035652924302678071905441888807335883");

            BigInteger g = new BigInteger(
                "1295803044029977994736031501663830424024151888134138500263653425152042419721198783098612873101251196454248315183108082550589073471331258695406913726236066567018401396005043453872551210334185332787681810456148123543377889869863748760398575936300740437500518704874336270169546226641350484350372440206355261385623953539587640977077191811154995323105691220341981396163943492687416530603120058467555614555374357379038465022119349145577756037781885293072844511723192976337462666899152617788373291368141977110976495987180085811581815594214632888129827901840873529932222421268980844112582598401840862376784761676463335878689");

            zp = new ZpSubgroup(g, p, q);

            exponentiation = MultiExponentiationImpl.getInstance();

        } catch (GeneralCryptoLibException ex) {
            throw new CryptoLibException(ex);
        }
    }

    @Test
    public void computeMultiExpo_n2() {

        computeMultiExpoAndCheckResult(2);
    }

    @Test
    public void computeMultiExpo_n3() {

        computeMultiExpoAndCheckResult(3);
    }

    @Test
    public void computeMultiExpo_n10() {

        computeMultiExpoAndCheckResult(10);
    }

    @Test
    public void computeMultiExpo_n64() {

        computeMultiExpoAndCheckResult(64);
    }

    @Test
    public void computeMultiExpo_n100() {

        computeMultiExpoAndCheckResult(100);
    }

    @Test
    public void computeMultiExpo_n200() {

        computeMultiExpoAndCheckResult(200);
    }

    private void computeMultiExpoAndCheckResult(final int n) {

        try {

            // generate random bases
            final ZpGroupElement[] base = new ZpGroupElement[n];
            for (int i = 0; i < n; i++) {
                base[i] = (ZpGroupElement) (zp.getGenerator()
                    .exponentiate(new Exponent(zp.getQ(), BigInteger.valueOf(i + 1))));
            }

            // generate random exponents
            final Exponent[] expo = new Exponent[n];
            for (int i = 0; i < n; i++) {
                expo[i] = ExponentTools.getRandomExponent(q);
            }
            // calculate result using straightforward method
            final ZpGroupElement expected = computeMultiExpoStraightforward(base, expo);

            System.out.println("Expected: " + expected);

            // calculate result using MultiExponentiationImpl
            final ZpGroupElement computed = (ZpGroupElement) exponentiation.computeMultiExpo(zp, base, expo);

            final String errorMsg = "the result from LimMultiExpo did not match the expected result";
            Assert.assertEquals(errorMsg, expected, computed);

        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException(e);
        }
    }

    private ZpGroupElement computeMultiExpoStraightforward(final ZpGroupElement[] base, final Exponent[] expo) {

        try {

            final int n = base.length;
            ZpGroupElement product = base[0].exponentiate(expo[0]);
            for (int i = 1; i < n; i++) {
                product = product.multiply(base[i].exponentiate(expo[i]));
            }
            return (ZpGroupElement) product;

        } catch (GeneralCryptoLibException ex) {
            throw new CryptoLibException(ex);
        }
    }
}
