/*
 * 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.factory.Verifier;
import com.scytl.cryptolib.proofs.maurer.function.PhiFunction;
import com.scytl.cryptolib.proofs.maurer.function.PhiFunctionSchnorr;
import com.scytl.cryptolib.proofs.proof.Proof;

/**
 * Tests of Verifier class
 */
public class VerifierTest {

    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 Proof _proof;

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

    private static Prover<BigInteger, ZpGroupElement> _prover;

    private static Verifier<BigInteger, ZpGroupElement> _verifier;

    private static CryptoRandomInteger _cryptoRandomInteger;

    @BeforeClass
    public static void setUp()
            throws NoSuchAlgorithmException, 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);

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

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

        // Set up the input variables

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

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

        // Generate a proof.
        _preComputedValues = _prover.preCompute(_cryptoRandomInteger);
        _proof =
            _prover.prove(_publicValues, _privateValues, _testString,
                _preComputedValues);
    }

    @Test
    public void givenValidGroupAndPhiWhenCreateVerifierThenOk()
            throws GeneralCryptoLibException {

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

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

        ZpSubgroup groupNull = null;

        new Verifier<BigInteger, ZpGroupElement>(groupNull,
            _phiFunctionSchnorr, getMaurerProofPolicy()
                .getMessageDigestAlgorithmAndProvider(),
            getMaurerProofPolicy().getCharset());
    }

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

        PhiFunction<BigInteger> nullPhiFunction = null;

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

    }

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

        List<ZpGroupElement> nullPublicValues = null;

        _verifier.verify(nullPublicValues, _proof, _testString);
    }

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

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

        _verifier.verify(emptyPublicValues, _proof, _testString);
    }

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

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

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

        _verifier.verify(publicValuesNotGroupMembers, _proof, _testString);
    }

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

        Proof nullProof = null;

        _verifier.verify(_publicValues, nullProof, _testString);
    }

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

        Exponent hashFromProof = _proof.getHashValue();
        List<Exponent> zFromProof = _proof.getValuesList();

        BigInteger q = hashFromProof.getQ();
        BigInteger newQ = q.add(BigInteger.ONE);

        Exponent newHash = new Exponent(newQ, hashFromProof.getValue());

        List<Exponent> newZ = new ArrayList<Exponent>();
        for (Exponent z : zFromProof) {
            newZ.add(new Exponent(newQ, z.getValue()));
        }

        // Create a new proof using the new c and z exponents
        Proof proofWithDifferentQ = new Proof(newHash, newZ);

        // Call verify, passing the new proof, which is based on the new q value
        _verifier.verify(_publicValues, proofWithDifferentQ, _testString);
    }

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

        String nullString = null;

        _verifier.verify(_publicValues, _proof, nullString);
    }

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