/*
 * 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.cryptolib.proofs.maurer.factory;

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

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.cryptolib.mathematical.groups.MathematicalGroup;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.primitives.messagedigest.configuration.ConfigMessageDigestAlgorithmAndProvider;
import com.scytl.cryptolib.proofs.cryptoapi.ProofVerifierAPI;
import com.scytl.cryptolib.proofs.maurer.configuration.ConfigProofHashCharset;
import com.scytl.cryptolib.proofs.proof.Proof;

/**
 * Implementation of {@link ProofVerifierAPI}, which allows for the verification
 * of several types of proofs.
 */
public final class ZpSubgroupProofVerifier implements ProofVerifierAPI {

    private final ZpSubgroup _group;

    private final ConfigMessageDigestAlgorithmAndProvider _configMessageDigestAlgorithmAndProvider;

    private final ConfigProofHashCharset _configProofHashCharset;

    ZpSubgroupProofVerifier(final MathematicalGroup<?> group,
            final ConfigMessageDigestAlgorithmAndProvider configMessageDigestAlgorithmAndProvider,
            final ConfigProofHashCharset configProofHashCharset)
            throws GeneralCryptoLibException {

        _group = (ZpSubgroup) group;
        _configMessageDigestAlgorithmAndProvider =
            configMessageDigestAlgorithmAndProvider;
        _configProofHashCharset = configProofHashCharset;
    }

    @Override
    public boolean verifySchnorrProof(
            final ZpGroupElement exponentiatedGenerator,
            final String voterID, final String electionEventID,
            final Proof proof) throws GeneralCryptoLibException {

        validateSchnorrProofVerifierInput(exponentiatedGenerator, voterID,
            electionEventID, proof);

        SchnorrProofVerifier<BigInteger, ZpGroupElement> verifier =
            new SchnorrProofVerifier<>(exponentiatedGenerator, voterID,
                electionEventID, proof, _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset);

        return verifier.verify();
    }

    @Override
    public boolean verifyExponentiationProof(
            final List<ZpGroupElement> exponentiatedElements,
            final List<ZpGroupElement> baseElements, final Proof proof)
            throws GeneralCryptoLibException {

        validateExponentiationProofVerifierInput(exponentiatedElements,
            baseElements, proof);

        ExponentiationProofVerifier<BigInteger, ZpGroupElement> verifier =
            new ExponentiationProofVerifier<>(exponentiatedElements,
                baseElements, proof, _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset);

        return verifier.verify();
    }

    @Override
    public boolean verifyDecryptionProof(final ElGamalPublicKey publicKey,
            final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext, final Proof proof)
            throws GeneralCryptoLibException {

        validateDecryptionProofVerifierInput(publicKey, ciphertext,
            plaintext, proof);

        DecryptionProofVerifier<BigInteger, ZpGroupElement> verifier =
            new DecryptionProofVerifier<>(publicKey,
                ciphertext.getElements(), plaintext, proof, _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset);

        return verifier.verify();
    }

    @Override
    public boolean verifyPlaintextProof(final ElGamalPublicKey publicKey,
            final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext, final Proof proof)
            throws GeneralCryptoLibException {

        validatePlaintextProofVerifierInput(publicKey, ciphertext,
            plaintext, proof);

        PlaintextProofVerifier<BigInteger, ZpGroupElement> verifier =
            new PlaintextProofVerifier<>(publicKey,
                ciphertext.getElements(), plaintext, proof, _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset);

        return verifier.verify();
    }

    @Override
    public boolean verifySimplePlaintextEqualityProof(
            final Ciphertext primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey,
            final Ciphertext secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey, final Proof proof)
            throws GeneralCryptoLibException {

        validateSimplePlaintextEqualityProofVerifierInput(
            primaryCiphertext, primaryPublicKey, secondaryCiphertext,
            secondaryPublicKey, proof);

        SimplePlaintextEqualityProofVerifier<BigInteger, ZpGroupElement> verifier =
            new SimplePlaintextEqualityProofVerifier<>(
                primaryCiphertext.getElements(), primaryPublicKey,
                secondaryCiphertext.getElements(), secondaryPublicKey,
                proof, _group, _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset);

        return verifier.verify();
    }

