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

import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.Callable;

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

import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.products.ov.mixnet.BGMixer;
import com.scytl.products.ov.mixnet.commons.ballots.ElGamalEncryptedBallots;
import com.scytl.products.ov.mixnet.commons.configuration.BGParams;
import com.scytl.products.ov.mixnet.commons.configuration.locations.LocationsProvider;
import com.scytl.products.ov.mixnet.commons.exceptions.MixingException;
import com.scytl.products.ov.mixnet.commons.homomorphic.Randomness;
import com.scytl.products.ov.mixnet.commons.homomorphic.impl.GjosteenElGamal;
import com.scytl.products.ov.mixnet.commons.proofs.bg.commitments.CommitmentParams;
import com.scytl.products.ov.mixnet.commons.proofs.bg.config.ProofsGeneratorConfigurationParams;
import com.scytl.products.ov.mixnet.commons.tools.MultiExponentiation;
import com.scytl.products.ov.mixnet.proofs.bg.multiexp.ParallelComputeAllE;

public class MixingBatch implements Callable<MixingResult> {

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

    private final int id;

    private final Randomness[] batchRandomness;

    private final List<Ciphertext> batchPreComputations;

    private final BGMixer mixer;

    private final ElGamalEncryptedBallots elGamalEncryptedBallots;

    private final int offset;

    private final Integer batchNum;

    private final BGParams batchBGParams;

    private final ZpSubgroup zpGroup;

    private final GjosteenElGamal elGamal;

    private final int concurrencyLevel;

    private final int partitionSize;

    private final ElGamalPublicKey publicKey;

    private final ElGamalPrivateKey ballotDecryptionKey;

    private final LocationsProvider locationsProvider;

    private final MultiExponentiation multiExponentiation;

    private final ParallelComputeAllE parallelComputeAllE;

    private PrimitivesServiceAPI primitivesService;

    public MixingBatch(final int partitionSize, final BGMixer mixer,
            final ElGamalEncryptedBallots elGamalEncryptedBallots, final Integer batchNum, final int offset,
            final BGParams bGParams, final ZpSubgroup zpGroup, final GjosteenElGamal elGamal,
            final int concurrencyLevel, final int id, final ElGamalPublicKey publicKey,
            final ElGamalPrivateKey ballotDecryptionKey, final LocationsProvider locationsProvider,
            final MultiExponentiation multiExponentiation, final ParallelComputeAllE parallelComputeAllE,
            final Randomness[] batchRandomness, final List<Ciphertext> batchPreComputations,
            final PrimitivesServiceAPI primitivesService) {

        this.partitionSize = partitionSize;
        this.mixer = mixer;
        this.elGamalEncryptedBallots = elGamalEncryptedBallots;
        this.offset = offset;
        this.batchNum = batchNum;
        this.batchBGParams = bGParams;
        this.zpGroup = zpGroup;
        this.elGamal = elGamal;
        this.concurrencyLevel = concurrencyLevel;
        this.publicKey = publicKey;
        this.ballotDecryptionKey = ballotDecryptionKey;
        this.locationsProvider = locationsProvider;
        this.id = id;
        this.batchRandomness = batchRandomness;
        this.batchPreComputations = batchPreComputations;
        this.multiExponentiation = multiExponentiation;
        this.parallelComputeAllE = parallelComputeAllE;
        this.primitivesService = primitivesService;
    }

    @Override
    public MixingResult call() throws MixingException {
        Instant start = Instant.now();
        MixingResult mixingResult;

        LOG.info("Mixing " + partitionSize + " encrypted ballots...");

        try {

            final CommitmentParams batchCommitmentParams = createBatchCommitmentParams(batchBGParams);
            final ProofsGeneratorConfigurationParams batchProofsGeneratorConfigurationParams =
                createBatchProofsGeneratorConfigurationParams(batchBGParams);

            mixer.mix(elGamalEncryptedBallots, batchBGParams, batchCommitmentParams,
                batchProofsGeneratorConfigurationParams, batchNum, partitionSize, offset, concurrencyLevel,
                publicKey, ballotDecryptionKey, zpGroup, locationsProvider, multiExponentiation,
                parallelComputeAllE, batchRandomness, batchPreComputations, primitivesService);

            mixingResult = new MixingResult(true, batchNum, partitionSize, offset);

            LOG.info("Batch id=" + id + "(index of partitions array) with " + partitionSize + " finished OK.");

        } catch (final Exception e) {
            LOG.error("An error occurred while performing mixing batch number " + batchNum + ". Exception message: "
                + e.getMessage(), e);
            mixingResult = new MixingResult(false, batchNum, partitionSize, offset);
        }

        Instant end = Instant.now();
        LOG.info("Batch id=" + id + " duration was: " + Duration.between(start, end));
        return mixingResult;
    }

    private CommitmentParams createBatchCommitmentParams(final BGParams batchBGParams) {
        return new CommitmentParams(zpGroup, batchBGParams.getN());
    }

    private ProofsGeneratorConfigurationParams createBatchProofsGeneratorConfigurationParams(final BGParams bGParams) {

        return new ProofsGeneratorConfigurationParams(bGParams.getM(), bGParams.getN(), elGamal,
            bGParams.getNumIterations(), bGParams.getMu(), zpGroup);
    }
}
