/*
 * 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.commons.exponentiation;

import static java.util.Objects.requireNonNull;

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

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.proofs.ProofsServiceAPI;
import com.scytl.cryptolib.elgamal.bean.ElGamalComputationsValues;
import com.scytl.cryptolib.elgamal.bean.WitnessImpl;
import com.scytl.cryptolib.elgamal.cryptoapi.Witness;
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.proof.Proof;

/**
 * Implementation of {@link ExponentiationService}.
 */
public final class ExponentiationServiceImpl
        implements ExponentiationService {
    private final ProofsServiceAPI proofsService;

    /**
     * Constructor.
     * 
     * @param proofsService
     */
    public ExponentiationServiceImpl(ProofsServiceAPI proofsService) {
        this.proofsService = proofsService;
    }

    @Override
    public PowersAndProof<BigInteger> exponentiateCleartexts(
            List<BigInteger> cleartexts, Exponent exponent,
            ZpSubgroup group)
            throws GeneralCryptoLibException {
        requireNonNull(cleartexts, "Cleartexts is null.");
        requireNonNull(exponent, "Exponent is null.");
        requireNonNull(group, "Group is null.");

        List<BigInteger> powers = new ArrayList<>(cleartexts.size());
        List<ZpGroupElement> baseElements =
            new ArrayList<>(cleartexts.size() + 1);
        baseElements.add(group.getGenerator());
        List<ZpGroupElement> powerElements =
            new ArrayList<>(cleartexts.size() + 1);
        powerElements.add(group.getGenerator().exponentiate(exponent));
        for (BigInteger cleartext : cleartexts) {
            ZpGroupElement baseElement =
                new ZpGroupElement(cleartext, group);
            ZpGroupElement powerElement =
                baseElement.exponentiate(exponent);
            powers.add(powerElement.getValue());
            baseElements.add(baseElement);
            powerElements.add(powerElement);
        }

        Witness witness = new WitnessImpl(exponent);
        ProofProverAPI prover = proofsService.createProofProverAPI(group);
        Proof proof = prover.createExponentiationProof(powerElements,
            baseElements, witness);
        return new PowersAndProof<>(powers, proof);
    }

    @Override
    public PowersAndProof<ElGamalComputationsValues> exponentiateCiphertexts(
            List<ElGamalComputationsValues> ciphertexts, Exponent exponent,
            ZpSubgroup group)
            throws GeneralCryptoLibException {
        requireNonNull(ciphertexts, "Ciphertexts is null.");
        requireNonNull(exponent, "Exponent is null.");
        requireNonNull(group, "Group is null.");

        List<ElGamalComputationsValues> powers = new ArrayList<>();
        List<ZpGroupElement> baseElements = new ArrayList<>();
        baseElements.add(group.getGenerator());
        List<ZpGroupElement> powerElements = new ArrayList<>();
        powerElements.add(group.getGenerator().exponentiate(exponent));
        for (ElGamalComputationsValues ciphertext : ciphertexts) {
            List<ZpGroupElement> baseValues = ciphertext.getValues();
            List<ZpGroupElement> powerValues =
                new ArrayList<>(baseValues.size());
            for (ZpGroupElement baseValue : baseValues) {
                powerValues.add(baseValue.exponentiate(exponent));
            }
            powers.add(new ElGamalComputationsValues(powerValues));
            baseElements.addAll(baseValues);
            powerElements.addAll(powerValues);
        }

        Witness witness = new WitnessImpl(exponent);
        ProofProverAPI prover = proofsService.createProofProverAPI(group);
        Proof proof = prover.createExponentiationProof(powerElements,
            baseElements, witness);
        return new PowersAndProof<>(powers, proof);
    }
}
