/*
 * 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.products.ov.commons.dto.mixing;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.products.ov.commons.beans.domain.model.ballotbox.BallotBoxIdImpl;
import com.scytl.products.ov.commons.beans.domain.model.vote.EncryptedVote;
import com.scytl.products.ov.commons.beans.domain.model.vote.VoteSetId;
import com.scytl.products.ov.commons.beans.domain.model.vote.VoteSetIdImpl;
import com.scytl.products.ov.commons.safestream.StreamSerializableObjectReaderImpl;
import com.scytl.products.ov.commons.safestream.StreamSerializableObjectWriterImpl;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

import org.apache.commons.io.IOUtils;
import org.junit.Test;

public class MixingPayloadImplTest {
    private final static ZpSubgroup encryptionParameters;

    static {
        try {
            encryptionParameters = new ZpSubgroup(new BigInteger("2"), new BigInteger("10"), new BigInteger("9"));
        } catch (GeneralCryptoLibException e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void testInitialPayloadSignableContent() throws Exception {
        final ZonedDateTime timestamp = ZonedDateTime.now();
        MixingPayload sut = getInitialPayload(timestamp);
        
        final String expectedValue =
                "tenantId-electionEventId-ballotBoxId-0{\"publicKey\":{\"zpSubgroup\":{\"g\":\"Ag==\",\"p\":\"Cg==\",\"q\":\"CQ==\"},\"elements\":[\"Aw==\"]}}"
                    + sut.getTimestamp().toEpochSecond() + "." + sut.getTimestamp().getNano();

        checkInputStreamContents(expectedValue, sut.getSignableContent());
    }

    @Test
    public void testOutputPayloadSignableContent() throws Exception {
        final ZonedDateTime timestamp = ZonedDateTime.now();
        
        MixingPayload initialPayload = getInitialPayload(timestamp);
        ElGamalPublicKey remainingVoteEncryptionKey = getVoteEncryptionKey(new BigInteger("4"));
        MixingPayload sut = new MixingPayloadImpl(initialPayload, remainingVoteEncryptionKey, Collections.emptyList(),
            Collections.singletonList("A"), Collections.emptyList(), "", Collections.emptyList());
        
        final String expectedValue =
                "tenantId-electionEventId-ballotBoxId-0{\"publicKey\":{\"zpSubgroup\":{\"g\":\"Ag==\",\"p\":\"Cg==\",\"q\":\"CQ==\"},\"elements\":[\"BA==\"]}}A"
                    + sut.getTimestamp().toEpochSecond() + "." + sut.getTimestamp().getNano() 
                    + "{\"publicKey\":{\"zpSubgroup\":{\"g\":\"Ag==\",\"p\":\"Cg==\",\"q\":\"CQ==\"},\"elements\":[\"Aw==\"]}}";


        checkInputStreamContents(expectedValue, sut.getSignableContent());
    }
    
    @Test
    public void testConsistentDeserializationWithEmptyVotes() throws Exception {
        final ZonedDateTime timestamp = ZonedDateTime.now();
        MixingPayload initialPayload = getInitialPayload(timestamp);
        ElGamalPublicKey remainingVoteEncryptionKey = getVoteEncryptionKey(new BigInteger("4"));
        initialPayload = new MixingPayloadImpl(initialPayload, remainingVoteEncryptionKey, Collections.emptyList(),
            Collections.singletonList("A"), Collections.emptyList(), "", Collections.emptyList());
        
        byte[] serializedPayload = new StreamSerializableObjectWriterImpl().write(initialPayload);
        
        MixingPayload deserializedPayload = new StreamSerializableObjectReaderImpl<MixingPayload>().read(serializedPayload);
        
        byte[] initialSignableContent = IOUtils.toByteArray(initialPayload.getSignableContent());
        byte[] deserializedSignableContent = IOUtils.toByteArray(deserializedPayload.getSignableContent());
        
        assertTrue(Arrays.equals(initialSignableContent, deserializedSignableContent));
    }

    /**
     * Tests the contents of an input stream against an expected string value.
     * 
     * @param expectedValue
     *            the expected value
     * @param inputStream
     *            the input stream
     * @throws IOException
     */
    private void checkInputStreamContents(final String expectedValue, InputStream inputStream) throws IOException {
        try (Scanner scanner = new Scanner(inputStream, "utf-8")) {
            String contents = scanner.useDelimiter("\\A").hasNext() ? scanner.next() : "";
            assertThat(contents, equalTo(expectedValue));
        }
    }

    /**
     * Gets an initial mixing payload for testing
     * 
     * @param timestamp
     *            a timestamp that will be set so that the signature is
     *            predictable
     * @return a mixing payload for testing
     * @throws GeneralCryptoLibException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private MixingPayload getInitialPayload(ZonedDateTime timestamp)
            throws GeneralCryptoLibException, NoSuchFieldException, IllegalAccessException {
        ElGamalPublicKey voteEncryptionKey = getVoteEncryptionKey(new BigInteger("3"));
        VoteSetId voteSetId = new VoteSetIdImpl(new BallotBoxIdImpl("tenantId", "electionEventId", "ballotBoxId"), 0);
        List<EncryptedVote> votes = Collections.emptyList();
        String electoralAuthorityId = "";
        MixingPayload sut =
            new MixingPayloadImpl(voteEncryptionKey, voteSetId, votes, electoralAuthorityId, encryptionParameters);

        Field timestampField = sut.getClass().getDeclaredField("timestamp");
        timestampField.setAccessible(true);
        timestampField.set(sut, timestamp);

        return sut;
    }

    private ElGamalPublicKey getVoteEncryptionKey(BigInteger value) throws GeneralCryptoLibException {
        ZpGroupElement groupElement = new ZpGroupElement(value, encryptionParameters);
        return new ElGamalPublicKey(Collections.singletonList(groupElement), encryptionParameters);
    }

}
