/*
 * 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.sdm.config.spring.batch;

import static com.scytl.products.ov.commons.util.MathUtils.isqrt;
import static com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents.GENCREDAT_ERROR_DERIVING_CREDENTIAL_ID;
import static com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents.GENSVPK_ERROR_DERIVING_KEYSTORE_PWD;
import static com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents.GENSVPK_ERROR_GENERATING_SVK;
import static com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents.GENSVPK_ERROR_GENERATING_VCID;
import static com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_LONGCHOICECODES;
import static com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_LONGVOTECASTCODE;
import static com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_PARTIALCHOICECODES;
import static com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents.GENVCC_ERROR_SIGNING_VOTECASTCODE;
import static com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents.GENVCC_ERROR_STORING_VOTECASTCODE;
import static java.util.Arrays.fill;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.crypto.SecretKey;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.beans.factory.annotation.Autowired;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIDerivedKey;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIPBKDFDeriver;
import com.scytl.cryptolib.elgamal.bean.ElGamalComputationsValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalDecrypter;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
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.writer.LoggingWriter;
import com.scytl.products.ov.choices.codes.ChoicesCodesGenerationAPI;
import com.scytl.products.ov.choices.codes.ChoicesCodesMappingEntry;
import com.scytl.products.ov.choices.codes.PartialCodeGenerator;
import com.scytl.products.ov.commons.beans.Ballot;
import com.scytl.products.ov.commons.beans.EncryptionParameters;
import com.scytl.products.ov.commons.dto.ElGamalComputationsValuesCodec;
import com.scytl.products.ov.config.actions.ExtendedAuthenticationService;
import com.scytl.products.ov.config.commands.uuid.UUIDGenerator;
import com.scytl.products.ov.config.commands.voters.JobExecutionObjectContext;
import com.scytl.products.ov.config.commands.voters.VotersGenerationTaskStaticContentProvider;
import com.scytl.products.ov.config.commands.voters.VotersParametersHolder;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardCodesDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardSetCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VotingCardCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VotingCardSetCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.generators.VerificationCardCredentialDataPackGenerator;
import com.scytl.products.ov.config.commands.voters.datapacks.generators.VotingCardCredentialDataPackGenerator;
import com.scytl.products.ov.config.exceptions.CreateVotingCardSetException;
import com.scytl.products.ov.config.exceptions.specific.GenerateCredentialIdException;
import com.scytl.products.ov.config.exceptions.specific.GenerateSVKVotingCardIdPassKeystoreException;
import com.scytl.products.ov.config.exceptions.specific.GenerateVerificationCardCodesException;
import com.scytl.products.ov.config.exceptions.specific.GenerateVotingcardIdException;
import com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents;
import com.scytl.products.ov.config.logs.Loggable;
import com.scytl.products.ov.config.model.authentication.ExtendedAuthInformation;
import com.scytl.products.ov.config.model.authentication.StartVotingKey;
import com.scytl.products.ov.config.model.authentication.service.StartVotingKeyService;
import com.scytl.products.ov.constants.Constants;
import com.scytl.products.ov.datapacks.beans.VerificationCardIdComputedValues;
import com.scytl.products.ov.datapacks.helpers.ReplacementsHolder;

/**
 * This class is a kind of hack because we do not 'read' anything and are not
 * passed any kind of item to process. We only generate data to be written.
 */
class VotingCardGenerator implements ItemProcessor<VerificationCardIdComputedValues, GeneratedVotingCardOutput> {

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

    @Loggable
    private LoggingWriter logWriter;

    private final VotingCardGenerationJobExecutionContext jobExecutionContext;

    private final String verificationCardSetId;

    private final String votingCardSetId;

    private final int numberOfVotingCards;

    private final CryptoAPIPBKDFDeriver pbkdfDeriver;

    private VotersParametersHolder holder;

    private VotersGenerationTaskStaticContentProvider staticContentProvider;

    private final PartialCodeGenerator<ZpGroupElement> partialCodeGenerator;

    @Autowired
    private JobExecutionObjectContext objectCache;

    @Autowired
    private UUIDGenerator uuidGenerator;

    @Autowired
    private ChoicesCodesGenerationAPI choicesCodesGeneration;

