/*
 * 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.util.ArrayList;
import java.util.List;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.mathematical.groups.GroupElement;
import com.scytl.cryptolib.mathematical.groups.MathematicalGroup;
import com.scytl.cryptolib.mathematical.groups.activity.GroupElementsDivider;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.primitives.messagedigest.configuration.ConfigMessageDigestAlgorithmAndProvider;
import com.scytl.cryptolib.proofs.bean.ProofPreComputedValues;
import com.scytl.cryptolib.proofs.maurer.configuration.ConfigProofHashCharset;
import com.scytl.cryptolib.proofs.maurer.configuration.Constants;
import com.scytl.cryptolib.proofs.maurer.function.PhiFunctionPlaintextEquality;
import com.scytl.cryptolib.proofs.proof.Proof;

/**
 * Generator of the zero knowledge proof for two ciphertexts that were each
 * generated with a different key pair and random exponent from the same
 * plaintext.
 * 
 * @param <T>
 *            the type of the mathematical group elements values to be used
 *            during this exchange.
 * @param <E>
 *            the type of the mathematical group elements that will be used
 *            during this exchange. E must be a type which extends GroupElement
 *            {@code <T>}.
 */
public final class PlaintextEqualityProofGenerator<T, E extends GroupElement<T>> {

    private final E _primaryCiphertextGamma;

    private final List<E> _primaryCiphertextPhis;

    private final ElGamalPublicKey _primaryPublicKey;

    private final Exponent _primaryExponent;

    private final E _secondaryCiphertextGamma;

    private final List<E> _secondaryCiphertextPhis;

    private final ElGamalPublicKey _secondaryPublicKey;

    private final Exponent _secondaryExponent;

    private final MathematicalGroup<E> _group;

    private final ConfigMessageDigestAlgorithmAndProvider _configMessageDigestAlgorithmAndProvider;

    private final ConfigProofHashCharset _configProofHashCharset;

    private final ProofPreComputedValues _preComputedValues;

    private final GroupElementsDivider _divider;

    /**
     * Default constructor.
     * 
     * @param primaryCiphertext
     *            the primary ElGamal encrypted plaintext.
     * @param primaryPublicKey
     *            the primary ElGamal public key.
     * @param primaryExponent
     *            the primary ElGamal encryption exponent, which acts as a
     *            witness for this proof.
     * @param secondaryCiphertext
     *            the secondary ElGamal encrypted plaintext.
     * @param secondaryPublicKey
     *            the secondary ElGamal public key.
     * @param secondaryExponent
     *            the primary ElGamal encryption exponent, which acts as a
     *            witness for this proof.
     * @param group
     *            the Zp subgroup used for ElGamal encryption.
     * @param configMessageDigestAlgorithmAndProvider
     *            the algorithm and provider of the message digest used to
     *            generate the proof.
     * @param configProofHashCharset
     *            the character set of the hash used to generate the proof.
     * @param preComputedValues
     *            the pre-computed values used to generate the proof.
     * @throws GeneralCryptoLibException
     *             if the phi function cannot be created.
     */
    PlaintextEqualityProofGenerator(
            final List<E> primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey,
            final Exponent primaryExponent,
            final List<E> secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey,
            final Exponent secondaryExponent,
            final MathematicalGroup<E> group,
            final ConfigMessageDigestAlgorithmAndProvider configMessageDigestAlgorithmAndProvider,
            final ConfigProofHashCharset configProofHashCharset,
            final ProofPreComputedValues preComputedValues)
            throws GeneralCryptoLibException {

        _primaryCiphertextGamma = primaryCiphertext.get(0);
        _primaryCiphertextPhis =
            new ArrayList<>(primaryCiphertext.subList(1,
                primaryCiphertext.size()));
        _primaryPublicKey = primaryPublicKey;
        _primaryExponent = primaryExponent;

        _secondaryCiphertextGamma = secondaryCiphertext.get(0);
        _secondaryCiphertextPhis =
            new ArrayList<>(secondaryCiphertext.subList(1,
                secondaryCiphertext.size()));
        _secondaryPublicKey = secondaryPublicKey;
        _secondaryExponent = secondaryExponent;

        _group = group;

        _configMessageDigestAlgorithmAndProvider =
            configMessageDigestAlgorithmAndProvider;
        _configProofHashCharset = configProofHashCharset;

        _preComputedValues = preComputedValues;

        _divider = new GroupElementsDivider();
    }

    /**
     * Generates the plaintext equality zero knowledge proof of knowledge.
     * 
     * @return the plaintext equality zero knowledge proof of knowledge, as an
     *         object of type {@link Proof}.
     * @throws GeneralCryptoLibException
     *             PlaintextEqualityProofGenerator if
     *             {@code PlaintextEqualityProofGenerator} was initialized by
     *             invalid arguments.
     */
    public Proof generate() throws GeneralCryptoLibException {

        PhiFunctionPlaintextEquality<T> phiFunctionPlaintextEquality =
            new PhiFunctionPlaintextEquality<>(_group,
                buildListBaseElements());

        Prover<T, E> prover =
            new Prover<>(_group, phiFunctionPlaintextEquality,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset);

        return prover.prove(buildListPublicValues(), buildListExponents(),
            Constants.PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA,
            _preComputedValues);
    }

    @SuppressWarnings("unchecked")
    private List<E> buildListBaseElements() {

        List<E> invertedSecondaryPublicKey =
            buildInvertedSecondaryPublicKey();

        List<E> baseElements = new ArrayList<>();
        baseElements.add(_group.getGenerator());
        baseElements.addAll((List<? extends E>) _primaryPublicKey
            .getKeys());
        baseElements.addAll(invertedSecondaryPublicKey);

        return baseElements;
    }

    @SuppressWarnings("unchecked")
    private List<E> buildInvertedSecondaryPublicKey() {

        List<E> invertedSecondaryPublicKey = new ArrayList<>();
        List<? extends E> secondaryPublicKeyElements =
            (List<? extends E>) _secondaryPublicKey.getKeys();
        for (E element : secondaryPublicKeyElements) {
            invertedSecondaryPublicKey.add((E) element.invert());
        }

        return invertedSecondaryPublicKey;
    }

    private List<E> buildListPublicValues()
            throws GeneralCryptoLibException {

        List<E> dividedSubCiphertext =
            _divider.divide(_primaryCiphertextPhis,
                _secondaryCiphertextPhis, _group);

        List<E> publicValues = new ArrayList<>();
        publicValues.add(_primaryCiphertextGamma);
        publicValues.add(_secondaryCiphertextGamma);
        publicValues.addAll(dividedSubCiphertext);

        return publicValues;
    }

    private List<Exponent> buildListExponents() {

        List<Exponent> exponents = new ArrayList<>();
        exponents.add(_primaryExponent);
        exponents.add(_secondaryExponent);

        return exponents;
    }
}