    /**
     * @deprecated
     *
     */
    @Override
    @Deprecated
    public boolean verifyPlaintextEqualityProof(
            final List<ZpGroupElement> primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey,
            final List<ZpGroupElement> secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey, final Proof proof)
            throws GeneralCryptoLibException {

        Validate.notNullOrEmptyAndNoNulls(primaryCiphertext,
            "Primary ciphertext");
        Validate.notNull(primaryPublicKey, "Primary ElGamal public key");
        Validate.notNullOrEmptyAndNoNulls(secondaryCiphertext,
            "Secondary ciphertext");
        Validate.notNull(secondaryPublicKey,
            "Secondary ElGamal public key");
        Validate.notNull(proof, "Plaintext equality proof");
        int primaryCiphertextLength = primaryCiphertext.size();
        int primaryPublicKeyLength = primaryPublicKey.getKeys().size();
        int secondaryCiphertextLength = secondaryCiphertext.size();
        int secondaryPublicKeyLength = secondaryPublicKey.getKeys().size();
        Validate.equals(primaryCiphertextLength, secondaryCiphertextLength,
            "Primary ciphertext length", "Secondary ciphertext length");
        Validate.equals(primaryPublicKeyLength, secondaryPublicKeyLength,
            "Primary ElGamal public key length",
            "Secondary ElGamal public key length");
        Validate.equals(primaryCiphertextLength,
            primaryPublicKeyLength + 1, "Ciphertext length",
            "ElGamal public key length plus 1");

        PlaintextEqualityProofVerifier<BigInteger, ZpGroupElement> verifier =
            new PlaintextEqualityProofVerifier<>(primaryCiphertext,
                primaryPublicKey, secondaryCiphertext, secondaryPublicKey,
                proof, _group, _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset);

        return verifier.verify();
    }

    @Override
    public boolean verifyPlaintextEqualityProof(
            final Ciphertext primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey,
            final Ciphertext secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey, final Proof proof)
            throws GeneralCryptoLibException {

        validatePlaintextEqualityProofVerifierInput(primaryCiphertext,
            primaryPublicKey, secondaryCiphertext, secondaryPublicKey,
            proof);

        PlaintextEqualityProofVerifier<BigInteger, ZpGroupElement> verifier =
            new PlaintextEqualityProofVerifier<>(
                primaryCiphertext.getElements(), primaryPublicKey,
                secondaryCiphertext.getElements(), secondaryPublicKey,
                proof, _group, _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset);

        return verifier.verify();
    }

    @Override
    public boolean verifyPlaintextExponentEqualityProof(
            final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext, final Proof proof)
            throws GeneralCryptoLibException {

        validatePlaintextExponentEqualityProofVerifierInput(ciphertext,
            plaintext, proof);

        PlaintextExponentEqualityProofVerifier<BigInteger, ZpGroupElement> verifier =
            new PlaintextExponentEqualityProofVerifier<>(
                ciphertext.getElements(), plaintext, proof, _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset);

        return verifier.verify();
    }

    @Override
    public boolean verifyORProof(Ciphertext ciphertext,
            ElGamalPublicKey publicKey, List<ZpGroupElement> elements,
            String data, Proof proof) throws GeneralCryptoLibException {
        validateQRProofVerifierInput(ciphertext, publicKey, elements, data,
            proof);
        return new ORProofVerifier(ciphertext, publicKey, elements, data,
            proof, _group, _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset).verify();
    }

    private void validateQRProofVerifierInput(Ciphertext ciphertext,
            ElGamalPublicKey publicKey, List<ZpGroupElement> elements,
            String data, Proof proof) throws GeneralCryptoLibException {
        Validate.notNull(ciphertext, "Ciphertext");
        Validate.notNull(publicKey, "ElGamal public key");
        Validate.notNullOrEmptyAndNoNulls(elements, "Elements");
        Validate.notNull(data, "Data");
        Validate.notNull(proof, "Proof");
        Validate.equals(ciphertext.size(), 2, "elements size",
            "single key ciphertext size");
        Validate.equals(publicKey.getKeys().size(), 1, "key size",
            "single key size");
        Validate.equals(publicKey.getGroup(), _group, "key group",
            "prover's group");
        Validate.equals(proof.getHashValue().getQ(), _group.getQ(),
            "hash Q", "group Q");
        Validate.equals(proof.getValuesList().size(), elements.size() * 2,
            "proof size", "double elements size");
    }

    private void validateSchnorrProofVerifierInput(
            final ZpGroupElement exponentiatedGenerator,
            final String voterID, final String electionEventID,
            final Proof proof) throws GeneralCryptoLibException {

        Validate.notNull(exponentiatedGenerator,
            "Exponentiated generator element");
        Validate.notNullOrBlank(voterID, "Voter ID");
        Validate.notNullOrBlank(electionEventID, "Election event ID");
        Validate.notNull(proof, "Schnorr proof");
    }

    private void validateExponentiationProofVerifierInput(
            final List<ZpGroupElement> exponentiatedElements,
            final List<ZpGroupElement> baseElements, final Proof proof)
            throws GeneralCryptoLibException {

        Validate.notNullOrEmptyAndNoNulls(exponentiatedElements,
            "List of exponentiated elements");
        Validate.notNullOrEmptyAndNoNulls(baseElements,
            "List of base elements");
        Validate.notNull(proof, "Exponentiation proof");
        Validate.equals(exponentiatedElements.size(), baseElements.size(),
            "Number of exponentiated elements", "number of base elements");
    }

