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

import java.math.BigInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.homomorphic.Randomness;
import com.scytl.ov.mixing.commons.proofs.bg.config.ProofsGeneratorConfigurationParams;
import com.scytl.ov.mixing.proofs.bg.multiexp.basic.BasicProofGenerator;
import com.scytl.ov.mixing.proofs.bg.multiexp.reduction.ReductionProofGenerator;
import com.scytl.ov.mixing.proofs.bg.shuffle.BaseCommitmentGenerator;
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;
import com.scytl.ov.mixing.commons.homomorphic.impl.GjosteenElGamalRandomness;

/**
 * Defines the required methods to generate a multi-exponentiation proof.
 */
public class MultiExponentiationProofGenerator {

    private static final Logger LOG = LoggerFactory.getLogger("std");

    private final ProofsGeneratorConfigurationParams config;

    private final Ciphertext[][] ciphertexts;

    private final Randomness[] initialRandomExponents;

    private final BasicProofGenerator basicProofGenerator;

    private final ReductionProofGenerator reductionProofGenerator;

    public MultiExponentiationProofGenerator(final ProofsGeneratorConfigurationParams config,
            final Ciphertext[][] ciphertexts, final Randomness[] initialRandomExponents,
            final BasicProofGenerator basicProofGenerator, final ReductionProofGenerator reductionProofGenerator) {

        super();
        this.config = config;
        this.ciphertexts = ciphertexts;
        this.initialRandomExponents = initialRandomExponents;

        this.basicProofGenerator = basicProofGenerator;

        this.reductionProofGenerator = reductionProofGenerator;
    }

    /**
     * Generates the multi-exponentiation proof and allows the {@code harvester} to collect the result.
     *
     * @param harvester
     * @throws GeneralCryptoLibException
     */
    public void generate(final StateHarvester harvester)
            throws GeneralCryptoLibException {

        calculateRho(harvester);

        harvester.collect(CommonStateLabels.CIPHERTEXTS, ciphertexts);

        if (config.applyReduction()) {

            harvester.collect(CommonStateLabels.REDUCT_ITERATIONS_LEFT, config.getNumIterations());

            LOG.info("\t MEP Applying reduction...");
            reductionProofGenerator.generate(harvester);

        } else {
            basicProofGenerator.generate(harvester);
        }

    }

    private void calculateRho(final StateHarvester harvester) {

        try {

            Randomness randomExponent = harvester.offer(Produced.CALCULATED_RANDOM_EXPONENT, Randomness.class);

            if (randomExponent == null) {

                final Exponent[][] exponents =
                    harvester.offer(BaseCommitmentGenerator.Produced.EXPONENTS, Exponent[][].class);

                int numberOfColumns = config.getMatrixDimensions().getNumberOfColumns();
                randomExponent = new GjosteenElGamalRandomness(
                    new Exponent(config.getZpGroupParams().getQ(), BigInteger.ZERO));
                for (int i = 0, numberOfRows = config.getMatrixDimensions().getNumberOfRows(); i < numberOfRows; i++) {

                    int iMultipliedByNumberOfColumns = i * numberOfColumns;

                    for (int j = 0; j < numberOfColumns; j++) {

                        final Randomness aux =
                            initialRandomExponents[iMultipliedByNumberOfColumns + j].multiply(exponents[i][j]);

                        randomExponent = randomExponent.add(aux);
                    }
                }
                randomExponent =
                    randomExponent.multiply(new Exponent(config.getZpGroupParams().getQ(), new BigInteger("-1")));

                harvester.collect(Produced.CALCULATED_RANDOM_EXPONENT, randomExponent);
            }

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

    public enum Produced implements StateLabel {
        CALCULATED_RANDOM_EXPONENT
    }

}
