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

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
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.GroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.primitives.messagedigest.configuration.ConfigMessageDigestAlgorithmAndProvider;
import com.scytl.cryptolib.proofs.maurer.configuration.ConfigProofHashCharset;

/**
 * Tests of HashBuilder class.
 */
public class HashBuilderTest {

    private static String _short_string;

    private static ConfigMessageDigestAlgorithmAndProvider _configMessageDigestAlgorithmAndProvider;

    private static ConfigProofHashCharset _charset;

    private static HashBuilder _hashBuilder;

    private static ZpGroupElement _ZpGroupElement_p23_v2;

    private static ZpGroupElement _ZpGroupElement_p23_v3;

    private static ZpGroupElement _ZpGroupElement_p23_v4;

    private static List<ZpGroupElement> _simpleList1;

    private static List<ZpGroupElement> _simpleList2;

    private static List<ZpGroupElement> _simpleList3;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

        _short_string = "I am a short String";

        _configMessageDigestAlgorithmAndProvider =
            ConfigMessageDigestAlgorithmAndProvider.SHA256_DEFAULT;

        _charset = ConfigProofHashCharset.UTF8;

        _hashBuilder = new HashBuilder(
            _configMessageDigestAlgorithmAndProvider, _charset);

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

        ZpSubgroup group = new ZpSubgroup(g, p, q);

        _ZpGroupElement_p23_v2 =
            new ZpGroupElement(new BigInteger("2"), group);

        _ZpGroupElement_p23_v3 =
            new ZpGroupElement(new BigInteger("3"), group);

        _ZpGroupElement_p23_v4 =
            new ZpGroupElement(new BigInteger("4"), group);

        // Create 3 lists of ZpGroupElement

        _simpleList1 = new ArrayList<ZpGroupElement>();
        _simpleList1.add(_ZpGroupElement_p23_v2);
        _simpleList1.add(_ZpGroupElement_p23_v3);

        _simpleList2 = new ArrayList<ZpGroupElement>();
        _simpleList2.add(_ZpGroupElement_p23_v4);

        _simpleList3 = new ArrayList<ZpGroupElement>();
        _simpleList3.add(_ZpGroupElement_p23_v2);

    }

    /**
     * Test that the method getCharset method returns the expected charset for a
     * given HashBuilder object.
     */
    @Test
    public void givenHashBuilderWhenGetCharsetThenExpectedString() {

        assertEquals("The returned has name was not what was expected",
            StandardCharsets.UTF_8,
            _hashBuilder.getCharset().getCharset());
    }

    /**
     * Test that the hash generated using the method buildHashForProofs, is the
     * expected value. In this case, the lists contain objects of type
     * QuadraticResidueGroupElement.
     * <P>
     * Performs the test with different sets of inputs to confirm different
     * outputs.
     *
     * @throws GeneralCryptoLibException
     */
    @Test
    public void buildHashFromCompositionTest()
            throws UnsupportedEncodingException,
            GeneralCryptoLibException {

        BigInteger expectedHashValue1 = new BigInteger(
            "-52725384769643723964651772032205801420047176905115581490667997821371105732649");

        BigInteger expectedHashValue2 = new BigInteger(
            "9563579561871556882094939654152381110170321556447090503066141180687265867060");

        getHashFromObjectsUsingToStringAndAssertValue(expectedHashValue1,
            _simpleList1, _simpleList2, _short_string);

        getHashFromObjectsUsingToStringAndAssertValue(expectedHashValue2,
            _simpleList1, _simpleList3, _short_string);
    }

    /**
     * Test that when an object which is null is passed to the method
     * buildHashForProofs, then a CryptoLibException is thrown.
     *
     * @throws GeneralCryptoLibException
     */
    @Test(expected = GeneralCryptoLibException.class)
    public void givenNullFirstListWhenBuildHashFromStringThenException()
            throws UnsupportedEncodingException,
            GeneralCryptoLibException {

        _hashBuilder.buildHashForProofs(null,
            new ArrayList<GroupElement<BigInteger>>(), "Data");
    }

    /**
     * Test that when an object which is null is passed to the method
     * buildHashForProofs, then a CryptoLibException is thrown.
     *
     * @throws GeneralCryptoLibException
     */
    @Test(expected = GeneralCryptoLibException.class)
    public void givenNullSecondListWhenBuildHashFromStringThenException()
            throws UnsupportedEncodingException,
            GeneralCryptoLibException {

        _hashBuilder.buildHashForProofs(
            new ArrayList<GroupElement<BigInteger>>(), null, "Data");
    }

    /**
     * Test that the intToBytes method returns the expected value for the
     * minimum int value.
     */
    @Test
    public void givenMinIntValueWhenIntToBytesThenExpectedBytes() {

        convertIntToBytesAssertExpectedValue(Integer.MIN_VALUE);
    }

    /**
     * Test that the intToBytes method returns the expected value for an int
     * value of 100.
     */
    @Test
    public void givenIntValue100WhenIntToBytesThenExpectedBytes() {

        convertIntToBytesAssertExpectedValue(100);
    }

    /**
     * Test that the intToBytes method returns the expected value for the
     * maximum int value.
     */
    @Test
    public void givenMaxIntValueWhenIntToBytesThenExpectedBytes() {

        convertIntToBytesAssertExpectedValue(Integer.MAX_VALUE);
    }

    private void convertIntToBytesAssertExpectedValue(final int intValue) {

        byte[] expectedBytes =
            ByteBuffer.allocate(4).putInt(intValue).array();

        byte[] bytes = _hashBuilder.intToBytes(intValue);

        String errorMsg =
            "The generated bytes dont match the expected bytes";
        assertArrayEquals(errorMsg, expectedBytes, bytes);
    }

    private void getHashFromObjectsUsingToStringAndAssertValue(
            final BigInteger expectedHashValue,
            final List<? extends GroupElement<?>> publicValues,
            final List<? extends GroupElement<?>> generatedValues,
            final String data)
            throws UnsupportedEncodingException,
            GeneralCryptoLibException {

        BigInteger hash = new BigInteger(_hashBuilder
            .buildHashForProofs(publicValues, generatedValues, data));

        assertHashMatchesExpectedValue(expectedHashValue, hash);
    }

    @Test
    public void getHashTwiceFromObjectsUsingToStringAndAssertEqualsValue()
            throws UnsupportedEncodingException,
            GeneralCryptoLibException {

        BigInteger hash1 =
            new BigInteger(1, _hashBuilder.buildHashForProofs(_simpleList1,
                _simpleList2, _short_string));

        BigInteger hash2 =
            new BigInteger(1, _hashBuilder.buildHashForProofs(_simpleList1,
                _simpleList2, _short_string));

        assertHashesMatch(hash1, hash2);
    }

    private void assertHashMatchesExpectedValue(
            final BigInteger expectedValue, final BigInteger hash) {

        String errorMsg =
            "Generated hash value does not match expected value";

        assertEquals(errorMsg, expectedValue, hash);
    }

    private void assertHashesMatch(final BigInteger hash1,
            final BigInteger hash2) {

        String errorMsg =
            "Expected that two hash values generated from the same inputs would be the same, but they are not";

        assertEquals(errorMsg, hash1, hash2);
    }
}
