/*
 * 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 java.io.IOException;
import java.io.OutputStream;
import java.util.Base64;
import java.util.List;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
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.mathematical.groups.impl.ZpSubgroup;
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.factory.LoggingFactory;
import com.scytl.products.oscore.logging.api.formatter.MessageFormatter;
import com.scytl.products.oscore.logging.api.writer.LoggingWriter;
import com.scytl.products.oscore.logging.core.factory.LoggingFactoryLog4j;
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.VoteWithProof;
import com.scytl.products.ov.mixnet.commons.configuration.locations.LocationsProvider;
import com.scytl.products.ov.mixnet.commons.constants.Constants;
import com.scytl.products.ov.mixnet.commons.exceptions.MixingException;
import com.scytl.products.ov.mixnet.commons.exceptions.ShufflerException;
import com.scytl.products.ov.mixnet.commons.io.ElGamalEncryptedBallotsWriter;
import com.scytl.products.ov.mixnet.commons.io.ElgamalPublicKeyWriter;
import com.scytl.products.ov.mixnet.commons.io.ZpGroupReader;
import com.scytl.products.ov.mixnet.commons.proofs.bg.commitments.CommitmentParams;
import com.scytl.products.ov.mixnet.infrastructure.log.MixingAndVerifyingLogEvents;
import com.scytl.products.ov.mixnet.io.VotesWithProofWriter;

public class NonMixer {

    private final TransactionInfoProvider transactionInfoProvider = new TransactionInfoProvider();

    private final LoggingWriter loggingWriter;

    private final BallotDecryptionService ballotDecryptionService;

    public NonMixer(BallotDecryptionService ballotDecryptionService) {
        final MessageFormatter formatter = new SplunkFormatter("OV", "MIX", transactionInfoProvider);
        final LoggingFactory loggerFactory = new LoggingFactoryLog4j(formatter);
        this.loggingWriter = loggerFactory.getLogger("splunkable");
        this.ballotDecryptionService = ballotDecryptionService;

    }

    public void mix(final ElGamalEncryptedBallots encryptedBallots, final CommitmentParams commitmentParams,
            final ElGamalPublicKey publicKey, final ZpSubgroup group, final int batchNum, final int partitionSize,
            final int offset, final ElGamalPrivateKey ballotDecryptionKey, final LocationsProvider locationsProvider,
            final PrimitivesServiceAPI primitivesService) throws MixingException {

        // It's a new thread, so we need to create a new trackId
        String tenant = "1";
        transactionInfoProvider.generate(tenant, Constants.LOGGING_LOCALHOST, Constants.LOGGING_LOCALHOST);

        loggingWriter
            .log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(
                        MixingAndVerifyingLogEvents.SHUFREENCR_NO_SHUFFLING_AND_NO_REENCRYPTING_THE_ENCRYPTED_BALLOTS)
                    .user("adminID").electionEvent("-").additionalInfo("mbb_id", "-").createLogInfo());

        loggingWriter.log(Level.INFO,
            new LogContent.LogContentBuilder()
                .logEvent(MixingAndVerifyingLogEvents.SHUFREENCR_NO_GENERATING_CRYPTOGRAPHIC_PROOF_OF_MIXING)
                .user("adminID").electionEvent("-").additionalInfo("mbb_id", "-").createLogInfo());

        // Start the decryption task.
        List<VoteWithProof> votesWithProofs = ballotDecryptionService.decrypt(encryptedBallots.getBallots(),
            ballotDecryptionKey, batchNum, partitionSize, offset);

        try {
            storeData(encryptedBallots, votesWithProofs, commitmentParams, batchNum, group, publicKey, locationsProvider,
                primitivesService);
        } catch (Exception e) {
            throw new MixingException("An error trying to store the outputs of the mixing", e);
        }
    }

    private void storeData(final ElGamalEncryptedBallots encryptedBallots, final List<VoteWithProof> votesWithProof,
            final CommitmentParams commitmentParams, final int batchNum, final ZpSubgroup groupConfig,
            final ElGamalPublicKey publicKey,
			final LocationsProvider locationsProvider, PrimitivesServiceAPI primitivesService)
			throws IOException, GeneralCryptoLibException {

        ElGamalEncryptedBallotsWriter.CSV.write(
            locationsProvider.getMixerLocationsProvider().getEncryptedBallotsOutputOfBatch(batchNum), encryptedBallots,
            primitivesService.getRawMessageDigest());

        VotesWithProofWriter.write(
            locationsProvider.getMixerLocationsProvider().getVotesWithProofOutputOfBatch(batchNum),
            locationsProvider.getMixerLocationsProvider().getDecryptedVotesOutputOfBatch(batchNum), votesWithProof);
        ZpGroupReader.serializeToStream(groupConfig,
            locationsProvider.getMixerLocationsProvider().getEncryptionParametersOutputOfBatch(batchNum));

        OutputStream pathThisBatchMixedBallot =
            locationsProvider.getMixerLocationsProvider().getReEncryptedBallotsOutputOfBatch(batchNum);

        byte[] hash = ElGamalEncryptedBallotsWriter.CSV.write(pathThisBatchMixedBallot, encryptedBallots,
            primitivesService.getRawMessageDigest());
        String hashAsString = Base64.getEncoder().encodeToString(hash);

        logHashOfWrittenBallotBox(batchNum, hashAsString);

        commitmentParams.serializeToStream(
            locationsProvider.getMixerLocationsProvider().getCommitmentParametersOutputOfBatch(batchNum));

        ElgamalPublicKeyWriter.serializeToStream(publicKey,
            locationsProvider.getMixerLocationsProvider().getPublicKeyOutputOfBatch(batchNum));
    }

    private void logHashOfWrittenBallotBox(final int batchNum, String hashAsString)
            throws GeneralCryptoLibException, IOException {

        try {

            loggingWriter.log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(
                        MixingAndVerifyingLogEvents.SHUFREENCR_SUCCESS_WRITING_THE_SHUFFLED_AND_REENCRYPTING_BALLOT_BOX)
                    .user("adminID").electionEvent("-").additionalInfo("mbb_id", "-")
                    .additionalInfo("batch_num", Integer.toString(batchNum)).additionalInfo("mbb_hash", hashAsString)
                    .createLogInfo());
        } catch (Exception e) {
            loggingWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(
                        MixingAndVerifyingLogEvents.SHUFREENCR_ERROR_WRITING_THE_SHUFFLED_AND_REENCRYPTING_BALLOT_BOX)
                    .user("adminID").electionEvent("-").additionalInfo("mbb_id", "-")
                    .additionalInfo("batch_num", Integer.toString(batchNum)).additionalInfo("mbb_hash", hashAsString)
                    .additionalInfo("error_msg", e.getMessage()).createLogInfo());
            throw new ShufflerException("An error occurred while writing the shuffled ballots box", e);
        }
    }

}