    private void validateDecryptionProofVerifierInput(
            final ElGamalPublicKey publicKey, final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext, final Proof proof)
            throws GeneralCryptoLibException {

        Validate.notNull(publicKey, "ElGamal public key");
        Validate.notNull(ciphertext, "Ciphertext");
        Validate.notNullOrEmptyAndNoNulls(plaintext, "Plaintext");
        Validate.notNull(proof, "Decryption proof");
        int publicKeyLength = publicKey.getKeys().size();
        int ciphertextLength = ciphertext.size();
        int plaintextLength = plaintext.size();
        Validate.equals(ciphertextLength, publicKeyLength + 1,
            "Ciphertext length", "ElGamal public key length plus 1");
        Validate.equals(plaintextLength, publicKeyLength,
            "Plaintext length", "ElGamal public key length");
    }

    private void validatePlaintextProofVerifierInput(
            final ElGamalPublicKey publicKey, final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext, final Proof proof)
            throws GeneralCryptoLibException {

        Validate.notNull(publicKey, "ElGamal public key");
        Validate.notNull(ciphertext, "Ciphertext");
        Validate.notNullOrEmptyAndNoNulls(plaintext, "Plaintext");
        Validate.notNull(proof, "Plaintext proof");
        int publicKeyLength = publicKey.getKeys().size();
        int ciphertextLength = ciphertext.size();
        int plaintextLength = plaintext.size();
        Validate.equals(ciphertextLength, publicKeyLength + 1,
            "Ciphertext length", "ElGamal public key length plus 1");
        Validate.equals(plaintextLength, publicKeyLength,
            "Plaintext length", "ElGamal public key length");
    }

    private void validateSimplePlaintextEqualityProofVerifierInput(
            final Ciphertext primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey,
            final Ciphertext secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey, final Proof proof)
            throws GeneralCryptoLibException {

        Validate.notNull(primaryCiphertext, "Primary ciphertext");
        Validate.notNull(primaryPublicKey, "Primary ElGamal public key");
        Validate.notNull(secondaryCiphertext, "Secondary ciphertext");
        Validate.notNull(secondaryPublicKey,
            "Secondary ElGamal public key");
        Validate.notNull(proof, "Simple plaintext equality proof");
        int primaryCiphertextLength = primaryCiphertext.size();
        int primaryPublicKeyLength = primaryPublicKey.getKeys().size();
        int secondaryCiphertextLength = secondaryCiphertext.size();
        int secondaryPublicKeyLength = secondaryPublicKey.getKeys().size();
        Validate.equals(primaryCiphertextLength, secondaryCiphertextLength,
            "Primary ciphertext length", "secondary ciphertext length");
        Validate.equals(primaryPublicKeyLength, secondaryPublicKeyLength,
            "Primary ElGamal public key length",
            "secondary ElGamal public key length");
        Validate.equals(primaryCiphertextLength,
            primaryPublicKeyLength + 1, "Ciphertext length",
            "ElGamal public key length plus 1");
    }

    private void validatePlaintextEqualityProofVerifierInput(
            final Ciphertext primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey,
            final Ciphertext secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey, final Proof proof)
            throws GeneralCryptoLibException {

        Validate.notNull(primaryCiphertext, "Primary ciphertext");
        Validate.notNull(primaryPublicKey, "Primary ElGamal public key");
        Validate.notNull(secondaryCiphertext, "Secondary ciphertext");
        Validate.notNull(secondaryPublicKey,
            "Secondary ElGamal public key");
        Validate.notNull(proof, "Plaintext equality proof");
        int primaryCiphertextLength = primaryCiphertext.size();
        int primaryPublicKeyLength = primaryPublicKey.getKeys().size();
        int secondaryCiphertextLength = secondaryCiphertext.size();
        int secondaryPublicKeyLength = secondaryPublicKey.getKeys().size();
        Validate.equals(primaryCiphertextLength, secondaryCiphertextLength,
            "Primary ciphertext length", "secondary ciphertext length");
        Validate.equals(primaryPublicKeyLength, secondaryPublicKeyLength,
            "Primary ElGamal public key length",
            "secondary ElGamal public key length");
        Validate.equals(primaryCiphertextLength,
            primaryPublicKeyLength + 1, "Ciphertext length",
            "ElGamal public key length plus 1");
    }

    private void validatePlaintextExponentEqualityProofVerifierInput(
            final Ciphertext ciphertext,
            final List<ZpGroupElement> baseElements, final Proof proof)
            throws GeneralCryptoLibException {

        Validate.notNull(ciphertext, "Ciphertext");
        Validate.notNullOrEmptyAndNoNulls(baseElements,
            "List of base elements");
        int numPhiElements = ciphertext.size() - 1;
        int expectedNumBaseElements = 2 * numPhiElements + 1;
        Validate.notNull(proof, "Plaintext exponent equality proof");
        Validate.equals(baseElements.size(), expectedNumBaseElements,
            "Length of list of base elements",
            "twice the ciphertext length minus 1");
    }
}
