/*
 * 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.proofs.bg.multiexp.basic;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.ov.mixing.commons.beans.proofs.MultiExponentiationBasicProofAnswer;
import com.scytl.ov.mixing.commons.homomorphic.Randomness;
import com.scytl.ov.mixing.commons.proofs.bg.commitments.PrivateCommitment;
import com.scytl.ov.mixing.commons.tools.ExponentTools;
import com.scytl.ov.mixing.commons.tools.RandomOracleHash;
import com.scytl.ov.mixing.commons.tools.RandomnessTools;
import com.scytl.ov.mixing.proofs.bg.shuffle.SecondCommitmentGenerator;
import com.scytl.ov.mixing.proofs.bg.state.CommonStateLabels;
import com.scytl.ov.mixing.proofs.bg.state.StateHarvester;
import com.scytl.ov.mixing.proofs.bg.state.StateLabel;

/**
 * Implements the parallel mode.
 */
public class ParallelBasicAnswerGenerator implements BasicAnswerGenerator {

    @Override
    public void generate(final StateHarvester harvester) {

        final RandomOracleHash randomOracle =
            harvester.offer(BasicProofGenerator.Produced.MULTIEXP_BASIC_RO, RandomOracleHash.class);

        final Exponent challengeX = randomOracle.getHash();

        calculateExponentsA(challengeX, harvester);
        calculateExponentR(challengeX, harvester);
        calculateExponentsB(challengeX, harvester);
        calculateExponentS(challengeX, harvester);
        calculateRandomnessTau(challengeX, harvester);

        collectAnswer(harvester);
    }

    private static void collectAnswer(final StateHarvester harvester) {

        final Exponent[] a = harvester.offer(InnerProduced.EXPONENTS_A, Exponent[].class);
        final Exponent r = harvester.offer(InnerProduced.EXPONENT_R, Exponent.class);
        final Exponent b = harvester.offer(InnerProduced.EXPONENTS_B, Exponent.class);
        final Exponent s = harvester.offer(InnerProduced.EXPONENT_S, Exponent.class);
        final Randomness tau = harvester.offer(InnerProduced.RANDOMNESS_TAU, Randomness.class);

        harvester.collect(Produced.BASIC_MULTIEXP_ANSWER, new MultiExponentiationBasicProofAnswer(a, r, b, s, tau));
    }

    private static void calculateExponentsA(final Exponent challengeX, final StateHarvester harvester) {
        // a
        final Exponent[] a0 = harvester.offer(BasicInitialMessageGenerator.Produced.EXPONENTS_A0, Exponent[].class);
        final Exponent[][] a =
            harvester.offer(BasicInitialMessageGenerator.Produced.EXPONENTS_ARR, Exponent[][].class);
        Exponent[] aExp = ExponentTools.addTwoVectors(a0, ExponentTools.evaluateVecPolyTransposeFromX(a, challengeX));

        harvester.collect(InnerProduced.EXPONENTS_A, aExp);
    }

    private static void calculateExponentR(final Exponent challengeX, final StateHarvester harvester) {

        try {

            // r
            final PrivateCommitment[] privateCommitmentsB =
                harvester.offer(SecondCommitmentGenerator.Produced.PRIVATE_COMMITS, PrivateCommitment[].class);

            final Ciphertext[][] ciphertexts = harvester.offer(CommonStateLabels.CIPHERTEXTS, Ciphertext[][].class);

            final int ciphertextsCount = ciphertexts.length;
            final Exponent[] r = new Exponent[ciphertextsCount];

            for (int i = 0; i < ciphertextsCount; i++) {
                r[i] = privateCommitmentsB[i].getR();
            }

            final Exponent r0 = harvester.offer(BasicInitialMessageGenerator.Produced.EXPONENTS_R0, Exponent.class);
            Exponent rExp = r0.add(ExponentTools.evaluatePolynomialFromX(r, challengeX));

            harvester.collect(InnerProduced.EXPONENT_R, rExp);

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

    private static void calculateExponentsB(final Exponent challengeX, final StateHarvester harvester) {

        // b
        final Exponent[] bMultiArray =
            harvester.offer(BasicInitialMessageGenerator.Produced.EXPONENTS_MULTIARR_B, Exponent[].class);

        Exponent b = ExponentTools.evaluatePolynomialFrom1(bMultiArray, challengeX);

        harvester.collect(InnerProduced.EXPONENTS_B, b);
    }

    private static void calculateExponentS(final Exponent challengeX, final StateHarvester harvester) {

        // s
        final Exponent[] sArray =
            harvester.offer(BasicInitialMessageGenerator.Produced.EXPONENTS_ARR_S, Exponent[].class);

        Exponent s = ExponentTools.evaluatePolynomialFrom1(sArray, challengeX);

        harvester.collect(InnerProduced.EXPONENT_S, s);
    }

    private static void calculateRandomnessTau(final Exponent challengeX, final StateHarvester harvester) {

        // tau
        final Randomness[] tauArray =
            harvester.offer(BasicInitialMessageGenerator.Produced.RANDOMNESS_TAU_ARR, Randomness[].class);

        Randomness tau = RandomnessTools.evaluatePolynomialFrom1(tauArray, challengeX);

        harvester.collect(InnerProduced.RANDOMNESS_TAU, tau);
    }

    // Only used inside this class
    public enum InnerProduced implements StateLabel {
        EXPONENTS_A, EXPONENT_R, EXPONENTS_B, EXPONENT_S, RANDOMNESS_TAU
    }

}
