/*
 * 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.junit.Assert.assertTrue;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;

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.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.ov.mixing.commons.beans.proofs.BayerGrothProof;
import com.scytl.ov.mixing.commons.beans.proofs.HadamardProductProofAnswer;
import com.scytl.ov.mixing.commons.beans.proofs.HadamardProductProofInitialMessage;
import com.scytl.ov.mixing.commons.beans.proofs.MultiExponentiationBasicProofAnswer;
import com.scytl.ov.mixing.commons.beans.proofs.MultiExponentiationBasicProofInitialMessage;
import com.scytl.ov.mixing.commons.beans.proofs.MultiExponentiationReductionAnswer;
import com.scytl.ov.mixing.commons.beans.proofs.MultiExponentiationReductionInitialMessage;
import com.scytl.ov.mixing.commons.beans.proofs.ProductProofMessage;
import com.scytl.ov.mixing.commons.beans.proofs.ShuffleProofSecondAnswer;
import com.scytl.ov.mixing.commons.beans.proofs.SingleValueProductProofAnswer;
import com.scytl.ov.mixing.commons.beans.proofs.SingleValueProductProofInitialMessage;
import com.scytl.ov.mixing.commons.beans.proofs.ZeroProofAnswer;
import com.scytl.ov.mixing.commons.beans.proofs.ZeroProofInitialMessage;
import com.scytl.ov.mixing.commons.homomorphic.impl.GjosteenElGamalRandomness;
import com.scytl.ov.mixing.commons.proofs.bg.commitments.CommitmentParams;
import com.scytl.ov.mixing.commons.proofs.bg.commitments.PrivateCommitment;
import com.scytl.ov.mixing.commons.proofs.bg.commitments.PublicCommitment;
import com.scytl.ov.mixing.commons.tests.categories.SlowTest;
import com.scytl.ov.mixing.commons.tools.ExponentTools;
import com.scytl.ov.mixing.commons.tools.GroupTools;
import com.scytl.ov.mixing.commons.tools.MultiExponentiation;
import com.scytl.ov.mixing.commons.tools.MultiExponentiationImpl;

@Category(SlowTest.class)
public class JSONProofsWriterTest {

    private static MultiExponentiation limMultiExpo;

    private static JSONProofsWriter writer;

    private static BayerGrothProof shuffleProof;

    private static ZpSubgroup group;

    private static JSONProofsReader jsonProofsReader;

    private static CommitmentParams comParams;

    @BeforeClass
    public static void setUp() {

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

        group = GroupTools.createGroupWithRandomGenerator(p, q);

        // needed in order for initialization purposes
        comParams = new CommitmentParams(group, 10);

        writer = new JSONProofsWriter();
        jsonProofsReader = new JSONProofsReader();

        limMultiExpo = MultiExponentiationImpl.getInstance();

        shuffleProof = createShuffleProof();

    }

    private static BayerGrothProof createShuffleProof() {
        final BayerGrothProof.Builder builder = new BayerGrothProof.Builder();

        builder.withInitialMessage(createPublicCommitmentArray()).withFirstAnswer(createPublicCommitmentArray())
            .withSecondAnswer(createShuffleSecondAnswer());

        return builder.build();
    }

    private static ShuffleProofSecondAnswer createShuffleSecondAnswer() {

        try {
            final MultiExponentiationReductionInitialMessage iniMEReduct =
                new MultiExponentiationReductionInitialMessage(createPrivateCommitmentArray(), createCiphertextArray());

            final BigInteger order = group.getQ();

            final MultiExponentiationBasicProofInitialMessage multiExponentiationBasicProofInitialMessage =
                new MultiExponentiationBasicProofInitialMessage(
                    new PrivateCommitment(new Exponent(order, new BigInteger("31")), comParams, limMultiExpo),
                    createPrivateCommitmentArray(), createCiphertextArray());

            final MultiExponentiationBasicProofAnswer multiExponentiationBasicProofAnswer =
                new MultiExponentiationBasicProofAnswer( //
                    ExponentTools.getVectorRandomExponent(1, order), //
                    new Exponent(order, new BigInteger("32")), //
                    ExponentTools.getRandomExponent(order), //
                    new Exponent(order, new BigInteger("33")), //
                    new GjosteenElGamalRandomness(new Exponent(order, new BigInteger("34")))//
                );

            final MultiExponentiationReductionAnswer ansMEReduct = new MultiExponentiationReductionAnswer(
                ExponentTools.getVectorRandomExponent(1, order), new Exponent(order, new BigInteger("40")),
                multiExponentiationBasicProofInitialMessage, multiExponentiationBasicProofAnswer);

            final SingleValueProductProofInitialMessage singleValueProductProofInitialMessage =
                new SingleValueProductProofInitialMessage(
                    new PrivateCommitment(new Exponent(order, new BigInteger("51")), comParams, limMultiExpo),
                    new PrivateCommitment(new Exponent(order, new BigInteger("52")), comParams, limMultiExpo),
                    new PrivateCommitment(new Exponent(order, new BigInteger("53")), comParams, limMultiExpo));

            final SingleValueProductProofAnswer singleValueProductProofAnswer = new SingleValueProductProofAnswer(
                ExponentTools.getVectorRandomExponent(1, order), ExponentTools.getVectorRandomExponent(1, order),
                new Exponent(order, new BigInteger("55")), new Exponent(order, new BigInteger("56")));

            final HadamardProductProofInitialMessage hadamardProductProofInitialMessage =
                new HadamardProductProofInitialMessage(createPrivateCommitmentArray());

            final ZeroProofInitialMessage zeroProofInitialMessage = new ZeroProofInitialMessage(
                new PrivateCommitment(new Exponent(order, new BigInteger("58")), comParams, limMultiExpo),
                new PrivateCommitment(new Exponent(order, new BigInteger("59")), comParams, limMultiExpo),
                createPrivateCommitmentArray());

            final ZeroProofAnswer zeroProofAnswer = new ZeroProofAnswer(ExponentTools.getVectorRandomExponent(1, order),
                ExponentTools.getVectorRandomExponent(1, order), new Exponent(order, new BigInteger("60")),
                new Exponent(order, new BigInteger("61")), new Exponent(order, new BigInteger("62")));

            final HadamardProductProofAnswer hadamardProductProofAnswer =
                new HadamardProductProofAnswer(zeroProofInitialMessage, zeroProofAnswer);

            final ProductProofMessage msgPA = new ProductProofMessage(
                new PublicCommitment(new ZpGroupElement(new BigInteger("50"), group.getP(), group.getQ())),
                singleValueProductProofInitialMessage, singleValueProductProofAnswer,
                hadamardProductProofInitialMessage, hadamardProductProofAnswer);

            final ShuffleProofSecondAnswer shuffleProofSecondAnswer =
                new ShuffleProofSecondAnswer(iniMEReduct, ansMEReduct, msgPA);

            return shuffleProofSecondAnswer;

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

    private static PublicCommitment[] createPublicCommitmentArray() {

        try {

            final PublicCommitment[] array = new PublicCommitment[2];
            array[0] = new PublicCommitment(new ZpGroupElement(new BigInteger("10"), group.getP(), group.getQ()));
            array[1] = new PublicCommitment(new ZpGroupElement(new BigInteger("20"), group.getP(), group.getQ()));
            return array;

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

    private static PrivateCommitment[] createPrivateCommitmentArray() {

        try {

            final PrivateCommitment[] array = new PrivateCommitment[2];
            array[0] = new PrivateCommitment(new Exponent(group.getQ(), new BigInteger("20")), comParams, limMultiExpo);
            array[1] = new PrivateCommitment(new Exponent(group.getQ(), new BigInteger("30")), comParams, limMultiExpo);
            return array;

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

    private static CiphertextImpl[] createCiphertextArray() {

        try {

            final ZpGroupElement[] input = new ZpGroupElement[2];
            input[0] = new ZpGroupElement(new BigInteger("30"), group.getP(), group.getQ());
            input[1] = new ZpGroupElement(new BigInteger("30"), group.getP(), group.getQ());

            List<ZpGroupElement> phis = new ArrayList<>();
            phis.add(input[1]);
            final CiphertextImpl[] array = new CiphertextImpl[1];
            array[0] = new CiphertextImpl(input[0], phis);
            return array;

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

    @Ignore
    @Test
    public void whenWritingProofsToJSONThenReadingThenOK() throws IOException {

        final Path filenameWrittenFile = Paths.get("target/proof.json");
        filenameWrittenFile.getParent().toFile().mkdir();
        FileOutputStream output = new FileOutputStream(filenameWrittenFile.toFile());
        writer.write(output, shuffleProof);
        output.close();

        FileInputStream input = new FileInputStream(filenameWrittenFile.toFile());
        final BayerGrothProof deserializedShuffleProof = jsonProofsReader.read(input);
        input.close();

        Files.createDirectories(Paths.get("target/2"));

        final Path filenameReadFile = Paths.get("target/2", "proofs_2.json");

        FileOutputStream output2 = new FileOutputStream(filenameReadFile.toFile());
        writer.write(output2, deserializedShuffleProof);
        output2.close();

        final String errorMsg = "The written and the read proofs are not equal";
        assertTrue(errorMsg, compareFiles(filenameWrittenFile.toString(), filenameReadFile.toString()));
    }

    private boolean compareFiles(final String firstFile, final String secondFile) throws IOException {

        final File file1 = Paths.get(firstFile).toFile();

        final File file2 = Paths.get(secondFile).toFile();

        final String firstCompletedFile = FileUtils.readFileToString(file1);

        final String secondCompletedFile = FileUtils.readFileToString(file2);

        return firstCompletedFile.equals(secondCompletedFile);
    }
}
