package meerkat.mixer.main;

import meerkat.mixer.MixGenerator;
import meerkat.mixer.MixerOutput;
import meerkat.mixer.proofs.Mix2nizk;
import meerkat.protobuf.BulletinBoardApi;
import meerkat.protobuf.Crypto;
import meerkat.mixer.necessary.AsyncBulletinBoardClient;


import java.util.ArrayList;
import java.util.List;
import java.util.Random;


/**
 * Created by Tzlil on 12/17/2015.
 * this class define all the operation meerkat.mixProverVerifier.network party should do:
 *  1. receive previous mixers output (re encrypted votes + proofs)
 *  2. verify its input
 *  3. mix
 *  4. send the meerkat.mixProverVerifier.network output
 */
public class MainMixing {

    private final MixGenerator mixer;
    private final Mix2nizk.Verifier verifier;
    private final AsyncBulletinBoardClient asyncBulletinBoardClient;
    private final byte[] id;


    /**
     * constructor
     * @param mixer
     * @param verifier
     * @param asyncBulletinBoardClient
     * @param id
     */
    public MainMixing(MixGenerator mixer, Mix2nizk.Verifier verifier,
                      AsyncBulletinBoardClient asyncBulletinBoardClient, byte[] id) {
        this.mixer = mixer;
        this.verifier = verifier;
        this.asyncBulletinBoardClient = asyncBulletinBoardClient;
        this.id = id;
    }

    /**
     *
     * @param prevBatchIds
     * @param batchId
     * @param random
     * @param callback
     * @throws Throwable
     */
    public void main(List<Integer> prevBatchIds, int batchId, boolean strictVerification, Random random, AsyncBulletinBoardClient.ClientCallback<?> callback) throws Throwable {

        List<Crypto.RerandomizableEncryptedMessage> mixerInput;

        List<BatchHandler> batchHandlers = new ArrayList<BatchHandler>(prevBatchIds.size());
        BatchHandler currentBatchHandler;
        for (Integer prevBatchId : prevBatchIds) {
            currentBatchHandler = new BatchHandler(verifier);
            asyncBulletinBoardClient.readBatch(id, prevBatchId,currentBatchHandler);
            batchHandlers.add(currentBatchHandler);
        }
        // check all handlers messages were received
        for (BatchHandler batchHandler : batchHandlers) {
            synchronized (batchHandler){
                if(!batchHandler.isMsgReceived())
                    batchHandler.wait();
            }
        }
        // assert all handlers succeeded
        for (BatchHandler batchHandler : batchHandlers) {
            if(!batchHandler.verifyTable(strictVerification)){
                throw new Exception("invalid input");
            }
        }

        BatchHandler lastBatchHandler = batchHandlers.get(batchHandlers.size() - 1);
        mixerInput = lastBatchHandler.getInputForMixer(strictVerification);

        MixerOutput mixerOutput = mixer.mix(mixerInput,random);
        updateBB(mixerOutput, batchId, callback);

    }

    /**
     * send meerkat.mixProverVerifier.network output to BB
     * @param mixerOutput
     * @param batchId
     * @param callback
     */
    private void updateBB(MixerOutput mixerOutput
            , int batchId, AsyncBulletinBoardClient.ClientCallback<?> callback) {

        BatchConverter batchConverter = new BatchConverter();
        List<BulletinBoardApi.BatchChunk> batchChunkList = batchConverter.MixerOutput2BatchChunk(mixerOutput);
        asyncBulletinBoardClient.postBatch(id, batchId, batchChunkList, callback);
    }

}
