/*
 * 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.elgamal.factory;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.math.BigInteger;

import org.junit.BeforeClass;
import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.utils.system.OperatingSystem;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalKeyPair;
import com.scytl.cryptolib.elgamal.configuration.ConfigGroupType;
import com.scytl.cryptolib.elgamal.configuration.ElGamalPolicy;
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.primitives.securerandom.configuration.ConfigSecureRandomAlgorithmAndProvider;

/**
 * ElGamalKeyGenerator tests
 */
public class CryptoElGamalKeyPairGeneratorTest {

    private static BigInteger _p;

    private static BigInteger _q;

    private static BigInteger _g;

    private static ZpSubgroup _group;

    private static int _numKeys;

    private static CryptoElGamalKeyPairGenerator _cryptoElGamalKeyPairGenerator;

    private static ElGamalKeyPair _generatedKeys;

    private static ElGamalPolicy _cryptoElGamalPolicy;

    private static ElGamalEncryptionParameters _elGamalEncryptionParameters;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

        loadConfiguration();

        _cryptoElGamalPolicy = getCryptoElGamalPolicy();

        _group = new ZpSubgroup(_g, _p, _q);

        ElGamalFactory elGamalFactory =
            new ElGamalFactory(_cryptoElGamalPolicy);

        _cryptoElGamalKeyPairGenerator =
            elGamalFactory.createCryptoElGamalKeyPairGenerator();

        _elGamalEncryptionParameters =
            new ElGamalEncryptionParameters(_p, _q, _g);
        _generatedKeys =
            _cryptoElGamalKeyPairGenerator.generateKeys(
                _elGamalEncryptionParameters, _numKeys);
    }

    private static void loadConfiguration()
            throws GeneralCryptoLibException {

        _p = new BigInteger("23");

        _q = new BigInteger("11");

        _g = new BigInteger("2");

        _group = new ZpSubgroup(_g, _p, _q);

        _numKeys = 15;
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenZeroKeysRequestedWhenGenerateKeysThenException()
            throws GeneralCryptoLibException {

        ElGamalEncryptionParameters encParams =
            new ElGamalEncryptionParameters(_p, _q, _g);

        _cryptoElGamalKeyPairGenerator.generateKeys(encParams, 0);
    }

    @Test
    public void testThatGeneratedKeysSizesAreTheExpectedValues() {

        int numGeneratedPrivateKeys =
            _generatedKeys.getPrivateKeys().getKeys().size();
        int numGeneratedPublicKeys =
            _generatedKeys.getPublicKeys().getKeys().size();
        String errorMsgWrongSize =
            "The generated keys are not the expected size";

        // Test that the sizes of the generated keys are the expected size
        assertEquals(errorMsgWrongSize, _numKeys, numGeneratedPrivateKeys);
        assertEquals(errorMsgWrongSize, _numKeys, numGeneratedPublicKeys);

    }

    @Test
    public void testThatAllGenerateKeysAreMembersOfTheGroup() {

        String errorMsgNotMembersOfGroup =
            "The generated keys are not all members of the group";

        // Test that all of the public keys are members of the group
        for (int i = 0; i < _generatedKeys.getPublicKeys().getKeys()
            .size(); i++) {
            assertTrue(
                errorMsgNotMembersOfGroup,
                _group.isGroupMember(_generatedKeys.getPublicKeys()
                    .getKeys().get(i)));
        }
    }

    @Test
    public void testVerifyThatKeysAreMathematicallyRelated()
            throws GeneralCryptoLibException {

        ZpGroupElement gRaisedToPrivateKey;
        Exponent privateKey;
        ZpGroupElement publicKey;

        String errorMsgKeysNotMathematicallyRelated =
            "The generated keys are not mathematically related ";

        for (int i = 0; i < _numKeys; i++) {

            publicKey = _generatedKeys.getPublicKeys().getKeys().get(i);
            privateKey = _generatedKeys.getPrivateKeys().getKeys().get(i);
            gRaisedToPrivateKey =
                _group.getGenerator().exponentiate(privateKey);

            assertEquals(errorMsgKeysNotMathematicallyRelated, publicKey,
                gRaisedToPrivateKey);
        }
    }

    private static ElGamalPolicy getCryptoElGamalPolicy() {

        return new ElGamalPolicy() {

            @Override
            public ConfigGroupType getGroupType() {
                return ConfigGroupType.ZP_2048_256;
            }

            @Override
            public ConfigSecureRandomAlgorithmAndProvider getSecureRandomAlgorithmAndProvider() {

                switch (OperatingSystem.current()) {

                case WINDOWS:
                    return ConfigSecureRandomAlgorithmAndProvider.PRNG_SUN_MSCAPI;
                case LINUX:
                    return ConfigSecureRandomAlgorithmAndProvider.NATIVE_PRNG_SUN;
                default:
                    throw new CryptoLibException("OS not supported");
                }
            }
        };
    }
}
