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

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

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.DefaultLocationNames;
import com.scytl.ov.mixing.commons.configuration.locations.LocationConstants;
import com.scytl.ov.mixing.commons.constants.Constants;
import com.scytl.ov.mixing.commons.preprocess.Partitioner;

public class FileBasedApplicationConfig {

    private int concurrencyLevel;

    private int numberOfParallelBatches;

    private int numOptions;

    private int minimumBatchSize;

    private FileBasedMixerConfig mixer;

    private FileBasedVerifierConfig verifier;

    public int getConcurrencyLevel() {
        return concurrencyLevel;
    }

    public void setConcurrencyLevel(final int concurrencyLevel) {
        this.concurrencyLevel = concurrencyLevel;
    }

    public int getNumOptions() {
        return numOptions;
    }

    public void setNumOptions(final int numOptions) {
        this.numOptions = numOptions;
    }

    public FileBasedMixerConfig getMixer() {
        return mixer;
    }

    public void setMixer(final FileBasedMixerConfig mixer) {
        this.mixer = mixer;
    }

    public FileBasedVerifierConfig getVerifier() {
        return verifier;
    }

    public void setVerifier(final FileBasedVerifierConfig verifier) {
        this.verifier = verifier;
    }

    public boolean hasMixerConfig() {
        return this.mixer != null;
    }

    public boolean hasVerifierConfig() {
        return this.verifier != null;
    }

    public int getMinimumBatchSize() {
        return minimumBatchSize;
    }

    public void setMinimumBatchSize(final int minimumBatchSize) {
        this.minimumBatchSize = minimumBatchSize;
    }

    public int getNumberOfParallelBatches() {
        return numberOfParallelBatches;
    }

    public void setNumberOfParallelBatches(final int numberOfParallelBatches) {
        this.numberOfParallelBatches = numberOfParallelBatches;
    }

    public static ApplicationConfig convertToStreamConfig(FileBasedApplicationConfig fbAppConfig) throws IOException {
        ApplicationConfig config = new ApplicationConfig();
        config.setConcurrencyLevel(fbAppConfig.getConcurrencyLevel());
        config.setMinimumBatchSize(fbAppConfig.getMinimumBatchSize());
        config.setNumberOfParallelBatches(fbAppConfig.getNumberOfParallelBatches());
        config.setNumOptions(fbAppConfig.getNumOptions());

        String encryptedBallotsParam = null;
        if (fbAppConfig.getMixer() != null) {
            encryptedBallotsParam = fbAppConfig.getMixer().getInput().get(LocationConstants.ENCRYPTED_BALLOTS_JSON_TAG);
        }
        Integer lineCount = null;
        if (encryptedBallotsParam != null) {
            Path encryptedBallots = Paths.get(encryptedBallotsParam + Constants.CSV_FILE_EXTENSION);
            try (Stream<String> lines = Files.lines(encryptedBallots)) {
                lineCount = (int) lines.count();
            }
        }
        if (fbAppConfig.getMixer() != null) {
            config.setMixer(convertMixer(fbAppConfig.getMixer(), fbAppConfig.getMinimumBatchSize(), lineCount));
        }
        if (fbAppConfig.getVerifier() != null) {
            config.setVerifier(convertVerifier(fbAppConfig.getVerifier(), fbAppConfig.getMinimumBatchSize(), lineCount));
        }
        return config;
    }