    @Autowired
    private ExtendedAuthenticationService extendedAuthenticationService;

    @Autowired
    private StartVotingKeyService startVotingKeyService;

    @Autowired
    private VotingCardCredentialDataPackGenerator votingCardCredentialDataPackGenerator;

    @Autowired
    private VerificationCardCredentialDataPackGenerator verificationCardCredentialDataPackGenerator;

    @Autowired
    private AsymmetricServiceAPI asymmetricService;

    @Autowired
    private ElGamalServiceAPI elGamalService;

    @Autowired
    private ElGamalComputationsValuesCodec codec;

    private ElGamalPrivateKey primeEncryptionPrivateKey;

    public VotingCardGenerator(final VotersParametersHolder holder,
            final VotingCardGenerationJobExecutionContext jobExecutionContext, final CryptoAPIPBKDFDeriver pbkdfDeriver,
            final VotersGenerationTaskStaticContentProvider staticContentProvider) {
        this.holder = holder;
        this.jobExecutionContext = jobExecutionContext;
        this.verificationCardSetId = jobExecutionContext.getVerificationCardSetId();
        this.votingCardSetId = jobExecutionContext.getVotingCardSetId();
        this.numberOfVotingCards = jobExecutionContext.getNumberOfVotingCards();
        String json = jobExecutionContext.getPrimeEncryptionPrivateKeyJson();
        try {
            this.primeEncryptionPrivateKey = ElGamalPrivateKey.fromJson(json);
        } catch (GeneralCryptoLibException e) {
            throw new IllegalStateException("Failed to create prime encryption private key from JSON.", e);
        }

        this.partialCodeGenerator = new PartialCodeGenerator<>();
        this.pbkdfDeriver = pbkdfDeriver;
        this.staticContentProvider = staticContentProvider;
    }

    @Override
    public GeneratedVotingCardOutput process(final VerificationCardIdComputedValues verificationCardIdComputedValues)
            throws Exception {

        final String verificationCardId = verificationCardIdComputedValues.getId();
        LOG.debug("Generating voting and verification cards for computed values of verification card id {}",
            verificationCardId);
        final String jobInstanceId = jobExecutionContext.getJobInstanceId();
        final String electionEventId = jobExecutionContext.getElectionEventId();
        final byte[] saltCredentialId = Base64.decodeBase64(jobExecutionContext.getSaltCredentialId());
        final byte[] saltPIN = Base64.decodeBase64(jobExecutionContext.getSaltPin());

        char[] pin = null;
        try {
            final String votingCardId = generateVotingCardId(electionEventId);

            final String startVotingKey = generateStartVotingKey(electionEventId);

            pin = generatePin(pbkdfDeriver, electionEventId, saltPIN, startVotingKey);

            final String credentialId =
                generateCredentialId(pbkdfDeriver, electionEventId, saltCredentialId, startVotingKey);

            final VotingCardCredentialDataPack voterCredentialDataPack =
                generateVotersCredentialDataPack(pin, credentialId);

            final VerificationCardSetCredentialDataPack verificationCardSetCredentialDataPack =
                objectCache.get(jobInstanceId, VerificationCardSetCredentialDataPack.class);

            final PrivateKey verificationCardIssuerPrivateKey =
                verificationCardSetCredentialDataPack.getVerificationCardIssuerKeyPair().getPrivate();

            final VerificationCardCredentialDataPack verificationCardCredentialDataPack =
                verificationCardCredentialDataPackGenerator.generate(holder.getVerificationCardInputDataPack(),
                    staticContentProvider.getEncryptionParameters(), electionEventId, verificationCardId,
                    verificationCardSetId, verificationCardIssuerPrivateKey, pin, holder.getAbsoluteBasePath());

            final VotingCardSetCredentialDataPack votingCardSetCredentialDataPack =
                objectCache.get(jobInstanceId, VotingCardSetCredentialDataPack.class);

            CryptoAPIElGamalDecrypter decrypter = elGamalService.createDecrypter(primeEncryptionPrivateKey);

            final Map<BigInteger, ZpGroupElement> ballotVotingOptionToPrePartialChoiceCodes = decryptChoiceCodes(
                decrypter, verificationCardIdComputedValues.getBallotVotingOption2prePartialChoiceCodes());

            final BigInteger ballotCastingKey =
                decryptBallotCastingKey(decrypter, verificationCardIdComputedValues.getEncryptedBallotCastingKey());

            final ZpGroupElement computedBallotCastingKey = decryptComputedBallotCastingKey(decrypter,
                verificationCardIdComputedValues.getComputedBallotCastingKey());

            final VerificationCardCodesDataPack verificationCardCodesDataPack = createVerificationCardCode(
                holder.getBallot(), votingCardSetCredentialDataPack,
                verificationCardCredentialDataPack.getVerificationCardKeyPair().getPrivateKeys(), verificationCardId,
                ballotVotingOptionToPrePartialChoiceCodes, ballotCastingKey, computedBallotCastingKey);

            final ExtendedAuthInformation extendedAuthInformation =
                getExtendedAuthInformation(electionEventId, startVotingKey);

            final String ballotId = jobExecutionContext.getBallotId();
            final String ballotBoxId = jobExecutionContext.getBallotBoxId();
            final String votCardSetId = jobExecutionContext.getVotingCardSetId();

            return GeneratedVotingCardOutput.success(votingCardId, votCardSetId, ballotId, ballotBoxId, credentialId,
                electionEventId, verificationCardId, verificationCardSetId, startVotingKey, voterCredentialDataPack,
                verificationCardCredentialDataPack, verificationCardCodesDataPack, extendedAuthInformation);

        } finally {
            if (pin != null)
                fill(pin, ' ');
        }
    }

