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

import java.util.concurrent.ForkJoinTask;

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

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.ov.mixing.commons.beans.proofs.MultiExponentiationBasicProofAnswer;
import com.scytl.ov.mixing.commons.beans.proofs.MultiExponentiationBasicProofInitialMessage;
import com.scytl.ov.mixing.commons.beans.proofs.MultiExponentiationReductionAnswer;
import com.scytl.ov.mixing.commons.beans.proofs.MultiExponentiationReductionInitialMessage;
import com.scytl.ov.mixing.commons.beans.proofs.ProductProofMessage;
import com.scytl.ov.mixing.commons.beans.proofs.ShuffleProofSecondAnswer;
import com.scytl.ov.mixing.commons.homomorphic.Randomness;
import com.scytl.ov.mixing.commons.proofs.bg.commitments.CommitmentParams;
import com.scytl.ov.mixing.commons.proofs.bg.config.ProofsGeneratorConfigurationParams;
import com.scytl.ov.mixing.commons.tools.MultiExponentiation;
import com.scytl.ov.mixing.proofs.bg.multiexp.ComputeAllE;
import com.scytl.ov.mixing.proofs.bg.multiexp.MultiExponentiationProofGenerator;
import com.scytl.ov.mixing.proofs.bg.multiexp.basic.BasicInitialMessageGenerator;
import com.scytl.ov.mixing.proofs.bg.multiexp.basic.BasicProofGenerator;
import com.scytl.ov.mixing.proofs.bg.multiexp.basic.ParallelBasicAnswerGenerator;
import com.scytl.ov.mixing.proofs.bg.multiexp.basic.ParallelBasicProofGenerator;
import com.scytl.ov.mixing.proofs.bg.multiexp.reduction.ParallelReductionProofGenerator;
import com.scytl.ov.mixing.proofs.bg.multiexp.reduction.ReductionProofGenerator;
import com.scytl.ov.mixing.proofs.bg.product.ParallelProductProofGenerator;
import com.scytl.ov.mixing.proofs.bg.product.ProductProofGenerator;
import com.scytl.ov.mixing.proofs.bg.state.StateHarvester;

/**
 * Implements the parallel mode.
 */
public class ParallelSecondAnswerGenerator implements SecondAnswerGenerator {

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

    private static final double PRODUCT_PROOF_CONCURRENT_WEIGHT = 0.3;

    private final ProofsGeneratorConfigurationParams config;

    private final ProductProofGenerator productProofGenerator;

    private final MultiExponentiationProofGenerator multiexpProofGenerator;

    public ParallelSecondAnswerGenerator(final ProofsGeneratorConfigurationParams config,
            final Randomness[] randomExponents, final Ciphertext[][] ciphertexts, final CommitmentParams comParams,
            final ComputeAllE computeAllE, final MultiExponentiation limMultiExpo) {
        super();
        this.config = config;

        final int multiExpoProofConcurrencyLevel;

        int concurrencyLevel = this.config.getConcurrencyLevel();
        if (concurrencyLevel == 1) {
            multiExpoProofConcurrencyLevel = 1;
        } else {
            int productProofConcurrencyLevel = getProductProofConcurrencyLevel(concurrencyLevel);
            multiExpoProofConcurrencyLevel = concurrencyLevel - productProofConcurrencyLevel;
        }

        this.productProofGenerator = new ParallelProductProofGenerator(this.config.getMatrixDimensions(), comParams,
        		this.config.getZpGroupParams().getQ(), limMultiExpo);

        final BasicProofGenerator basicProofGenerator = new ParallelBasicProofGenerator(this.config, comParams,
            limMultiExpo, computeAllE, multiExpoProofConcurrencyLevel);

        this.multiexpProofGenerator = new MultiExponentiationProofGenerator(this.config, ciphertexts, randomExponents,
            //
            basicProofGenerator, //
            new ParallelReductionProofGenerator(config, comParams.getCommitmentLength(), basicProofGenerator, comParams,
                computeAllE, limMultiExpo, multiExpoProofConcurrencyLevel));
    }

    private int getProductProofConcurrencyLevel(final int concurrencyLevel) {

        if (concurrencyLevel == 1) {
            return concurrencyLevel;
        } else {
            return (int) Math.round((concurrencyLevel) * PRODUCT_PROOF_CONCURRENT_WEIGHT);
        }
    }

    @Override
    public void generate(final StateHarvester harvester)
            throws GeneralCryptoLibException {

        LOG.info("Generating the product proof...");
        final ForkJoinTask<Void> productProofCalculation = ForkJoinTask.adapt(() -> {
            productProofGenerator.generate(harvester);
            return null;
        });
        productProofCalculation.fork();

        LOG.info("Generating the multi-exponentiation proof...");
        multiexpProofGenerator.generate(harvester);
        LOG.info("Multi-exponentiation has been generated successfully");

        if (productProofCalculation.tryUnfork()) {
            productProofCalculation.invoke();
        } else {
            productProofCalculation.join();
        }
        LOG.info("Product proof has been generated successfully");

        collectSecondAnswer(harvester);
    }

    private void collectSecondAnswer(final StateHarvester harvester) {

        final ProductProofMessage productProofMsg =
            harvester.offer(ProductProofGenerator.Produced.PRODUCT_PROOF_MSG, ProductProofMessage.class);

        ShuffleProofSecondAnswer thisAns;
        if (config.applyReduction()) {

            final MultiExponentiationReductionInitialMessage iniME =
                harvester.offer(ReductionProofGenerator.Produced.REDUCT_MULTIEXP_INIT_MSG,
                    MultiExponentiationReductionInitialMessage.class);

            final MultiExponentiationReductionAnswer ansME = harvester.offer(
                ReductionProofGenerator.Produced.REDUCT_MULTIEXP_ANSWER, MultiExponentiationReductionAnswer.class);

            thisAns = new ShuffleProofSecondAnswer(iniME, ansME, productProofMsg);

        } else {

            final MultiExponentiationBasicProofInitialMessage iniME =
                harvester.offer(BasicInitialMessageGenerator.Produced.BASIC_MULTIEXP_INIT_MSG,
                    MultiExponentiationBasicProofInitialMessage.class);

            final MultiExponentiationBasicProofAnswer ansME = harvester.offer(
                ParallelBasicAnswerGenerator.Produced.BASIC_MULTIEXP_ANSWER, MultiExponentiationBasicProofAnswer.class);

            thisAns = new ShuffleProofSecondAnswer(iniME, ansME, productProofMsg);
        }
        harvester.collect(Produced.SHUFFLE_SECOND_ANSWER, thisAns);
    }
}