    private static MixerConfig convertMixer(FileBasedMixerConfig fileBasedMixerConfig, Integer partitionSize,
            Integer lines) throws IOException {
        MixerConfig mixerConfig = new MixerConfig();
        mixerConfig.setMix(fileBasedMixerConfig.isMix());
        mixerConfig.setWarmUpSize(fileBasedMixerConfig.getWarmUpSize());
        if (fileBasedMixerConfig.getInput().get(LocationConstants.BALLOT_DECRYPTION_KEY_JSON_TAG) != null) {
            mixerConfig.setBallotDecryptionKeyInput(new FileInputStream(
                Paths.get(fileBasedMixerConfig.getInput().get(LocationConstants.BALLOT_DECRYPTION_KEY_JSON_TAG)
                    + Constants.JSON_FILE_EXTENSION).toFile()));
        }
        if (fileBasedMixerConfig.getInput().get(LocationConstants.ENCRYPTED_BALLOTS_JSON_TAG) != null) {
            mixerConfig.setEncryptedBallotsInput(new FileInputStream(
                Paths.get(fileBasedMixerConfig.getInput().get(LocationConstants.ENCRYPTED_BALLOTS_JSON_TAG)
                    + Constants.CSV_FILE_EXTENSION).toFile()));
        }
        if (fileBasedMixerConfig.getInput().get(LocationConstants.ENCRYPTION_PARAMETERS_JSON_TAG) != null) {
            mixerConfig.setEncryptionParametersInput(new FileInputStream(
                Paths.get(fileBasedMixerConfig.getInput().get(LocationConstants.ENCRYPTION_PARAMETERS_JSON_TAG)
                    + Constants.JSON_FILE_EXTENSION).toFile()));
        }
        if (fileBasedMixerConfig.getInput().get(LocationConstants.PUBLIC_KEY_JSON_TAG) != null) {
            mixerConfig.setPublicKeyInput(
                new FileInputStream(Paths.get(fileBasedMixerConfig.getInput().get(LocationConstants.PUBLIC_KEY_JSON_TAG)
                    + Constants.JSON_FILE_EXTENSION).toFile()));
        }

        String outputDirectory = fileBasedMixerConfig.getOutputDirectory();
        if (outputDirectory != null) {
            Path path = Paths.get(outputDirectory);
            path.toFile().mkdirs();
            if (lines != null) {
                int numBatches = new Partitioner(partitionSize).generatePartitionsOfMinimumSize(lines).size();
                List<OutputStream> commitmentParametersOutputOfBatch = new ArrayList<>(numBatches);
                List<OutputStream> decryptedVotesOutputOfBatch = new ArrayList<>(numBatches);
                List<OutputStream> encryptedBallotsOutputOfBatch = new ArrayList<>(numBatches);
                List<OutputStream> encryptionParametersOutputOfBatch = new ArrayList<>(numBatches);
                List<OutputStream> proofsOutputOfBatch = new ArrayList<>(numBatches);
                List<OutputStream> publicKeyOutputOfBatch = new ArrayList<>(numBatches);
                List<OutputStream> reEncryptedBallotsOutputOfBatch = new ArrayList<>(numBatches);
                List<OutputStream> votesWithProofOutputOfBatch = new ArrayList<>(numBatches);
                for (int i = 0; i < numBatches; i++) {
                    String batchNum = Integer.toString(i);
                    path.resolve(batchNum).toFile().mkdirs();
                    commitmentParametersOutputOfBatch.add(new FileOutputStream(Paths
                        .get(outputDirectory, batchNum,
                            DefaultLocationNames.COMMITMENT_PARAMETERS_OUTPUT_FILE_NAME + Constants.JSON_FILE_EXTENSION)
                        .toFile()));
                    decryptedVotesOutputOfBatch.add(new FileOutputStream(Paths
                        .get(outputDirectory, batchNum,
                            DefaultLocationNames.DECRYPTED_VOTES_OUTPUT_FILE_NAME + Constants.CSV_FILE_EXTENSION)
                        .toFile()));
                    encryptedBallotsOutputOfBatch.add(new FileOutputStream(Paths
                        .get(outputDirectory, batchNum,
                            DefaultLocationNames.ENCRYPTED_BALLOTS_OUTPUT_FILE_NAME + Constants.CSV_FILE_EXTENSION)
                        .toFile()));
                    encryptionParametersOutputOfBatch.add(new FileOutputStream(Paths
                        .get(outputDirectory, batchNum,
                            DefaultLocationNames.ENCRYPTION_PARAMETERS_OUTPUT_FILE_NAME + Constants.JSON_FILE_EXTENSION)
                        .toFile()));
                    proofsOutputOfBatch
                        .add(new FileOutputStream(Paths
                            .get(outputDirectory, batchNum,
                                DefaultLocationNames.PROOFS_OUTPUT_FILE_NAME + Constants.JSON_FILE_EXTENSION)
                            .toFile()));
                    publicKeyOutputOfBatch.add(new FileOutputStream(Paths
                        .get(outputDirectory, batchNum,
                            DefaultLocationNames.PUBLIC_KEY_OUTPUT_FILE_NAME + Constants.JSON_FILE_EXTENSION)
                        .toFile()));
                    reEncryptedBallotsOutputOfBatch.add(new FileOutputStream(Paths
                        .get(outputDirectory, batchNum,
                            DefaultLocationNames.REENCRYPTED_BALLOTS_OUTPUT_FILE_NAME + Constants.CSV_FILE_EXTENSION)
                        .toFile()));
                    votesWithProofOutputOfBatch
                        .add(new FileOutputStream(Paths
                            .get(outputDirectory, batchNum,
                                DefaultLocationNames.VOTES_WITH_PROOF_FILE_NAME + Constants.CSV_FILE_EXTENSION)
                            .toFile()));
                }

                mixerConfig.setCommitmentParametersOutputOfBatch(commitmentParametersOutputOfBatch);
                mixerConfig.setDecryptedVotesOutputOfBatch(decryptedVotesOutputOfBatch);
                mixerConfig.setEncryptedBallotsOutputOfBatch(encryptedBallotsOutputOfBatch);
                mixerConfig.setEncryptionParametersOutputOfBatch(encryptionParametersOutputOfBatch);
                mixerConfig.setProofsOutputOfBatch(proofsOutputOfBatch);
                mixerConfig.setPublicKeyOutputOfBatch(publicKeyOutputOfBatch);
                mixerConfig.setReEncryptedBallotsOutputOfBatch(reEncryptedBallotsOutputOfBatch);
                mixerConfig.setVotesWithProofOutputOfBatch(votesWithProofOutputOfBatch);
            }
        }
        return mixerConfig;
    }

