/*
 * 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.ov.mixing.commons.tools;

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

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
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.math.MultiExponentiator;
import com.scytl.math.MultiExponentiatorFactory;
import com.scytl.math.MultiExponentiatorFactoryImpl;

/**
 * Implementation of {@link MultiExponentiation}.
 */
public final class MultiExponentiationImpl implements MultiExponentiation {
    private static final MultiExponentiation INSTANCE =
        new MultiExponentiationImpl(MultiExponentiatorFactoryImpl.getInstance());

    private final MultiExponentiatorFactory factory;

    /**
     * Constructor. For internal use only.
     *
     * @param factory
     */
    MultiExponentiationImpl(final MultiExponentiatorFactory factory) {
        this.factory = factory;
    }

    /**
     * Return the instance.
     *
     * @return the instance.
     */
    public static MultiExponentiation getInstance() {
        return INSTANCE;
    }

    @Override
    public ZpGroupElement computeMultiExpo(final ZpSubgroup group, final ZpGroupElement[] bases,
            final Exponent[] exponents) {

        try {

            List<BigInteger> baseValues = new ArrayList<>(bases.length);
            for (ZpGroupElement base : bases) {
                baseValues.add(base.getValue());
            }
            List<BigInteger> exponentValues = new ArrayList<>(exponents.length);
            int bitLength = 1;
            for (Exponent exponent : exponents) {
                BigInteger exponentValue = exponent.getValue();
                exponentValues.add(exponentValue);
                bitLength = Math.max(bitLength, exponentValue.bitLength());
            }
            MultiExponentiator exponentiator = factory.newMultiExponentiator(baseValues, group.getP(), bitLength);
            BigInteger value = exponentiator.exponentiate(exponentValues);
            return new ZpGroupElement(value, group.getP(), group.getQ());

        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException(e);
        }
    }
}
