/*
 * 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.primitives.ffc;

import static com.scytl.math.BigIntegers.modPow;
import static java.lang.System.arraycopy;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.ffc.cryptoapi.FFCPrimes;
import com.scytl.cryptolib.primitives.messagedigest.factory.CryptoMessageDigest;
import com.scytl.cryptolib.primitives.messagedigest.factory.MessageDigestFactory;

/**
 * Implementation of {@link GFactory}.
 */
class GFactoryImpl implements GFactory {
    private static final byte[] GGEN =
        "ggen".getBytes(StandardCharsets.UTF_8);

    private final MessageDigestFactory digestFactory;

    /**
     * Constructor.
     * 
     * @param digestFactory
     */
    public GFactoryImpl(MessageDigestFactory digestFactory) {
        this.digestFactory = digestFactory;
    }

    private static byte[] newU(FFCPrimes primes, int index) {
        byte[] firstSeed = primes.firstSeed().toByteArray();
        byte[] pSeed = primes.p().seed().toByteArray();
        byte[] qSeed = primes.q().seed().toByteArray();
        byte[] u = new byte[firstSeed.length + pSeed.length + qSeed.length
            + GGEN.length + 1 + Integer.BYTES];
        int from = 0;
        arraycopy(firstSeed, 0, u, from, firstSeed.length);
        from += firstSeed.length;
        arraycopy(pSeed, 0, u, from, pSeed.length);
        from += pSeed.length;
        arraycopy(qSeed, 0, u, from, qSeed.length);
        from += qSeed.length;
        u[from] = (byte) (index & 0xFF);
        return u;
    }

    private static final void updateU(byte[] u, int count) {
        int from = u.length - Integer.BYTES;
        u[from] = (byte) ((count >> 24) & 0xFF);
        u[from + 1] = (byte) ((count >> 16) & 0xFF);
        u[from + 2] = (byte) ((count >> 8) & 0xFF);
        u[from + 3] = (byte) (count & 0xFF);
    }

    @Override
    public BigInteger newG(FFCPrimes primes, int index)
            throws GeneralCryptoLibException {
        BigInteger p = primes.p().value();
        BigInteger q = primes.q().value();
        BigInteger e = p.subtract(BigInteger.ONE).divide(q);
        byte[] u = newU(primes, index);
        int count = 0;
        BigInteger g;
        do {
            if (++count == 0) {
                throw new GeneralCryptoLibException(
                    "Failed to create generator.");
            }
            updateU(u, count);
            BigInteger w = hashU(u);
            g = modPow(w, e, p);
        } while (g.compareTo(BigInteger.ONE) <= 0);
        return g;
    }

    private BigInteger hashU(byte[] u) {
        CryptoMessageDigest digest = digestFactory.create();
        return new BigInteger(1, digest.generate(u));
    }
}
