/*
 * 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.mathematical.groups.utils;

import static org.junit.Assert.assertEquals;

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

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.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.primitives.securerandom.configuration.ConfigSecureRandomAlgorithmAndProvider;
import com.scytl.cryptolib.primitives.securerandom.configuration.SecureRandomPolicy;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomInteger;
import com.scytl.cryptolib.primitives.securerandom.factory.SecureRandomFactory;

/**
 * Tests of ZpSubgroupUtils.
 */
public class GroupUtilsTest {

    private static BigInteger _q;

    private static BigInteger _p;

    private static BigInteger _g;

    private static ZpSubgroup _group_g2_q11;

    private static GroupUtils _groupUtils;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

        _q = new BigInteger("11");

        _p = new BigInteger("23");

        _g = new BigInteger("2");

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

        _groupUtils = new GroupUtils();
    }

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

        _groupUtils.buildQuadraticResidueGroupFromPAndG(_p, null);
    }

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

        _groupUtils.buildQuadraticResidueGroupFromPAndG(null, _g);
    }

    @Test
    public void givenPAndGWhenBuildGroupThenExpectedGroup()
            throws GeneralCryptoLibException {

        ZpSubgroup group =
            _groupUtils.buildQuadraticResidueGroupFromPAndG(_p, _g);

        String errorMsg = "The constructed group is not what was expected";
        assertEquals(errorMsg, _group_g2_q11, group);
    }

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

        int numRequiredElements = 9;

        _groupUtils.extractNumberOfGroupMembersFromListOfPossibleMembers(
            null, _group_g2_q11, numRequiredElements);
    }

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

        int numRequiredElements = 9;

        List<BigInteger> list = new ArrayList<BigInteger>();

        _groupUtils.extractNumberOfGroupMembersFromListOfPossibleMembers(
            list, _group_g2_q11, numRequiredElements);
    }

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

        int numRequiredElements = 9;

        List<BigInteger> allGroupElements = buildListOfAllGroupElements();

        _groupUtils.extractNumberOfGroupMembersFromListOfPossibleMembers(
            allGroupElements, null, numRequiredElements);
    }

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

        int numRequiredElements = 0;
        List<BigInteger> allGroupElements = buildListOfAllGroupElements();

        _groupUtils.extractNumberOfGroupMembersFromListOfPossibleMembers(
            allGroupElements, _group_g2_q11, numRequiredElements);
    }

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

        List<BigInteger> allGroupElements = buildListOfAllGroupElements();
        int numRequiredElements = allGroupElements.size() + 1;

        _groupUtils.extractNumberOfGroupMembersFromListOfPossibleMembers(
            allGroupElements, _group_g2_q11, numRequiredElements);
    }

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

        int numRequiredElements = 15;

        List<BigInteger> groupElementsAndNonGroupElements =
            buildListOfAllGroupElementsAndNonGroupElements(_p.intValue());

        _groupUtils.extractNumberOfGroupMembersFromListOfPossibleMembers(
            groupElementsAndNonGroupElements, _group_g2_q11,
            numRequiredElements);
    }

    @Test
    public void givenListAndGroupAndNumRequiredElementsWhenExtractListThenOK()
            throws GeneralCryptoLibException {

        int numRequiredElements = 9;
        List<BigInteger> groupElementsAndNonGroupElements =
            buildListOfAllGroupElementsAndNonGroupElements(_p.intValue());

        List<BigInteger> expectedList =
            buildExpectedListOfGroupElements(numRequiredElements);

        List<BigInteger> returnList =
            _groupUtils
                .extractNumberOfGroupMembersFromListOfPossibleMembers(
                    groupElementsAndNonGroupElements, _group_g2_q11,
                    numRequiredElements);

        String errorMsg = "The returned list was not the expected list";
        assertEquals(errorMsg, expectedList, returnList);
    }

    private List<BigInteger> buildListOfAllGroupElementsAndNonGroupElements(
            final int p) {

        List<BigInteger> groupElementList = new ArrayList<BigInteger>();

        BigInteger current = BigInteger.ONE;

        for (int i = 1; i <= p; i++) {
            groupElementList.add(current);
            current = current.add(BigInteger.ONE);
        }
        return groupElementList;
    }

    private List<BigInteger> buildListOfAllGroupElements() {

        List<BigInteger> groupElementList = new ArrayList<BigInteger>();
        groupElementList.add(new BigInteger("1"));
        groupElementList.add(new BigInteger("2"));
        groupElementList.add(new BigInteger("3"));
        groupElementList.add(new BigInteger("4"));
        groupElementList.add(new BigInteger("6"));
        groupElementList.add(new BigInteger("8"));
        groupElementList.add(new BigInteger("9"));
        groupElementList.add(new BigInteger("12"));
        groupElementList.add(new BigInteger("13"));
        groupElementList.add(new BigInteger("16"));
        groupElementList.add(new BigInteger("18"));

        return groupElementList;
    }

    private List<BigInteger> buildExpectedListOfGroupElements(
            final int numRequiredElements) {

        List<BigInteger> groupElementList = buildListOfAllGroupElements();

        List<BigInteger> expectedList = new ArrayList<BigInteger>();

        for (int i = 0; i < numRequiredElements; i++) {
            expectedList.add(groupElementList.get(i));
        }

        return expectedList;
    }

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

        GroupUtils groupsUtils = new GroupUtils();

        BigInteger p = new BigInteger("23");
        BigInteger q = new BigInteger("11");
        BigInteger g = new BigInteger("2");
        ZpSubgroup subgroup = new ZpSubgroup(g, p, q);
        CryptoRandomInteger cryptoRandomInteger =
            new SecureRandomFactory(getSecureRandomPolicy())
                .createIntegerRandom();
        groupsUtils.getRandomShortExponent(subgroup, cryptoRandomInteger);
    }

    private static SecureRandomPolicy getSecureRandomPolicy() {

        return new SecureRandomPolicy() {

            @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");
                }
            }
        };
    }

}
