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

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
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.GeneralCryptoLibException;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;

/**
 * Tests of ElGamalPrivateKey.
 */
public class ElGamalPrivateKeyTest {

    private static BigInteger _p;

    private static BigInteger _q;

    private static BigInteger _g;

    private static ZpSubgroup _group;

    private static int _numKeys;

    private static List<Exponent> _privKeys;

    private static ElGamalPrivateKey _elGamalPrivateKey;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

        _p = new BigInteger("23");

        _q = new BigInteger("11");

        _g = new BigInteger("2");

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

        _numKeys = 2;

        _privKeys = new ArrayList<Exponent>();
        _privKeys.add(new Exponent(_q, new BigInteger("4")));
        _privKeys.add(new Exponent(_q, new BigInteger("5")));

        _elGamalPrivateKey = new ElGamalPrivateKey(_privKeys, _group);
    }

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

        List<Exponent> nullKeysList = null;
        new ElGamalPrivateKey(nullKeysList, _group);
    }

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

        List<Exponent> nullKeysList = new ArrayList<Exponent>();
        new ElGamalPrivateKey(nullKeysList, _group);
    }

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

        ZpSubgroup nullGroup = null;
        new ElGamalPrivateKey(_privKeys, nullGroup);
    }

    @Test
    public void givenPrivateKeyWhenGetKeysThenExpectedKeys() {

        List<Exponent> returnedKeys = _elGamalPrivateKey.getKeys();

        String errorMsg =
            "The created private key does not have the expected number of elements";
        assertEquals(errorMsg, _numKeys, returnedKeys.size());

        errorMsg = "The private does not have the expected list of keys";
        assertEquals(errorMsg, _privKeys, returnedKeys);
    }

    @Test
    public void givenPrivateKeyWhenGetGroupThenExpectedGroup() {

        String errorMsg =
            "The created private key does not have the expected group";
        assertEquals(errorMsg, _group, _elGamalPrivateKey.getGroup());
    }

    @Test
    public void givenJsonStringWhenReconstructThenEqualToOriginalPrivateKey()
            throws GeneralCryptoLibException {

        BigInteger p = new BigInteger("23");
        BigInteger q = new BigInteger("11");
        BigInteger g = new BigInteger("2");
        ZpSubgroup smallGroup = new ZpSubgroup(g, p, q);

        List<Exponent> privKeys = new ArrayList<Exponent>();
        privKeys.add(new Exponent(q, new BigInteger("4")));
        privKeys.add(new Exponent(q, new BigInteger("5")));
        ElGamalPrivateKey expectedElGamalPrivateKey =
            new ElGamalPrivateKey(privKeys, smallGroup);

        String jsonStr = _elGamalPrivateKey.toJson();

        ElGamalPrivateKey reconstructedPrivateKey =
            ElGamalPrivateKey.fromJson(jsonStr);

        String errorMsg =
            "The reconstructed ElGamal private key is not equal to the expected key";
        assertEquals(errorMsg, expectedElGamalPrivateKey,
            reconstructedPrivateKey);
    }

    @Test
    public void givenKeyWhenMultiplyThenOK()
            throws GeneralCryptoLibException {
        List<Exponent> exponents =
            asList(new Exponent(_q, BigInteger.valueOf(13)));
        ElGamalPrivateKey other = new ElGamalPrivateKey(exponents, _group);
        ElGamalPrivateKey product = _elGamalPrivateKey.multiply(other);

        assertEquals(_group, product.getGroup());
        List<Exponent> productExponents = product.getKeys();
        assertEquals(1, productExponents.size());
        assertEquals(new Exponent(_q, BigInteger.valueOf(17)),
            productExponents.get(0));
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenNullWhenMultiplyThenException()
            throws GeneralCryptoLibException {
        _elGamalPrivateKey.multiply((ElGamalPrivateKey) null);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenKeyWithDifferentGroupWhenMultiplyThenException()
            throws GeneralCryptoLibException {
        BigInteger g = BigInteger.valueOf(2);
        BigInteger p = BigInteger.valueOf(7);
        BigInteger q = BigInteger.valueOf(3);
        ZpSubgroup zpSubgroup = new ZpSubgroup(g, p, q);
        List<Exponent> exponents =
            singletonList(new Exponent(q, BigInteger.valueOf(2)));
        ElGamalPrivateKey other =
            new ElGamalPrivateKey(exponents, zpSubgroup);
        _elGamalPrivateKey.multiply(other);
    }

    @Test
    public void givenArrayOfKeysWhenMultiplyThenOK()
            throws GeneralCryptoLibException {
        List<Exponent> exponents =
            asList(new Exponent(_q, BigInteger.valueOf(6)));
        ElGamalPrivateKey first = new ElGamalPrivateKey(exponents, _group);
        exponents = asList(new Exponent(_q, BigInteger.valueOf(7)));
        ElGamalPrivateKey other = new ElGamalPrivateKey(exponents, _group);
        ElGamalPrivateKey product =
            ElGamalPrivateKey.multiply(_elGamalPrivateKey, first, other);

        assertEquals(_group, product.getGroup());
        List<Exponent> productExponents = product.getKeys();
        assertEquals(1, productExponents.size());
        assertEquals(new Exponent(_q, BigInteger.valueOf(17)),
            productExponents.get(0));
    }

    @Test
    public void givenCollectionOfKeysWhenMultiplyThenOK()
            throws GeneralCryptoLibException {
        List<Exponent> exponents =
            asList(new Exponent(_q, BigInteger.valueOf(13)));
        ElGamalPrivateKey other = new ElGamalPrivateKey(exponents, _group);
        ElGamalPrivateKey product =
            ElGamalPrivateKey.multiply(asList(_elGamalPrivateKey, other));

        assertEquals(_group, product.getGroup());
        List<Exponent> productExponents = product.getKeys();
        assertEquals(1, productExponents.size());
        assertEquals(new Exponent(_q, BigInteger.valueOf(17)),
            productExponents.get(0));
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenEmptyCollectionOfKeysWhenMultiplyThenException()
            throws GeneralCryptoLibException {
        ElGamalPrivateKey.multiply(emptyList());
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenCollectionOfKeysWithNullWhenMultiplyThenException()
            throws GeneralCryptoLibException {
        ElGamalPrivateKey.multiply(asList(_elGamalPrivateKey, null));
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenCollectionOfKeysWithDifferentGroupWhenMultiplyThenException()
            throws GeneralCryptoLibException {
        List<Exponent> exponents =
            asList(new Exponent(_q, BigInteger.valueOf(13)));
        BigInteger g = BigInteger.valueOf(2);
        BigInteger p = BigInteger.valueOf(7);
        BigInteger q = BigInteger.valueOf(3);
        ZpSubgroup zpSubgroup = new ZpSubgroup(g, p, q);
        ElGamalPrivateKey other =
            new ElGamalPrivateKey(exponents, zpSubgroup);
        ElGamalPrivateKey.multiply(asList(_elGamalPrivateKey, other));
    }

    @Test
    public void testBinarySerialization()
            throws GeneralCryptoLibException {
        byte[] bytes = _elGamalPrivateKey.toBytes();
        ElGamalPrivateKey other = ElGamalPrivateKey.fromBytes(bytes);
        assertEquals(other, _elGamalPrivateKey);
    }

    @Test
    public void testInvert() throws GeneralCryptoLibException {
        ElGamalPrivateKey inverted = _elGamalPrivateKey.invert();
        assertEquals(_group, inverted.getGroup());
        List<Exponent> invertedExponents = inverted.getKeys();
        assertEquals(2, invertedExponents.size());
        assertEquals(new Exponent(_q, BigInteger.valueOf(7)),
            invertedExponents.get(0));
        assertEquals(new Exponent(_q, BigInteger.valueOf(6)),
            invertedExponents.get(1));
    }

    @Test
    public void testDivide() throws GeneralCryptoLibException {
        List<Exponent> exponents =
            asList(new Exponent(_q, BigInteger.valueOf(6)));
        ElGamalPrivateKey other = new ElGamalPrivateKey(exponents, _group);
        ElGamalPrivateKey quotient = _elGamalPrivateKey.divide(other);
        assertEquals(_group, quotient.getGroup());
        List<Exponent> quotientExponents = quotient.getKeys();
        assertEquals(1, quotientExponents.size());
        assertEquals(new Exponent(_q, BigInteger.valueOf(9)),
            quotientExponents.get(0));
    }
}
