/*
 * 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.config.commands.voters.datapacks.generators;

import static com.scytl.products.ov.config.commons.Constants.CHOICE_CODES_KEY_DELIMITER;

import java.io.IOException;
import java.security.KeyPair;
import java.util.Properties;

import org.apache.commons.configuration.ConfigurationException;
import org.codehaus.jettison.json.JSONObject;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.api.scytl.keystore.ScytlKeyStoreServiceAPI;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomString;
import com.scytl.products.oscore.logging.api.domain.Level;
import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardSetCredentialDataPack;
import com.scytl.products.ov.config.commands.voters.datapacks.beans.VerificationCardSetCredentialInputDataPack;
import com.scytl.products.ov.config.exceptions.CreateVotingCardSetException;
import com.scytl.products.ov.config.exceptions.specific.GenerateCredentialDataException;
import com.scytl.products.ov.config.logevents.ConfigGeneratorLogEvents;
import com.scytl.products.ov.datapacks.beans.CertificateParameters;
import com.scytl.products.ov.datapacks.generators.X509CertificateGenerator;
import com.scytl.products.ov.utils.FingerprintGenerator;

public class VerificationCardSetCredentialDataPackGenerator extends ElGamalCredentialDataPackGenerator {

    private final FingerprintGenerator fingerprintGenerator;

    public VerificationCardSetCredentialDataPackGenerator(final AsymmetricServiceAPI asymmetricService,
            final X509CertificateGenerator certificateGenerator, final ScytlKeyStoreServiceAPI storesService,
            final CryptoAPIRandomString cryptoRandomString, final ElGamalServiceAPI elGamalService,
            final PrimitivesServiceAPI primitivesService) {
        super(asymmetricService, cryptoRandomString, certificateGenerator, storesService, elGamalService);

        fingerprintGenerator = new FingerprintGenerator(primitivesService);
    }

    public VerificationCardSetCredentialDataPack generate(
            final VerificationCardSetCredentialInputDataPack inputDataPack, final String verificationCardSetID,
            final String choiceCodesEncryptionKeyAsConcatenatedString,
            final Properties verificationCardSetCerificateProperties) throws ConfigurationException, IOException {

        final VerificationCardSetCredentialDataPack dataPack = new VerificationCardSetCredentialDataPack();

        KeyPair verificationCardIssuerKeypair;
        try {

            verificationCardIssuerKeypair = asymmetricService.getKeyPairForSigning();

            String verificationCardIssuerKeypairFingerprint =
                fingerprintGenerator.generate(verificationCardIssuerKeypair.getPublic());

            logWriter.log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCD_SUCCESS_GENERATING_VERIFICATION_CARD_ISSUER_KEYPAIR)
                    .electionEvent(inputDataPack.getEeid()).user("adminID")
                    .additionalInfo("verifcs_id", verificationCardSetID)
                    .additionalInfo("pubkey_id", verificationCardIssuerKeypairFingerprint).createLogInfo());

            dataPack.setVerificationCardIssuerKeyPair(verificationCardIssuerKeypair);

        } catch (Exception e) {

            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCD_ERROR_GENERATING_VERIFICATION_CARD_ISSUER_KEYPAIR)
                    .electionEvent(inputDataPack.getEeid()).user("adminID")
                    .additionalInfo("verifcs_id", verificationCardSetID).additionalInfo("err_desc", e.getMessage())
                    .createLogInfo());

            throw new GenerateCredentialDataException(e);
        }

        final CertificateParameters verificationCardIssuerCertificateParameters = getCertificateParameters(
            inputDataPack.getVerificationCardSetProperties(), inputDataPack.getStartDate(), inputDataPack.getEndDate(),
            inputDataPack.getReplacementsHolder(), verificationCardSetCerificateProperties);

        CryptoAPIX509Certificate verificationCardIssuerCert;
        try {
            verificationCardIssuerCert = createX509Certificate(inputDataPack,
                verificationCardIssuerCertificateParameters, verificationCardIssuerKeypair);

            logWriter
                .log(Level.INFO,
                    new LogContent.LogContentBuilder()
                        .logEvent(
                            ConfigGeneratorLogEvents.GENVCD_SUCCESS_GENERATING_VERIFICATION_CARD_ISSUER_CERIFICATE)
                        .electionEvent(inputDataPack.getEeid()).user("adminID")
                        .additionalInfo("cert_cn", verificationCardIssuerCert.getSubjectDn().getCommonName())
                        .additionalInfo("cert_sn", verificationCardIssuerCert.getSerialNumber().toString())
                        .createLogInfo());

        } catch (Exception e) {

            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCD_ERROR_GENERATING_VERIFICATION_CARD_ISSUER_CERIFICATE)
                    .electionEvent(inputDataPack.getEeid()).user("adminID")
                    .additionalInfo("cert_cn",
                        verificationCardIssuerCertificateParameters.getUserSubjectDn().getCommonName())
                    .additionalInfo("err_desc", e.getMessage()).createLogInfo());

            throw new GenerateCredentialDataException(e);
        }

        dataPack.setVerificationCardIssuerCert(verificationCardIssuerCert);

        String[] choiceCodesEncryptionKeys =
            choiceCodesEncryptionKeyAsConcatenatedString.split(CHOICE_CODES_KEY_DELIMITER);
        ElGamalPublicKey combinedChoiceCodesEncryptionPublicKey;
        ElGamalPublicKey[] nonCombinedChoiceCodesEncryptionPublicKeys =
            new ElGamalPublicKey[choiceCodesEncryptionKeys.length];

        try {

            JSONObject jsonObject = new JSONObject(choiceCodesEncryptionKeys[0]);
            String choiceCodesEncryptionPublicKeyJson = jsonObject.getString("publicKey");
            ElGamalPublicKey choiceCodesEncryptionPublicKey =
                ElGamalPublicKey.fromJson(choiceCodesEncryptionPublicKeyJson);
            nonCombinedChoiceCodesEncryptionPublicKeys[0] = choiceCodesEncryptionPublicKey;

            combinedChoiceCodesEncryptionPublicKey = choiceCodesEncryptionPublicKey;
            for (int i = 1; i < choiceCodesEncryptionKeys.length; i++) {
                jsonObject = new JSONObject(choiceCodesEncryptionKeys[i]);
                choiceCodesEncryptionPublicKeyJson = jsonObject.getString("publicKey");
                choiceCodesEncryptionPublicKey = ElGamalPublicKey.fromJson(choiceCodesEncryptionPublicKeyJson);
                nonCombinedChoiceCodesEncryptionPublicKeys[i] = choiceCodesEncryptionPublicKey;
                combinedChoiceCodesEncryptionPublicKey =
                    combinedChoiceCodesEncryptionPublicKey.combineWith(choiceCodesEncryptionPublicKey);
            }

            String choicesCodeEncryptionPublicKeyFingerprint =
                fingerprintGenerator.generate(combinedChoiceCodesEncryptionPublicKey);

            logWriter.log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCD_SUCCESS_GENERATING_CHOICES_CODES_KEYPAIR)
                    .electionEvent(inputDataPack.getEeid()).user("adminID")
                    .additionalInfo("verifcs_id", verificationCardSetID)
                    .additionalInfo("publickey_id", choicesCodeEncryptionPublicKeyFingerprint).createLogInfo());

        } catch (Exception e) {

            logWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder()
                    .logEvent(ConfigGeneratorLogEvents.GENVCD_ERROR_GENERATING_CHOICES_CODES_KEYPAIR)
                    .electionEvent(inputDataPack.getEeid()).user("adminID")
                    .additionalInfo("verifcs_id", verificationCardSetID).additionalInfo("err_desc", e.getMessage())
                    .createLogInfo());

            throw new CreateVotingCardSetException(
                "An error occurred while trying to set the choices codes ElGamal public key", e);
        }

        dataPack.setChoiceCodesEncryptionPublicKey(combinedChoiceCodesEncryptionPublicKey);
        dataPack.setNonCombinedChoiceCodesEncryptionPublicKeys(nonCombinedChoiceCodesEncryptionPublicKeys);

        return dataPack;
    }
}