    private Map<BigInteger, ZpGroupElement> decryptChoiceCodes(CryptoAPIElGamalDecrypter decrypter,
            String encryptedChoiceCodes) throws GeneralCryptoLibException {
        Map<ElGamalComputationsValues, ElGamalComputationsValues> ciphertextMap = codec.decodeMap(encryptedChoiceCodes);
        ZpSubgroup group = getZpSubGroup();
        Map<BigInteger, ZpGroupElement> choiceCodes = new HashMap<>();
        for (Map.Entry<ElGamalComputationsValues, ElGamalComputationsValues> entry : ciphertextMap.entrySet()) {
            BigInteger key = decrypter.decrypt(entry.getKey(), false).get(0).getValue();
            BigInteger value = decrypter.decrypt(entry.getValue(), false).get(0).getValue();
            choiceCodes.put(key, new ZpGroupElement(value, group));
        }
        return choiceCodes;
    }

    private BigInteger decryptBallotCastingKey(CryptoAPIElGamalDecrypter decrypter, String encryptedBallotCastingKey)
            throws GeneralCryptoLibException {
        ElGamalComputationsValues ciphertext = codec.decodeSingle(encryptedBallotCastingKey);
        return decrypter.decrypt(ciphertext, false).get(0).getValue();
    }

    private ZpGroupElement decryptComputedBallotCastingKey(CryptoAPIElGamalDecrypter decrypter,
            String computedBallotCastingKey) throws GeneralCryptoLibException {
        ElGamalComputationsValues ciphertext = codec.decodeSingle(computedBallotCastingKey);
        return decrypter.decrypt(ciphertext, false).get(0);
    }

    private ZpSubgroup getZpSubGroup() {
        EncryptionParameters encryptionParameters = holder.getEncryptionParameters();
        String generatorEncryptParam = encryptionParameters.getG();
        String pEncryptParam = encryptionParameters.getP();
        String qEncryptParam = encryptionParameters.getQ();

        ZpSubgroup zpSubgroup = null;
        try {
            BigInteger generatorEncryptParamBigInteger = new BigInteger(generatorEncryptParam);
            BigInteger pEncryptParamBigInteger = new BigInteger(pEncryptParam);
            BigInteger qEncryptParamBigInteger = new BigInteger(qEncryptParam);
            zpSubgroup =
                new ZpSubgroup(generatorEncryptParamBigInteger, pEncryptParamBigInteger, qEncryptParamBigInteger);
        } catch (GeneralCryptoLibException e) {
            LOG.error("Error creating ZpSubGroup from encryptionparameters", e);
        }

        return zpSubgroup;
    }

    private ExtendedAuthInformation getExtendedAuthInformation(final String electionEventId, final String svk) {
        // generates the authentication key and derives the ID
        StartVotingKey startVotingKey = StartVotingKey.ofValue(svk);
        return extendedAuthenticationService.create(startVotingKey, electionEventId);
    }

