/*
 * 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.proofs.maurer.actors;

import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
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.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.mathematical.groups.utils.GroupUtils;
import com.scytl.cryptolib.primitives.messagedigest.configuration.ConfigMessageDigestAlgorithmAndProvider;
import com.scytl.cryptolib.primitives.securerandom.configuration.ConfigSecureRandomAlgorithmAndProvider;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomInteger;
import com.scytl.cryptolib.primitives.securerandom.factory.SecureRandomFactory;
import com.scytl.cryptolib.proofs.bean.ProofPreComputedValues;
import com.scytl.cryptolib.proofs.maurer.configuration.ConfigProofHashCharset;
import com.scytl.cryptolib.proofs.maurer.configuration.MaurerProofPolicy;
import com.scytl.cryptolib.proofs.maurer.factory.Prover;
import com.scytl.cryptolib.proofs.maurer.function.PhiFunction;
import com.scytl.cryptolib.proofs.maurer.function.PhiFunctionSchnorr;

/**
 * Tests of Prover class.
 */
public class ProverTest {

    private static GroupUtils _utils;

    private static ZpSubgroup _group;

    private static PhiFunction<BigInteger> _phiFunctionSchnorr;

    private static List<ZpGroupElement> _publicValues;

    private static List<Exponent> _privateValues;

    private static ProofPreComputedValues _preComputedValues;

    private static String _testString = "Hello, I am a short String";

    private static Prover<BigInteger, ZpGroupElement> _prover;

    private static CryptoRandomInteger _cryptoRandomInteger;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

        _utils = new GroupUtils();

        BigInteger ZpSubgroupP = new BigInteger("23");
        BigInteger ZpSubgroupQ = new BigInteger("11");
        BigInteger ZpSubgroupG = new BigInteger("2");

        _group = new ZpSubgroup(ZpSubgroupG, ZpSubgroupP, ZpSubgroupQ);

        _cryptoRandomInteger =
            new SecureRandomFactory(getMaurerProofPolicy())
                .createIntegerRandom();

        // Set up the PHI function parameters

        List<ZpGroupElement> baseElementsSmall =
            new ArrayList<ZpGroupElement>();
        baseElementsSmall.add(_group.getGenerator());

        _phiFunctionSchnorr =
            new PhiFunctionSchnorr<BigInteger>(_group, baseElementsSmall);

        _prover =
            new Prover<BigInteger, ZpGroupElement>(_group,
                _phiFunctionSchnorr, getMaurerProofPolicy()
                    .getMessageDigestAlgorithmAndProvider(),
                getMaurerProofPolicy().getCharset());

        _privateValues = new ArrayList<Exponent>();
        _privateValues.add(_utils.getRandomExponent(_group,
            _cryptoRandomInteger));

        _publicValues = new ArrayList<ZpGroupElement>();
        _publicValues.add(baseElementsSmall.get(0).exponentiate(
            _privateValues.get(0)));

        _preComputedValues = _prover.preCompute(_cryptoRandomInteger);
    }

    @Test
    public void givenValidGroupAndPhiWhenCreateProverThenOK()
            throws GeneralCryptoLibException {

        new Prover<BigInteger, ZpGroupElement>(_group,
            _phiFunctionSchnorr, getMaurerProofPolicy()
                .getMessageDigestAlgorithmAndProvider(),
            getMaurerProofPolicy().getCharset());
    }

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

        ZpSubgroup nullGroup = null;
        new Prover<BigInteger, ZpGroupElement>(nullGroup,
            _phiFunctionSchnorr, getMaurerProofPolicy()
                .getMessageDigestAlgorithmAndProvider(),
            getMaurerProofPolicy().getCharset());
    }

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

        PhiFunction<BigInteger> nullPhiFunction = null;

        new Prover<BigInteger, ZpGroupElement>(_group, nullPhiFunction,
            getMaurerProofPolicy().getMessageDigestAlgorithmAndProvider(),
            getMaurerProofPolicy().getCharset());
    }

    @Test
    public void givenValidArgumentsWhenProveThenOK()
            throws NoSuchAlgorithmException, GeneralCryptoLibException {

        _prover.prove(_publicValues, _privateValues, _testString,
            _preComputedValues);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenNullPublicValuesWhenProveThenException()
            throws NoSuchAlgorithmException, GeneralCryptoLibException {

        List<ZpGroupElement> nullPublicValues = null;

        _prover.prove(nullPublicValues, _privateValues, _testString,
            _preComputedValues);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenInvalidPublicValuesSizeWhenProveThenException()
            throws NoSuchAlgorithmException, GeneralCryptoLibException {

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

        wrongSizePublicValues.add(_publicValues.get(0));
        wrongSizePublicValues.add(_publicValues.get(0));

        _prover.prove(wrongSizePublicValues, _privateValues, _testString,
            _preComputedValues);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenPublicValuesNotGroupMembersWhenProveThenException()
            throws NoSuchAlgorithmException, GeneralCryptoLibException {

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

        publicValuesNotGroupMembers.add(new ZpGroupElement(new BigInteger(
            "1000000"), _group));

        _prover.prove(publicValuesNotGroupMembers, _privateValues,
            _testString, _preComputedValues);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenNullPrivateValuesWhenProveThenException()
            throws NoSuchAlgorithmException, GeneralCryptoLibException {

        List<Exponent> nullPrivateValues = null;

        _prover.prove(_publicValues, nullPrivateValues, _testString,
            _preComputedValues);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenInvalidPrivateValuesSizeWhenProveThenException()
            throws NoSuchAlgorithmException, GeneralCryptoLibException {

        List<Exponent> wrongSizePrivateValues = new ArrayList<Exponent>();

        wrongSizePrivateValues.add(_privateValues.get(0));
        wrongSizePrivateValues.add(_privateValues.get(0));

        _prover.prove(_publicValues, wrongSizePrivateValues, _testString,
            _preComputedValues);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void givenNullStringWhenProveThenException()
            throws NoSuchAlgorithmException, GeneralCryptoLibException {

        String nullString = null;
        _prover.prove(_publicValues, _privateValues, nullString,
            _preComputedValues);
    }

    private static MaurerProofPolicy getMaurerProofPolicy() {

        return new MaurerProofPolicy() {

            @Override
            public ConfigMessageDigestAlgorithmAndProvider getMessageDigestAlgorithmAndProvider() {
                return ConfigMessageDigestAlgorithmAndProvider.SHA256_DEFAULT;
            }

            @Override
            public ConfigProofHashCharset getCharset() {
                return ConfigProofHashCharset.UTF8;
            }

            @Override
            public ConfigSecureRandomAlgorithmAndProvider getSecureRandomAlgorithmAndProvider() {

                return getSecureRandom();
            }
        };
    }

    private static ConfigSecureRandomAlgorithmAndProvider getSecureRandom() {

        switch (OperatingSystem.current()) {

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