/*
 * $Id$
 * @author aakimov
 * @date   Jul 1, 2016 9:05:26 AM
 *
 * Copyright (C) 2016 Scytl Secure Electronic Voting SA
 *
 * All rights reserved.
 */
package com.scytl.math;

import static com.scytl.math.BigIntegers.isOne;
import static com.scytl.math.BigIntegers.modMultiply;
import static com.scytl.math.BigIntegers.modSquare;
import static java.util.Arrays.fill;

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

/**
 * Implementation of {@link MultiExponentiator} which uses sliding window
 * algorithm from {@link http://dasan.sejong.ac.kr/~chlim/pub/multi_exp.ps}.
 * 
 * @author aakimov
 */
class SlidingWindowMultiExponentiator extends AbstractMultiExponentiator {
    private final int window;
    private final BigInteger[][] oddPowers;

    /**
     * Constructor.
     * 
     * @param bases
     * @param modulus
     * @param maximumBitLengthOfExponent
     */
    public SlidingWindowMultiExponentiator(List<BigInteger> bases,
            BigInteger modulus, int maximumBitLengthOfExponent) {
        super(bases, modulus, maximumBitLengthOfExponent);
        window = chooseWindow(maximumBitLengthOfExponent);
        oddPowers = computeOddPowers(this.bases, modulus, window);
    }

    private static int chooseWindow(int maximumBitLengthOfExponent) {
        int window;
        // the constants are borrowed from the article mentioned above.
        if (maximumBitLengthOfExponent <= 24) {
            window = 2;
        } else if (maximumBitLengthOfExponent <= 80) {
            window = 3;
        } else if (maximumBitLengthOfExponent <= 240) {
            window = 4;
        } else if (maximumBitLengthOfExponent <= 672) {
            window = 5;
        } else {
            window = 6;
        }
        return window;
    }

    private static BigInteger[][] computeOddPowers(BigInteger[] bases,
            BigInteger modulus, int window) {
        BigInteger[][] powers = new BigInteger[bases.length][1 << (window - 1)];
        for (int i = 0; i < powers.length; i++) {
            powers[i][0] = bases[i].mod(modulus);
            BigInteger square = modSquare(powers[i][0], modulus);
            for (int j = 1; j < powers[i].length; j++) {
                powers[i][j] = modMultiply(powers[i][j - 1], square, modulus);
            }
        }
        return powers;
    }

    @Override
    protected BigInteger doExponentiate(BigInteger[] exponents) {
        BigInteger[] factors = computeFactors(exponents);
        return computeMultiExponent(factors);
    }

    private BigInteger[] computeFactors(BigInteger[] exponents) {
        BigInteger[] factors = new BigInteger[maximumBitLengthOfExponent()];
        fill(factors, BigInteger.ONE);
        int mask = (1 << window) - 1;
        for (int i = 0; i < exponents.length; i++) {
            BigInteger exponent = exponents[i];
            int index = 0;
            while (exponent.signum() > 0) {
                int shift = exponent.getLowestSetBit();
                exponent = exponent.shiftRight(shift);
                index += shift;
                int j = (exponent.intValue() & mask) >> 1;
                BigInteger power = oddPowers[i][j];
                factors[index] = modMultiply(factors[index], power, modulus());
                exponent = exponent.shiftRight(window);
                index += window;
            }
        }
        return factors;
    }

    private BigInteger computeMultiExponent(BigInteger[] factors) {
        BigInteger value = BigInteger.ONE;
        for (int i = factors.length - 1; i >= 0; i--) {
            if (!isOne(value)) {
                value = modSquare(value, modulus());
            }
            if (!isOne(factors[i])) {
                value = modMultiply(value, factors[i], modulus());
            }
        }
        return value;
    }
}
