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

import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.products.oscore.logging.api.domain.Level;
import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.oscore.logging.core.factory.SecureLoggingFactoryLog4j;
import com.scytl.products.oscore.logging.core.formatter.SplunkFormatter;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.mixnet.commons.ballots.ElGamalEncryptedBallots;
import com.scytl.products.ov.mixnet.commons.beans.ShuffleOutput;
import com.scytl.products.ov.mixnet.commons.beans.proofs.ShuffleProof;
import com.scytl.products.ov.mixnet.commons.constants.Constants;
import com.scytl.products.ov.mixnet.commons.exceptions.ShufflerException;
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.MatrixArranger;
import com.scytl.products.ov.mixnet.commons.tools.MultiExponentiation;
import com.scytl.products.ov.mixnet.infrastructure.log.MixingAndVerifyingLogEvents;
import com.scytl.products.ov.mixnet.proofs.bg.multiexp.ParallelComputeAllE;
import com.scytl.products.ov.mixnet.proofs.bg.shuffle.ParallelPrivateAndPublicCommitmentsGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.shuffle.PrivateAndPublicCommitmentsGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.shuffle.ShuffleProofGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.shuffle.ShuffleProofInputData;

public class SimpleShuffleProofService implements ShuffleProofService {

    private final SecureLoggingWriter loggingWriter;

    private final TransactionInfoProvider transactionInfoProvider;

    public SimpleShuffleProofService(TransactionInfoProvider transactionInfoProvider) {
        this.transactionInfoProvider = transactionInfoProvider;
        loggingWriter = initSecureLogger();
    }

    /**
     * Initialises the secure logger.
     *
     * @return a configured secure logger.
     */
    private SecureLoggingWriter initSecureLogger() {
        SplunkFormatter splunkFormatter =
            new SplunkFormatter(Constants.LOGGING_APPLICATION, Constants.LOGGING_APPLICATION, transactionInfoProvider);
        final SecureLoggingFactoryLog4j loggerFactory = new SecureLoggingFactoryLog4j(splunkFormatter);
        return loggerFactory.getLogger(Constants.SECURE_LOGGER_NAME);
    }

    /**
     * Generates a shuffle proof
     *
     * @return a shuffle proof
     */
    @Override
    public ShuffleProof generate(final ElGamalEncryptedBallots encryptedBallots,
            final ShuffleOutput<ElGamalEncryptedBallots> shuffleOutput, final int m, final int n,
            final CommitmentParams commitmentParams,
            final ProofsGeneratorConfigurationParams proofsGeneratorConfigurationParams,
            final int concurrencyLevel, final MultiExponentiation multiExponentiation,
            final ParallelComputeAllE parallelComputeAllE, final int batchNum, final int partitionSize,
            final int offset) {
        if (transactionInfoProvider.get() == null) {
            // Running in a new thread, create a new transaction info object.
            transactionInfoProvider.generate(Constants.LOGGING_TENANT, Constants.LOGGING_LOCALHOST,
                Constants.LOGGING_LOCALHOST);
        }

        try {
            ShuffleProofInputData shuffleProofInputData = adapt(encryptedBallots, shuffleOutput, m, n);
            final ShuffleProofGenerator spg = new ShuffleProofGenerator(proofsGeneratorConfigurationParams,
                commitmentParams, shuffleProofInputData.getEncryptedCiphertexts(),
                shuffleProofInputData.getPermutation(), shuffleProofInputData.getPrivateAndPublicCommitmentsGenerator(),
                multiExponentiation, concurrencyLevel, shuffleProofInputData.getReencryptedCiphertexts(), 
                shuffleProofInputData.getRho(), parallelComputeAllE);

            ShuffleProof result = spg.generate();

            loggingWriter.log(Level.INFO, new LogContent.LogContentBuilder()
                .logEvent(MixingAndVerifyingLogEvents.SHUFREENCR_SUCCESS_GENERATING_CRYPTOGRAPHIC_PROOF_OF_MIXING)
                .user("adminID").electionEvent("-").additionalInfo("mbb_id", "-")
                .additionalInfo("batch_num", Integer.toString(batchNum)).additionalInfo("partition_size", Integer.toString( partitionSize))
                .additionalInfo("offset", Integer.toString(offset)).createLogInfo());

            return result;
        } catch (Exception e) {
            loggingWriter.log(Level.ERROR, new LogContent.LogContentBuilder()
                .logEvent(MixingAndVerifyingLogEvents.SHUFREENCR_ERROR_GENERATING_CRYPTOGRAPHIC_PROOF_OF_MIXING)
                .user("adminID").electionEvent("-").additionalInfo("mbb_id", "-")
                .additionalInfo("batch_num", Integer.toString(batchNum)).additionalInfo("partition_size", Integer.toString(partitionSize))
                .additionalInfo("offset", Integer.toString(offset)).additionalInfo("error_msg", e.getMessage()).createLogInfo());
            throw new ShufflerException("An error occurred while trying to generate a cryptographic proof", e);
        }
    }

    private ShuffleProofInputData adapt(final ElGamalEncryptedBallots ballots,
            final ShuffleOutput<ElGamalEncryptedBallots> shuffleOutput, final int m, final int n) {

        final Ciphertext[][] encryptedCiphertexts = MatrixArranger.arrangeInCiphertextMatrix(ballots, m, n);

        final Ciphertext[][] reencryptedCiphertexts =
            MatrixArranger.arrangeInCiphertextMatrix(shuffleOutput.getReEncryptedBallots(), m, n);

        final PrivateAndPublicCommitmentsGenerator privateAndPublicCommitmentsGenerator =
            createPrivateAndPublicCommitmentsGenerator();

        return new ShuffleProofInputData(encryptedCiphertexts, shuffleOutput.getPermutation(),
            privateAndPublicCommitmentsGenerator, reencryptedCiphertexts, shuffleOutput.getExponents());
    }

    private static PrivateAndPublicCommitmentsGenerator createPrivateAndPublicCommitmentsGenerator() {
        return new ParallelPrivateAndPublicCommitmentsGenerator();
    }
}
