/*
 * 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.ElGamalPrivateKey;
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.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.PhiFunctionDecryption;
import com.scytl.cryptolib.proofs.proof.Proof;

/**
 * Decryption proof generator.
 *
 * @param <T>
 *            the type which parameterizes the group elements of the
 *            {@link MathematicalGroup} that will be used during this exchange.
 * @param <E>
 *            the type of the group elements of the {@link MathematicalGroup}
 *            that will be used during this exchange. E must be a type which
 *            extends GroupElement{@code <T>}.
 */
public final class DecryptionProofGenerator<T, E extends GroupElement<T>> {

    private final ElGamalPublicKey _publicKey;

    private final ElGamalPrivateKey _privateKey;

    private final E _gamma;

    private final List<E> _phis;

    private final List<E> _plaintext;

    private final MathematicalGroup<E> _group;

    private final GroupElementsDivider _divider;

    private final ConfigMessageDigestAlgorithmAndProvider _configMessageDigestAlgorithmAndProvider;

    private final ConfigProofHashCharset _configProofHashCharset;

    private final ProofPreComputedValues _preComputedValues;

    /**
     * Default constructor.
     * 
     * @param publicKey
     *            the public key used to ElGamal encrypt the plaintext.
     * @param ciphertext
     *            the ElGamal encrypted plaintext.
     * @param plaintext
     *            the plaintext.
     * @param privateKey
     *            the private key used to ElGamal decrypt the plaintext, which
     *            acts as the 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.
     */
    DecryptionProofGenerator(
            final ElGamalPublicKey publicKey,
            final List<E> ciphertext,
            final List<E> plaintext,
            final ElGamalPrivateKey privateKey,
            final MathematicalGroup<E> group,
            final ConfigMessageDigestAlgorithmAndProvider configMessageDigestAlgorithmAndProvider,
            final ConfigProofHashCharset configProofHashCharset,
            final ProofPreComputedValues preComputedValues)
            throws GeneralCryptoLibException {

        _gamma = ciphertext.get(0);
        _phis = new ArrayList<>(ciphertext.subList(1, ciphertext.size()));

        _publicKey = publicKey;
        _plaintext = new ArrayList<>(plaintext);
        _privateKey = privateKey;
        _group = group;

        _configMessageDigestAlgorithmAndProvider =
            configMessageDigestAlgorithmAndProvider;
        _configProofHashCharset = configProofHashCharset;

        _preComputedValues = preComputedValues;

        _divider = new GroupElementsDivider();
    }

    /**
     * Generates a decryption proof.
     *
     * @return the generated proof.
     * @throws GeneralCryptoLibException
     *             if {@code DecryptionProofGenerator} was initialized by
     *             invalid parameters.
     */
    public Proof generate() throws GeneralCryptoLibException {

        int numKeyElements = _publicKey.getKeys().size();

        PhiFunctionDecryption<T> phiFunctionDecryption =
            new PhiFunctionDecryption<>(_group, numKeyElements,
                buildListOfBaseElements());

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

        return prover.prove(buildListPublicValues(),
            _privateKey.getKeys(),
            Constants.DECRYPTION_PROOF_AUXILIARY_DATA, _preComputedValues);
    }

    @SuppressWarnings("unchecked")
    private List<E> buildListPublicValues()
            throws GeneralCryptoLibException {

        List<E> dividedCiphertext =
            _divider.divide(_phis, _plaintext, _group);

        List<? extends E> publicKeyElements =
            (List<? extends E>) _publicKey.getKeys();

        List<E> publicValues = new ArrayList<>();

        for (int i = 0; i < publicKeyElements.size(); i++) {
            publicValues.add(publicKeyElements.get(i));
            publicValues.add(dividedCiphertext.get(i));
        }
        return publicValues;
    }

    private List<E> buildListOfBaseElements() {

        List<E> list = new ArrayList<>();
        list.add(_group.getGenerator());
        list.add(_gamma);
        return list;
    }
}
