/*
 * 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.channel.cc.choicecodes.verification.service;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import org.junit.Assert;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncrypterValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalKeyPair;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.bean.WitnessImpl;
import com.scytl.cryptolib.elgamal.cryptoapi.Witness;
import com.scytl.cryptolib.mathematical.groups.activity.GroupElementsCompressor;
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.cryptolib.proofs.cryptoapi.ProofProverAPI;
import com.scytl.cryptolib.proofs.cryptoapi.ProofVerifierAPI;
import com.scytl.cryptolib.proofs.proof.Proof;

public class PlainTextEqualityProofGenerator {

    private final ZpSubgroup group_g2_q11;

    private final ProofProverAPI proofProverAPI;

    private final ProofVerifierAPI proofVerifierAPI;

    private ElGamalKeyPair elGamalKeyPair;

    public PlainTextEqualityProofGenerator(ProofProverAPI proofProver, ProofVerifierAPI proofVerifier,
            ZpSubgroup group_g2_q11, ElGamalKeyPair elGamalKeyPair)
            throws GeneralCryptoLibException {
        super();
        this.elGamalKeyPair = elGamalKeyPair;
        proofProverAPI = proofProver;
        proofVerifierAPI = proofVerifier;
        this.group_g2_q11 = group_g2_q11;
    }

    @SuppressWarnings("deprecation")
    public Proof createPlaintextEqualityProof(String cipherTextExponentiations,
            ElGamalEncrypterValues encryptGroupElements)
            throws GeneralCryptoLibException {
        Exponent exponent1 = encryptGroupElements.getExponent();
        Exponent exponent2 = elGamalKeyPair.getPrivateKeys().getKeys().get(0);
        BigInteger finalExponentInteger = exponent1.getValue().multiply(exponent2.getValue()).mod(group_g2_q11.getQ());
        Exponent finalExponent = new Exponent(group_g2_q11.getQ(), finalExponentInteger);
        Witness witness = new WitnessImpl(finalExponent);

        String encryptedPartialChoiceCodes = cipherTextExponentiations;
        ElGamalPublicKey elGamalPublicKey = elGamalKeyPair.getPublicKeys();

        final List<ZpGroupElement> primaryCiphertext = getPrimaryCipherText(group_g2_q11, cipherTextExponentiations);

        final List<ZpGroupElement> secondaryCiphertext =
            getSecondaryCiphertext(group_g2_q11, encryptedPartialChoiceCodes);

        Proof proof = proofProverAPI.createPlaintextEqualityProof(primaryCiphertext, elGamalPublicKey, witness,
            secondaryCiphertext, elGamalPublicKey, witness);
        Assert.assertTrue(proofVerifierAPI.verifyPlaintextEqualityProof(primaryCiphertext, elGamalPublicKey,
            secondaryCiphertext, elGamalPublicKey, proof));
        return proof;
    }

    private List<ZpGroupElement> getZpGroupElements(String encryptedChoiceCodes, ZpSubgroup mathematicalGroup)
            throws GeneralCryptoLibException {
        List<ZpGroupElement> elements = new ArrayList<>();
        for (String partial : encryptedChoiceCodes.split(";")) {
            elements.add(new ZpGroupElement(new BigInteger(partial), mathematicalGroup));
        }
        return elements;
    }

    public List<ZpGroupElement> getPrimaryCipherText(final ZpSubgroup mathematicalGroup,
            String cipherTextExponentiations)
            throws GeneralCryptoLibException {
        final List<ZpGroupElement> primaryCiphertext = new ArrayList<>();
        primaryCiphertext
            .add(getZpGroupElementFromExponentiatedOptions(cipherTextExponentiations, mathematicalGroup, 0));
        primaryCiphertext
            .add(getZpGroupElementFromExponentiatedOptions(cipherTextExponentiations, mathematicalGroup, 1));
        return primaryCiphertext;
    }

    public List<ZpGroupElement> getSecondaryCiphertext(final ZpSubgroup mathematicalGroup,
            final String encryptedChoiceCodes)
            throws GeneralCryptoLibException {
        // Retrieval of D0 and D'1 as result of compressing the D1-Dn elements
        // of the
        // partial choice codes
        final List<ZpGroupElement> zpGroupElements = getZpGroupElements(encryptedChoiceCodes, mathematicalGroup);
        final ZpGroupElement D0 = zpGroupElements.get(0);
        final ZpGroupElement D1prima =
            getCompressedList(mathematicalGroup, zpGroupElements.subList(1, zpGroupElements.size()));
        final List<ZpGroupElement> secondaryCiphertext = new ArrayList<>();
        secondaryCiphertext.add(D0);
        secondaryCiphertext.add(D1prima);
        return secondaryCiphertext;
    }

    private ZpGroupElement getCompressedList(ZpSubgroup mathematicalGroup, List<ZpGroupElement> elements)
            throws GeneralCryptoLibException {
        GroupElementsCompressor<ZpGroupElement> compressor = new GroupElementsCompressor<>();
        return compressor.compress(elements);
    }

    public ZpGroupElement getZpGroupElement(final ZpSubgroup mathematicalGroup, final int ck, String encryptedOptions)
            throws GeneralCryptoLibException {
        final BigInteger Ck = new BigInteger(getCkFromEncryptedOptions(encryptedOptions, ck));
        return new ZpGroupElement(Ck, mathematicalGroup);
    }

    public ZpGroupElement getZpGroupElementFromExponentiatedOptions(final String cipherTextExponentiations,
            final ZpSubgroup mathematicalGroup, final int ck)
            throws GeneralCryptoLibException {
        final BigInteger Ck = new BigInteger(getCkFromEncryptedOptions(cipherTextExponentiations, ck));
        return new ZpGroupElement(Ck, mathematicalGroup);
    }

    private String getCkFromEncryptedOptions(String encryptedOptions, int ck) {
        return encryptedOptions.split(";")[ck];
    }

}
