/*
 * $Id$
 * @author aakimov
 * @date   Jul 1, 2016 10:02:04 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 java.math.BigInteger;
import java.util.List;

/**
 * Implementation of {@link MultiExponentiator} which uses the ideas of Brickell
 * et al.'s algorithm from
 * {@link http://dasan.sejong.ac.kr/~chlim/pub/multi_exp.ps}.
 * 
 * @author aakimov
 */
class BrickellMultiExponentiator extends AbstractMultiExponentiator {
    private final int window;
    private final int windowCount;
    private final BigInteger[][] powers;

    /**
     * Constructor.
     * 
     * @param bases
     * @param modulus
     * @param maximumBitLengthOfExponent
     */
    public BrickellMultiExponentiator(List<BigInteger> bases,
            BigInteger modulus, int maximumBitLengthOfExponent) {
        super(bases, modulus, maximumBitLengthOfExponent);
        window = chooseWindow(this.bases.length);
        windowCount = getWindowCount(maximumBitLengthOfExponent, window);
        powers = computePowers(this.bases, modulus, window);
    }

    private static int chooseWindow(int baseCount) {
        int window;
        // the constants are borrowed from the article mentioned above.
        if (baseCount <= 11) {
            window = 2;
        } else if (baseCount <= 25) {
            window = 3;
        } else if (baseCount <= 61) {
            window = 4;
        } else if (baseCount <= 148) {
            window = 5;
        } else if (baseCount <= 324) {
            window = 6;
        } else if (baseCount <= 776) {
            window = 7;
        } else if (baseCount <= 1892) {
            window = 8;
        } else if (baseCount <= 3826) {
            window = 9;
        } else if (baseCount <= 12269) {
            window = 10;
        } else if (baseCount <= 23513) {
            window = 11;
        } else {
            window = 12;
        }
        return window;
    }

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

    @Override
    protected BigInteger doExponentiate(BigInteger[] exponents) {
        int[][] windowedExponents = computeWindowedExponents(exponents);
        return computeMultiExponent(windowedExponents);
    }

    private BigInteger computeMultiExponent(int[][] exponents) {
        BigInteger value = BigInteger.ONE;
        for (int j = windowCount - 1; j >= 0; j--) {
            if (!isOne(value)) {
                for (int k = 0; k < window; k++) {
                    value = modSquare(value, modulus());
                }
            }
            for (int i = 0; i < bases.length; i++) {
                BigInteger factor = powers[i][exponents[i][j]];
                if (!isOne(factor)) {
                    value = modMultiply(value, factor, modulus());
                }
            }
        }
        return value;
    }

    private int[][] computeWindowedExponents(BigInteger[] exponents) {
        int[][] windowedExponents = new int[bases.length][windowCount];
        int mask = (1 << window) - 1;
        for (int i = 0; i < windowedExponents.length; i++) {
            BigInteger exponent = exponents[i];
            for (int j = 0; j < windowedExponents[i].length; j++) {
                windowedExponents[i][j] = exponent.intValue() & mask;
                exponent = exponent.shiftRight(window);
            }
        }
        return windowedExponents;
    }

    private int getWindowCount(int maximumBitLengthOfExponent, int window) {
        int count = maximumBitLengthOfExponent / window;
        if (maximumBitLengthOfExponent % window > 0) {
            count++;
        }
        return count;
    }
}
