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

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

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.elgamal.bean.CiphertextImpl;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.primitives.service.PrimitivesService;
import com.scytl.ov.mixing.commons.ballots.ElGamalEncryptedBallots;
import com.scytl.ov.mixing.commons.configuration.ApplicationConfig;
import com.scytl.ov.mixing.commons.configuration.MixerConfig;
import com.scytl.ov.mixing.commons.configuration.VerifierConfig;
import com.scytl.ov.mixing.commons.configuration.locations.LocationsProvider;

public class ElGamalEncryptedBallotsWriterTest {

    private static LocationsProvider locProvider;

    private static ElGamalEncryptedBallots encryptedBallots;

    private static String[] gammaString;

    private static String[] phisString;

    @BeforeClass
    public static void setUp() {

        final ApplicationConfig mixnetConfig = new ApplicationConfig();
        final MixerConfig mixerConfig = new MixerConfig();
        mixerConfig.setEncryptionParametersInput(new ByteArrayInputStream("".getBytes()));
        mixerConfig.setEncryptedBallotsInput(new ByteArrayInputStream("".getBytes()));
        mixerConfig.setPublicKeyInput(new ByteArrayInputStream("".getBytes()));
        List<OutputStream> mixedBallots = new ArrayList<>();
        mixedBallots.add(new ByteArrayOutputStream());
        List<InputStream> mixedBallotsInput = new ArrayList<>();
        mixedBallotsInput.add(new ByteArrayInputStream("".getBytes()));
        mixerConfig.setDecryptedVotesOutputOfBatch(mixedBallots);
        mixerConfig.setEncryptedBallotsOutputOfBatch(mixedBallots);
        mixnetConfig.setMixer(mixerConfig);
        final VerifierConfig verifierConfig = new VerifierConfig();
        verifierConfig.setDecryptedVotesInputOfBatch(mixedBallotsInput);
        mixnetConfig.setVerifier(verifierConfig);

        locProvider = LocationsProvider.build(mixnetConfig);

        gammaString = new String[] {"7606155287466709247547562508296526348227245",
                "4012556967239296284125919604329103854893595", "11583975953794885768215634543368899452667420",
                "7155423998998370092051586125176128718489417", "7830708621782553206289139938032543522538535" };

        phisString = new String[] {"9894101363857510707841445959199640374054748",
                "6054959594806155309765068576952771674747761", "2114609311581369961287633946894396492461861",
                "4070746468346708777122357082197449848916890", "795602734299768285623543899693683842706894" };

        encryptedBallots = createElGamalEncryptedBallots();
    }

    private static ElGamalEncryptedBallots createElGamalEncryptedBallots() {

        try {
            final BigInteger p = new BigInteger("15294034768093677312256663166625633354362303");
            final BigInteger q = new BigInteger("7647017384046838656128331583312816677181151");

            final List<Ciphertext> ballots = new ArrayList<>();

            final int numElements = gammaString.length;

            final ZpGroupElement[] gammas = new ZpGroupElement[numElements];

            for (int i = 0; i < gammaString.length; i++) {
                gammas[i] = new ZpGroupElement(new BigInteger(gammaString[i]), p, q);
            }

            final ZpGroupElement[] phis = new ZpGroupElement[numElements];
            for (int i = 0; i < phisString.length; i++) {
                phis[i] = new ZpGroupElement(new BigInteger(phisString[i]), p, q);
            }

            for (int i = 0; i < numElements; i++) {

                final List<ZpGroupElement> ballotPhis = new ArrayList<>();
                ballotPhis.add(phis[i]);
                ballots.add(new CiphertextImpl(gammas[i], ballotPhis));
            }

            return new ElGamalEncryptedBallots(ballots);

        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException(e);
        }
    }

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

        ElGamalEncryptedBallotsWriter.CSV.write(
            locProvider.getMixerLocationsProvider().getEncryptedBallotsOutputOfBatch(1), encryptedBallots,
            new PrimitivesService().getRawMessageDigest());

        final List<String> actualLines =
            actualLines(locProvider.getMixerLocationsProvider().getEncryptedBallotsOutputOfBatch(1));
        for (int i = 0; i < gammaString.length; i++) {
            assertThat("failed on line = " + (i + 1), actualLines.get(i), is(gammaString[i] + ";" + phisString[i]));
        }
    }

    @Test(expected = RuntimeException.class)
    public void failOnANullPath() throws IOException, GeneralCryptoLibException {
        ElGamalEncryptedBallotsWriter.CSV.write(null, encryptedBallots, new PrimitivesService().getRawMessageDigest());
    }

    @Test(expected = RuntimeException.class)
    public void failOnNullContents() throws IOException, GeneralCryptoLibException {
        ElGamalEncryptedBallotsWriter.CSV.write(
            locProvider.getMixerLocationsProvider().getReEncryptedBallotsOutputOfBatch(0), null,
            new PrimitivesService().getRawMessageDigest());
    }

    private List<String> actualLines(final OutputStream stream) {
        List<String> lines = new ArrayList<>();
        if (stream instanceof ByteArrayOutputStream) {
            String response = ((ByteArrayOutputStream) stream).toString();
            String[] split = response.split(Pattern.quote(System.lineSeparator()));
            for (String string : split) {
                lines.add(string);
            }

        }
        return lines;
    }

}
