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

/**
 * @date   19/12/2013 18:56:36
 *
 * Copyright (C) 2013 Scytl Secure Electronic Voting SA
 *
 * All rights reserved.
 *
 */

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

import org.junit.Assert;
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.ElGamalComputationsValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalKeyPair;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
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;

public class CryptoElGamalDecrypterTest {

    private static BigInteger _p;

    private static BigInteger _q;

    private static BigInteger _g;

    private static ZpSubgroup _group_g2_q11;

    private static int _numKeys;

    private static ElGamalPolicy _cryptoElGamalPolicy;

    private static ElGamalFactory _cryptoElGamalFactory;

    private static ElGamalKeyPair _cryptoElGamalKeyPair;

    private static ElGamalPrivateKey _privKey;

    private static CryptoElGamalDecrypter _decrypter;

    private static ElGamalEncryptionParameters _elGamalEncryptionParameters;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

        _p = new BigInteger("23");

        _q = new BigInteger("11");

        _g = new BigInteger("2");

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

        _numKeys = 2;

        _cryptoElGamalPolicy = getCryptoElGamalPolicy();

        _cryptoElGamalFactory = new ElGamalFactory(_cryptoElGamalPolicy);

        _elGamalEncryptionParameters =
            new ElGamalEncryptionParameters(_p, _q, _g);
        _cryptoElGamalKeyPair =
            _cryptoElGamalFactory.createCryptoElGamalKeyPairGenerator()
                .generateKeys(_elGamalEncryptionParameters, _numKeys);

        _privKey = _cryptoElGamalKeyPair.getPrivateKeys();

        _decrypter = _cryptoElGamalFactory.createDecrypter(_privKey);
    }

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

        ZpGroupElement gamma =
            new ZpGroupElement(new BigInteger("7"), _group_g2_q11);

        List<ZpGroupElement> phis = new ArrayList<ZpGroupElement>();

        phis.add(new ZpGroupElement(BigInteger.ONE, _group_g2_q11));
        phis.add(new ZpGroupElement(new BigInteger("2"), _group_g2_q11));

        ElGamalComputationsValues ciphertext =
            new ElGamalComputationsValues(gamma, phis);

        _decrypter.decrypt(ciphertext, true);
    }

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

        ZpGroupElement gamma =
            new ZpGroupElement(new BigInteger("7"), _group_g2_q11);

        List<ZpGroupElement> phis = new ArrayList<ZpGroupElement>();

        phis.add(gamma);
        phis.add(new ZpGroupElement(new BigInteger("6"), _group_g2_q11));

        ElGamalComputationsValues ciphertext =
            new ElGamalComputationsValues(gamma, phis);

        _decrypter.decrypt(ciphertext, true);
    }

    @Test
    public void givenAValidInputWhenDecryptThenSucceed()
            throws GeneralCryptoLibException {

        ElGamalComputationsValues constructedEncryptionValues =
            buildEncryptionValues();

        List<Exponent> constructedPrivateKeys = buildKeys();

        BigInteger plaintextPart1 = new BigInteger("6");
        BigInteger plaintestPart2 = new BigInteger("12");

        CryptoElGamalDecrypter decrypterWithConstructedKeys =
            _cryptoElGamalFactory.createDecrypter(new ElGamalPrivateKey(
                constructedPrivateKeys, _group_g2_q11));

        List<ZpGroupElement> decryptedMessages =
            decrypterWithConstructedKeys.decrypt(
                constructedEncryptionValues, true);

        String errorMessage =
            "The decrypted message does not match with the original message";
        Assert.assertEquals(errorMessage, plaintextPart1,
            decryptedMessages.get(0).getValue());
        Assert.assertEquals(errorMessage, plaintestPart2,
            decryptedMessages.get(1).getValue());
    }

    private ElGamalComputationsValues buildEncryptionValues()
            throws GeneralCryptoLibException {

        List<ZpGroupElement> phis = new ArrayList<ZpGroupElement>();

        ZpGroupElement gamma =
            new ZpGroupElement(new BigInteger("4"), _group_g2_q11);
        ZpGroupElement phi1 =
            new ZpGroupElement(new BigInteger("9"), _group_g2_q11);
        ZpGroupElement phi2 =
            new ZpGroupElement(new BigInteger("16"), _group_g2_q11);
        phis.add(phi1);
        phis.add(phi2);

        ElGamalComputationsValues values =
            new ElGamalComputationsValues(gamma, phis);

        return values;
    }

    private List<Exponent> buildKeys() throws GeneralCryptoLibException {

        List<Exponent> privKeys = new ArrayList<Exponent>();
        privKeys.add(new Exponent(_group_g2_q11.getQ(),
            new BigInteger("9")));
        privKeys.add(new Exponent(_group_g2_q11.getQ(),
            new BigInteger("8")));

        return privKeys;
    }

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