/*
 * 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.primitives.messagedigest.factory;

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

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.constants.Constants;
import com.scytl.cryptolib.primitives.messagedigest.configuration.ConfigMessageDigestAlgorithmAndProvider;
import com.scytl.cryptolib.primitives.messagedigest.configuration.MessageDigestPolicy;
import com.scytl.cryptolib.primitives.messagedigest.configuration.MessageDigestPolicyFromProperties;

public class MessageDigestGenerationTest {

    private static CryptoMessageDigest _cryptoMessageDigestFromProperties;

    private static CryptoMessageDigest _cryptoMessageDigestSHA512_224;

    private final int MESSAGE_DIGEST_SHA256_BYTE_LENGTH = 32;

    private final int MESSAGE_DIGEST_SHA512_224_BYTE_LENGTH = 28;

    private final String TEST_DATA_1 = "test data 1";

    private final String TEST_DATA_2 = "test data 2";

    @BeforeClass
    public static void setUp()
            throws InvalidKeyException, GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        _cryptoMessageDigestFromProperties =
            new MessageDigestFactory(
                new MessageDigestPolicyFromProperties(
                    Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH))
                .create();

        _cryptoMessageDigestSHA512_224 =
            new MessageDigestFactory(
                getMessageDigestPolicySha512_224AndDefault()).create();
    }

    @AfterClass
    public static void removeBouncyCastleProvider() {
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    @Test
    public void whenGenerateHashThenHasExpectedLength()
            throws GeneralCryptoLibException {

        byte[] messageDigest =
            _cryptoMessageDigestFromProperties.generate(TEST_DATA_1
                .getBytes());

        int messageDigestByteLength = messageDigest.length;

        Assert.assertEquals(messageDigestByteLength,
            MESSAGE_DIGEST_SHA256_BYTE_LENGTH);

        messageDigest =
            _cryptoMessageDigestSHA512_224
                .generate(TEST_DATA_1.getBytes());

        messageDigestByteLength = messageDigest.length;

        Assert.assertEquals(messageDigestByteLength,
            MESSAGE_DIGEST_SHA512_224_BYTE_LENGTH);
    }

    @Test
    public void whenGenerateHashTwiceThenHashsAreEqual()
            throws GeneralCryptoLibException {

        byte[] messageDigest1 =
            _cryptoMessageDigestFromProperties.generate(TEST_DATA_1
                .getBytes());

        byte[] messageDigest2 =
            _cryptoMessageDigestFromProperties.generate(TEST_DATA_1
                .getBytes());

        Assert.assertTrue(Arrays.equals(messageDigest1, messageDigest2));

        messageDigest1 =
            _cryptoMessageDigestSHA512_224
                .generate(TEST_DATA_1.getBytes());

        messageDigest2 =
            _cryptoMessageDigestSHA512_224
                .generate(TEST_DATA_1.getBytes());

        Assert.assertTrue(Arrays.equals(messageDigest1, messageDigest2));
    }

    @Test
    public void whenGenerateHashsForDifferentDataThenDifferentHashs()
            throws GeneralCryptoLibException {

        byte[] messageDigest1 =
            _cryptoMessageDigestFromProperties.generate(TEST_DATA_1
                .getBytes());

        byte[] messageDigest2 =
            _cryptoMessageDigestFromProperties.generate(TEST_DATA_2
                .getBytes());

        Assert.assertFalse(Arrays.equals(messageDigest1, messageDigest2));

        messageDigest1 =
            _cryptoMessageDigestSHA512_224
                .generate(TEST_DATA_1.getBytes());

        messageDigest2 =
            _cryptoMessageDigestSHA512_224
                .generate(TEST_DATA_2.getBytes());

        Assert.assertFalse(Arrays.equals(messageDigest1, messageDigest2));
    }

    @Test
    public void testHashFromStreamSmallFile()
            throws GeneralCryptoLibException, IOException {

        InputStream in = constructStreamContainingBytes(1024);

        byte[] hash = _cryptoMessageDigestFromProperties.generate(in);

        String hashBase64 = Base64.getEncoder().encodeToString(hash);
        String expectedHashBase64 =
            "eFsHUfwsU9wUpM49gA5p75zhAJ6zJ8z0WK/gnCQsJsk=";

        assertEquals(expectedHashBase64, hashBase64);
    }

    @Test
    public void testHashFromStreamLargeFile()
            throws GeneralCryptoLibException, IOException {

        InputStream inStream = constructStreamContainingBytes(5632);

        byte[] hash =
            _cryptoMessageDigestFromProperties.generate(inStream);

        String hashBase64 = Base64.getEncoder().encodeToString(hash);

        String expectedHashBase64 =
            "kcjIP7AE3JUkpb+yTfvb8hPGu92BFA+w1x7yL8DKGVY=";

        assertEquals(expectedHashBase64, hashBase64);
    }

    @Test
    public void confirmHashGeneratedUsingDifferentMethodsAreEqual()
            throws GeneralCryptoLibException, IOException {

        byte[] bytes = constructBytes(5632);
        InputStream stream = new ByteArrayInputStream(bytes);

        // get hash using stream
        byte[] hashFromStream =
            _cryptoMessageDigestFromProperties.generate(stream);

        // get hash by reading all of the bytes at once
        byte[] hashFromBytes =
            _cryptoMessageDigestFromProperties.generate(bytes);

        // confirm that both hashes are equal
        assertTrue(Arrays.equals(hashFromStream, hashFromBytes));
    }

    private static MessageDigestPolicy getMessageDigestPolicySha512_224AndDefault() {
        return new MessageDigestPolicy() {

            @Override
            public ConfigMessageDigestAlgorithmAndProvider getMessageDigestAlgorithmAndProvider() {
                return ConfigMessageDigestAlgorithmAndProvider.SHA512_224_DEFAULT;
            }
        };
    }
    
    private byte[] constructBytes(int count) {
        byte[] bytes = new byte[count];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) i;
        }
        return bytes;
    }

    private InputStream constructStreamContainingBytes(final int numberBytes) {
        return new ByteArrayInputStream(constructBytes(numberBytes));
    }
}