    private VerificationCardCodesDataPack createVerificationCardCode(final Ballot enrichedBallot,
            final VotingCardSetCredentialDataPack votingCardSetCredentialDataPack,
            final ElGamalPrivateKey verificationCardPrivateKey, final String verificationCardId,
            final Map<BigInteger, ZpGroupElement> ballotVotingOption2prePartialChoiceCodes,
            final BigInteger squaredBallotCastingKey, final ZpGroupElement computedBallotCastingKey)
            throws GeneralCryptoLibException {

        PrivateKey voteCastCodeSignerPrivateKey =
            votingCardSetCredentialDataPack.getVoteCastCodeSignerKeyPair().getPrivate();

        // Create Vote Casting Code
        // ->ChoicesCodesGeneratorImpl.generateVoteCastingCode()
        String voteCastingCode = choicesCodesGeneration.generateVoteCastingCode();

        // The signature of the voteCasting code includes the verification
        // cardId
        byte[] verificationIdCardAsBytes = verificationCardId.getBytes(StandardCharsets.UTF_8);
        byte[] voteCastingCodeAsBytes = voteCastingCode.getBytes(StandardCharsets.UTF_8);

        byte[] signatureOfVoteCastCode = signVoteCastCode(enrichedBallot, verificationCardId,
            voteCastCodeSignerPrivateKey, verificationIdCardAsBytes, voteCastingCodeAsBytes);

        // Extract the list of options from ballot -> enrichedBallot
        final List<ZpGroupElement> optionRepresentations = staticContentProvider.getOptionRepresentations();
        final Map<BigInteger, List<String>> representationsWithCorrectness =
            staticContentProvider.getRepresentationsWithCorrectness();

        Map<String, BigInteger> choiceCodes2BallotVotingOptionOrderedBasedOnInsertion = new LinkedHashMap<>();
        Map<String, String> optionsChoiceCodesUnordered = new HashMap<>();
        Exponent verificationCardPrivateKeyAsExponent = verificationCardPrivateKey.getKeys().get(0);
        SecretKey codesSecretKey = votingCardSetCredentialDataPack.getCodesSecretKey();

        // for each option
        for (Map.Entry<BigInteger, ZpGroupElement> entry : ballotVotingOption2prePartialChoiceCodes.entrySet()) {
            String shortChoiceCode =
                generateShortChoiceCodeForVO(enrichedBallot, verificationCardId, choiceCodes2BallotVotingOptionOrderedBasedOnInsertion);

            // add to map [short choice codes - ballot voting options]
            choiceCodes2BallotVotingOptionOrderedBasedOnInsertion.put(shortChoiceCode, entry.getKey());

            // get bytes
            byte[] shortChoiceCodeAsBytes = shortChoiceCode.getBytes(StandardCharsets.UTF_8);

            // Get
            ZpGroupElement prePartialChoiceCode = ballotVotingOption2prePartialChoiceCodes.get(entry.getKey());

            ZpGroupElement partialChoiceCode = generatePartialChoiceCode(enrichedBallot, verificationCardId,
                verificationCardPrivateKeyAsExponent, prePartialChoiceCode);

            // create a Long Choice Code using concat(Partial Choice Code,
            // verificationCardId, _holder.getEeid()) and
            // attributes with correctness
            List<String> attributesWithCorrectness = representationsWithCorrectness.get(entry.getKey());

            byte[] longChoiceCode = generateLongChoiceCodeForVO(holder.getEeid(), enrichedBallot, verificationCardId,
                partialChoiceCode, attributesWithCorrectness);

            // encrypt Short Choice Code with Long Choice Code ->
            // ChoicesCodesGeneratorImpl.generateMapping()
            // and add generated entry to map
            ChoicesCodesMappingEntry choicesCodesMappingEntry =
                choicesCodesGeneration.generateMapping(shortChoiceCodeAsBytes, longChoiceCode, codesSecretKey);
            // convert key and data to Base64

            String keyAsBase64 =
                new String(Base64.encodeBase64(choicesCodesMappingEntry.getKey()), StandardCharsets.UTF_8);
            String dataAsBase64 =
                new String(Base64.encodeBase64(choicesCodesMappingEntry.getData()), StandardCharsets.UTF_8);
            optionsChoiceCodesUnordered.put(keyAsBase64, dataAsBase64);
            // end for
        }

        // success - partial Choice Codes successfully generated
        logWriter.log(Level.DEBUG,
            new LogContent.LogContentBuilder()
                .logEvent(ConfigGeneratorLogEvents.GENVCC_SUCCESS_PARTIALCHOICECODES_GENERATED)
                .electionEvent(enrichedBallot.getElectionEvent().getId()).user("adminID")
                .objectId(verificationCardSetId).additionalInfo("verifc_id", verificationCardId)
                .additionalInfo("num_cc", Integer.toString(optionRepresentations.size())).createLogInfo());

        // success - long Choice Code successfully generated
        logWriter.log(Level.DEBUG,
            new LogContent.LogContentBuilder()
                .logEvent(ConfigGeneratorLogEvents.GENVCC_SUCCESS_LONGCHOICECODES_GENERATED)
                .electionEvent(enrichedBallot.getElectionEvent().getId()).objectId(verificationCardSetId)
                .user("adminID").additionalInfo("verifc_id", verificationCardId)
                .additionalInfo("num_cc", Integer.toString(optionRepresentations.size())).createLogInfo());

        byte[] longChoiceCode = generateLongChoiceCode(enrichedBallot, holder.getEeid(), verificationCardId,
            computedBallotCastingKey, Collections.emptyList());

        String signatureOfVoteCastCodeAsBase64 =
            new String(Base64.encodeBase64(signatureOfVoteCastCode), StandardCharsets.UTF_8);
        byte[] voteCastingCodeWithSignature =
            String.join(Constants.SEMICOLON, voteCastingCode, signatureOfVoteCastCodeAsBase64)
                .getBytes(StandardCharsets.UTF_8);

        try {
            ChoicesCodesMappingEntry voterCastingCodeMappingEntry =
                choicesCodesGeneration.generateMapping(voteCastingCodeWithSignature, longChoiceCode, codesSecretKey);
            // add the Voter Casting Code to the map of hash(longCode) to
            // encrypt(voteCastingCodeWithSignature,
            // withLongChoice)
            String keyAsBase64 =
                new String(Base64.encodeBase64(voterCastingCodeMappingEntry.getKey()), StandardCharsets.UTF_8);
            String dataAsBase64 =
                new String(Base64.encodeBase64(voterCastingCodeMappingEntry.getData()), StandardCharsets.UTF_8);
            optionsChoiceCodesUnordered.put(keyAsBase64, dataAsBase64);

            // Vote Cast Code correctly stored
            logWriter.log(Level.DEBUG,
                new LogContent.LogContentBuilder().logEvent(ConfigGeneratorLogEvents.GENVCC_SUCCESS_VOTECASTCODE_STORED)
                    .electionEvent(enrichedBallot.getElectionEvent().getId()).user("adminID")
                    .objectId(verificationCardSetId).additionalInfo("verifc_id", verificationCardId).createLogInfo());

        } catch (CreateVotingCardSetException e) {
            // error storing the Vote Cast Code in the Code - Mapping Table
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(GENVCC_ERROR_STORING_VOTECASTCODE)
                    .electionEvent(enrichedBallot.getElectionEvent().getId()).user("adminID")
                    .objectId(verificationCardSetId).additionalInfo("verifc_id", verificationCardId)
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());
            throw new GenerateVerificationCardCodesException(GENVCC_ERROR_STORING_VOTECASTCODE.getInfo(), e);
        }
        // return the two parts of ChoicesCodesGeneratorImpl.generateMapping() +
        // the map created with the options