    private static VerifierConfig convertVerifier(FileBasedVerifierConfig fileBasedVerifierConfig,
            Integer partitionSize, Integer lines) throws FileNotFoundException {
        VerifierConfig verifierConfig = new VerifierConfig();
        verifierConfig.setVerify(fileBasedVerifierConfig.isVerify());
        String outputDirectory = fileBasedVerifierConfig.getOutputDirectory();
        String inputDirectory = fileBasedVerifierConfig.getInputDirectory();
        if (outputDirectory != null) {
            Path path = Paths.get(outputDirectory);
            path.toFile().mkdirs();
            verifierConfig.setOutput(new FileOutputStream(Paths
                .get(outputDirectory, DefaultLocationNames.VERIFIER_OUTPUT_FILE_NAME + Constants.JSON_FILE_EXTENSION)
                .toFile()));
        }
        if (lines != null) {
            int numBatches = new Partitioner(partitionSize).generatePartitionsOfMinimumSize(lines).size();
            List<InputStream> commitmentParametersInputOfBatch = new ArrayList<>(numBatches);
            List<InputStream> decryptedVotesInputOfBatch = new ArrayList<>(numBatches);
            List<InputStream> encryptedBallotsInputOfBatch = new ArrayList<>(numBatches);
            List<InputStream> encryptionParametersInputOfBatch = new ArrayList<>(numBatches);
            List<InputStream> proofsInputOfBatch = new ArrayList<>(numBatches);
            List<InputStream> publicKeyInputOfBatch = new ArrayList<>(numBatches);
            List<InputStream> reEncryptedBallotsInputOfBatch = new ArrayList<>(numBatches);
            List<InputStream> votesWithProofInputOfBatch = new ArrayList<>(numBatches);
            for (int i = 0; i < numBatches; i++) {
                String batchNum = Integer.toString(i);
                commitmentParametersInputOfBatch.add(new FileInputStream(Paths
                    .get(inputDirectory, batchNum,
                        DefaultLocationNames.COMMITMENT_PARAMETERS_OUTPUT_FILE_NAME + Constants.JSON_FILE_EXTENSION)
                    .toFile()));
                encryptedBallotsInputOfBatch.add(new FileInputStream(Paths
                    .get(inputDirectory, batchNum,
                        DefaultLocationNames.ENCRYPTED_BALLOTS_OUTPUT_FILE_NAME + Constants.CSV_FILE_EXTENSION)
                    .toFile()));
                encryptionParametersInputOfBatch.add(new FileInputStream(Paths
                    .get(inputDirectory, batchNum,
                        DefaultLocationNames.ENCRYPTION_PARAMETERS_OUTPUT_FILE_NAME + Constants.JSON_FILE_EXTENSION)
                    .toFile()));
                proofsInputOfBatch
                    .add(
                        new FileInputStream(Paths
                            .get(inputDirectory, batchNum,
                                DefaultLocationNames.PROOFS_OUTPUT_FILE_NAME + Constants.JSON_FILE_EXTENSION)
                            .toFile()));
                publicKeyInputOfBatch
                    .add(new FileInputStream(Paths
                        .get(inputDirectory, batchNum,
                            DefaultLocationNames.PUBLIC_KEY_OUTPUT_FILE_NAME + Constants.JSON_FILE_EXTENSION)
                        .toFile()));
                reEncryptedBallotsInputOfBatch.add(new FileInputStream(Paths
                    .get(inputDirectory, batchNum,
                        DefaultLocationNames.REENCRYPTED_BALLOTS_OUTPUT_FILE_NAME + Constants.CSV_FILE_EXTENSION)
                    .toFile()));
            }

            verifierConfig.setCommitmentParametersInputOfBatch(commitmentParametersInputOfBatch);
            verifierConfig.setDecryptedVotesInputOfBatch(decryptedVotesInputOfBatch);
            verifierConfig.setEncryptedBallotsInputOfBatch(encryptedBallotsInputOfBatch);
            verifierConfig.setEncryptionParametersInputOfBatch(encryptionParametersInputOfBatch);
            verifierConfig.setProofsInputOfBatch(proofsInputOfBatch);
            verifierConfig.setPublicKeyInputOfBatch(publicKeyInputOfBatch);
            verifierConfig.setReEncryptedBallotsInputOfBatch(reEncryptedBallotsInputOfBatch);
            verifierConfig.setVotesWithProofInputOfBatch(votesWithProofInputOfBatch);
        }
        return verifierConfig;
    }
}