        String ballotCastingKey = isqrt(squaredBallotCastingKey).toString();
        ballotCastingKey = StringUtils.leftPad(ballotCastingKey, Constants.NUM_DIGITS_BALLOT_CASTING_KEY_FINAL, "0");

        return new VerificationCardCodesDataPack(optionsChoiceCodesUnordered, ballotCastingKey, voteCastingCode,
            choiceCodes2BallotVotingOptionOrderedBasedOnInsertion);
    }

    private byte[] generateLongChoiceCode(final Ballot enrichedBallot, final String eeid,
            final String verificationCardId, final ZpGroupElement partialCode,
            final List<String> attributesWithCorrectness) {

        byte[] longChoiceCode;
        try {
            // Codes SecretKey ->
            // votingCardSetCredentialDataPack.getKeyStore().getSecretKeyEntry()
            // using -> ChoicesCodesGeneratorImpl.generateLongChoiceCode()
            longChoiceCode = choicesCodesGeneration.generateLongChoiceCode(eeid, verificationCardId, partialCode,
                attributesWithCorrectness);
        } catch (Exception e) {
            // error - error generating the Long Vote Cast Code
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_LONGVOTECASTCODE)
                    .electionEvent(enrichedBallot.getElectionEvent().getId()).user("adminID")
                    .objectId(verificationCardSetId).additionalInfo("verifc_id", verificationCardId)
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());
            throw new GenerateVerificationCardCodesException(GENVCC_ERROR_GENERATING_LONGVOTECASTCODE.getInfo(), e);
        }
        // success - long Vote Cast Code successfully generated
        logWriter.log(Level.DEBUG,
            new LogContent.LogContentBuilder()
                .logEvent(ConfigGeneratorLogEvents.GENVCC_SUCCESS_LONGVOTECASTCODE_GENERATED)
                .electionEvent(enrichedBallot.getElectionEvent().getId()).user("adminID")
                .objectId(verificationCardSetId).additionalInfo("verifc_id", verificationCardId).createLogInfo());
        // encrypt concat(Vote Casting Code, signed Vote Casting Code) with Long
        // Vote Casting Code ->
        // ChoicesCodesGeneratorImpl.generateMapping()
        return longChoiceCode;

    }

    private byte[] generateLongChoiceCodeForVO(final String eeid, final Ballot enrichedBallot,
            final String verificationCardId, final ZpGroupElement partialCode,
            final List<String> attributesWithCorrectness) {
        byte[] longChoiceCode;
        try {
            // Codes SecretKey ->
            // votingCardSetCredentialDataPack.getKeyStore().getSecretKeyEntry()
            // using -> ChoicesCodesGeneratorImpl.generateLongChoiceCode()

            longChoiceCode = choicesCodesGeneration.generateLongChoiceCode(eeid, verificationCardId, partialCode,
                attributesWithCorrectness);
        } catch (Exception e) {
            // error - error generating long Choice Codes
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_LONGCHOICECODES)
                    .electionEvent(enrichedBallot.getElectionEvent().getId()).user("adminID")
                    .objectId(verificationCardSetId).additionalInfo("verifc_id", verificationCardId)
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());
            throw new GenerateVerificationCardCodesException(GENVCC_ERROR_GENERATING_LONGCHOICECODES.getInfo(), e);
        }
        return longChoiceCode;

    }

    private ZpGroupElement generatePartialChoiceCode(final Ballot enrichedBallot, final String verificationCardId,
            final Exponent verificationCardPrivateKeyAsExponent, final ZpGroupElement prePartialChoiceCode) {
        ZpGroupElement partialChoiceCode;
        try {
            // create a Partial Choice Code with representation of the
            // option and verificationCardPrivateKey
            partialChoiceCode =
                partialCodeGenerator.generatePartialCode(prePartialChoiceCode, verificationCardPrivateKeyAsExponent);
        } catch (Exception e) {
            // error - error generating the partial Choice Codes
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_PARTIALCHOICECODES)
                    .electionEvent(enrichedBallot.getElectionEvent().getId()).user("adminID")
                    .objectId(verificationCardSetId).additionalInfo("verifc_id", verificationCardId)
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());
            throw new GenerateVerificationCardCodesException(GENVCC_ERROR_GENERATING_PARTIALCHOICECODES.getInfo(), e);
        }
        return partialChoiceCode;
    }

    private String generateShortChoiceCodeForVO(final Ballot enrichedBallot, final String verificationCardId,
            final Map<String, BigInteger> choiceCodes2BallotVotingOption) {
        String shortChoiceCode;
        try {
            do {
                // create a Short Choice Code, if this value exist then
                // create a new one
                shortChoiceCode = choicesCodesGeneration.generateShortChoiceCode();
            } while (choiceCodes2BallotVotingOption.containsKey(shortChoiceCode));
        } catch (Exception e) {
            // error - error generating short Vote Code
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_SHORTCHOICECODE)
                    .electionEvent(enrichedBallot.getElectionEvent().getId()).objectId(verificationCardSetId)
                    .user("adminID").additionalInfo("verifc_id", verificationCardId)
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());
            throw new GenerateVerificationCardCodesException(
                ConfigGeneratorLogEvents.GENVCC_ERROR_GENERATING_SHORTCHOICECODE.getInfo(), e);
        }
        // success - short Vote Cast Code successfully generated
        logWriter.log(Level.DEBUG,
            new LogContent.LogContentBuilder()
                .logEvent(ConfigGeneratorLogEvents.GENVCC_SUCCESS_SHORTVOTECASTCODE_GENERATED)
                .electionEvent(enrichedBallot.getElectionEvent().getId()).objectId(verificationCardSetId)
                .user("adminID").createLogInfo());
        return shortChoiceCode;
    }

    private byte[] signVoteCastCode(final Ballot enrichedBallot, final String verificationCardId,
            final PrivateKey voteCastCodeSignerPrivateKey, final byte[] verificationIdCardAsBytes,
            final byte[] voteCastingCodeAsBytes) {
        // Sign Vote Casting Code with voteCastCodeSignerPrivateKey
        byte[] signatureOfVoteCastCode;
        try {
            signatureOfVoteCastCode =
                asymmetricService.sign(voteCastCodeSignerPrivateKey, voteCastingCodeAsBytes, verificationIdCardAsBytes);
        } catch (GeneralCryptoLibException e) {
            // error - error while signing the Vote Cast Code
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(ConfigGeneratorLogEvents.GENVCC_ERROR_SIGNING_VOTECASTCODE)
                    .electionEvent(enrichedBallot.getElectionEvent().getId()).user("adminID")
                    .objectId(verificationCardSetId).additionalInfo("verifc_id", verificationCardId)
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());
            throw new GenerateVerificationCardCodesException(GENVCC_ERROR_SIGNING_VOTECASTCODE.getInfo(), e);
        }
        // success - Vote Cast Code successfully signed
        logWriter.log(Level.DEBUG,
            new LogContent.LogContentBuilder().logEvent(ConfigGeneratorLogEvents.GENVCC_SUCCESS_VOTECASTCODE_SIGNED)
                .electionEvent(enrichedBallot.getElectionEvent().getId()).user("adminID")
                .objectId(verificationCardSetId).additionalInfo("verifc_id", verificationCardId).createLogInfo());
        return signatureOfVoteCastCode;

    }

    private VotingCardCredentialDataPack generateVotersCredentialDataPack(final char[] pin, final String credentialId) {
        final VotingCardCredentialDataPack voterCredentialDataPack;

        // create replacementHolder with eeid and credential ID
        final ReplacementsHolder replacementsHolder =
            new ReplacementsHolder(holder.getVotingCardCredentialInputDataPack().getEeid(), credentialId);

        try {
            voterCredentialDataPack =
                votingCardCredentialDataPackGenerator.generate(holder.getVotingCardCredentialInputDataPack(),
                    replacementsHolder, pin, credentialId, holder.getVotingCardSetID(),
                    holder.getCreateVotingCardSetCertificateProperties().getCredentialSignCertificateProperties(),
                    holder.getCreateVotingCardSetCertificateProperties().getCredentialAuthCertificateProperties(),
                    holder.getCredentialCACert(), holder.getElectionCACert());

        } catch (GeneralCryptoLibException | ConfigurationException | IOException e) {
            throw new CreateVotingCardSetException(
                "An error occurred while generating the voters credential data pack: " + e.getMessage(), e);
        }
        return voterCredentialDataPack;
    }

    private String generateVotingCardId(final String electionEventId) {
        String vcid;
        try {
            vcid = uuidGenerator.generate();
            logWriter.log(Level.DEBUG,
                new LogContent.LogContentBuilder().logEvent(ConfigGeneratorLogEvents.GENSVPK_SUCCESS_VCIDS_GENERATED)
                    .user("adminID").electionEvent(electionEventId).objectId(votingCardSetId)
                    .additionalInfo("vcid", vcid).createLogInfo());
        } catch (Exception e) {
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(ConfigGeneratorLogEvents.GENSVPK_ERROR_GENERATING_VCID)
                    .user("adminID").electionEvent(electionEventId).objectId(votingCardSetId)
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());
            throw new GenerateVotingcardIdException(GENSVPK_ERROR_GENERATING_VCID.getInfo(), e);
        }
        return vcid;
    }

    private String generateCredentialId(final CryptoAPIPBKDFDeriver pbkdfDeriver, final String electionEventId,
            final byte[] salt, final String svk) {
        String credentialID;
        try {
            credentialID = new String(getDerivedBytesInHEX(pbkdfDeriver, salt, svk));
        } catch (Exception e) {
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENCREDAT_ERROR_DERIVING_CREDENTIAL_ID).user("adminID")
                    .objectId(votingCardSetId).electionEvent(electionEventId)
                    .additionalInfo("vcs_size", Integer.toString(numberOfVotingCards))
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());
            throw new GenerateCredentialIdException(GENCREDAT_ERROR_DERIVING_CREDENTIAL_ID.getInfo(), e);
        }

        logWriter.log(Level.DEBUG,
            new LogContent.LogContentBuilder()
                .logEvent(ConfigGeneratorLogEvents.GENCREDAT_SUCCESS_CREDENTIAL_ID_DERIVED).user("adminID")
                .electionEvent(electionEventId).objectId(votingCardSetId).additionalInfo("c_id", credentialID)
                .createLogInfo());
        return credentialID;
    }

    private char[] generatePin(final CryptoAPIPBKDFDeriver pbkdfDeriver, final String electionEventId,
            final byte[] salt, final String svk) {
        char[] pin;
        try {
            pin = getDerivedBytesInHEX(pbkdfDeriver, salt, svk);
        } catch (Exception e) {
            // error - error deriving the keystore password
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENSVPK_ERROR_DERIVING_KEYSTORE_PWD).user("adminID")
                    .objectId(votingCardSetId).electionEvent(electionEventId)
                    .additionalInfo("vcs_size", Integer.toString(numberOfVotingCards))
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());
            throw new GenerateSVKVotingCardIdPassKeystoreException(GENSVPK_ERROR_DERIVING_KEYSTORE_PWD.getInfo(), e);
        }

        // success - Keystore passwords successfully derived
        logWriter.log(Level.DEBUG,
            new LogContent.LogContentBuilder().logEvent(ConfigGeneratorLogEvents.GENSVPK_SUCCESS_KEYSTORE_PWDS_DERIVED)
                .electionEvent(electionEventId).objectId(votingCardSetId).user("adminID")
                .additionalInfo("vcs_size", Integer.toString(numberOfVotingCards)).createLogInfo());
        return pin;
    }

    private String generateStartVotingKey(final String electionEventId) {
        String svk;
        try {
            svk = startVotingKeyService.generateStartVotingKey();
        } catch (Exception e) {
            // error - error deriving the keystore password
            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(GENSVPK_ERROR_GENERATING_SVK).electionEvent(electionEventId)
                    .user("adminID").objectId(holder.getVotingCardSetID())
                    .additionalInfo("vcs_size", Integer.toString(numberOfVotingCards))
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());

            throw new CreateVotingCardSetException(GENSVPK_ERROR_GENERATING_SVK.getInfo() + ":" + e.getMessage(), e);
        }

        // success - Start Voting Keys successfully generated
        logWriter.log(Level.INFO,
            new LogContent.LogContentBuilder().logEvent(ConfigGeneratorLogEvents.GENSVPK_SUCCESS_SVK_GENERATED)
                .electionEvent(electionEventId).objectId(votingCardSetId).user("adminID")
                .additionalInfo("vcs_size", Integer.toString(numberOfVotingCards)).createLogInfo());

        return svk;
    }

    private char[] getDerivedBytesInHEX(final CryptoAPIPBKDFDeriver derived, final byte[] salt,
            final String inputString) throws GeneralCryptoLibException {
        final CryptoAPIDerivedKey cryptoAPIDerivedKey = derived.deriveKey(inputString.toCharArray(), salt);
        final byte[] encoded = cryptoAPIDerivedKey.getEncoded();
        return Hex.encodeHex(encoded);
    }

}
