package meerkat.crypto.dkg.gjkr;

import meerkat.crypto.dkg.comm.MessageUtils;
import meerkat.crypto.secretsharing.feldman.VerifiableSecretSharing;
import meerkat.crypto.secretsharing.shamir.Polynomial;
import com.google.protobuf.ByteString;
import meerkat.protobuf.DKG;
import org.factcenter.qilin.primitives.Group;
import org.factcenter.qilin.util.ByteEncoder;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Random;
import java.util.Set;

/**
 * Created by Tzlil on 3/16/2016.
 * TODO: comments
 * TODO: put Channel (ChannelImpl) in constructor
 */
public class Protocol<T> extends meerkat.crypto.dkg.feldman.Protocol<T> {

    private VerifiableSecretSharing<T> maskingShares;
    private final T h;
    private Party<T>[] parties;

    public Protocol(int t, int n, BigInteger zi, Random random, BigInteger q, T g
            , T h, Group<T> group, int id, ByteEncoder<T> byteEncoder) {
        super(t, n, zi, random, q, g, group, id,byteEncoder);
        this.h = h;
        BigInteger r = new BigInteger(q.bitLength(),random).mod(q);
        this.maskingShares = new VerifiableSecretSharing(t,n,r,random,q,h,group);
        this.parties = new Party[n];
        for (int i = 1; i <= n ; i++){
            this.parties[i - 1] = new Party(i,n,t);
        }
        this.parties[id - 1].share = getShare(id);
        this.parties[id - 1].shareT = maskingShares.getShare(id);
        super.setParties(parties);
    }

    protected Party[] getParties(){
        return parties;
    }

    protected void setParties(Party[] parties) {
        super.setParties(parties);
        this.parties = parties;
    }


    @Override
    public void sendSecret(int j) {
        Polynomial.Point secret = getShare(j);
        Polynomial.Point secretT = maskingShares.getShare(j);
        DKG.ShareMessage doubleSecretMessage = createShareMessage(id,j,secret,secretT);
        // TODO: Change SHARE to SHARE
        channel.sendMessage(j, MessageUtils.createMessage(DKG.Payload.Type.SHARE, doubleSecretMessage));
    }


    @Override
    public boolean isValidShare(int i){
        Party party = parties[i - 1];
        return isValidShare(party.share, party.shareT, party.verifiableValues, id);
    }

    /**
     * test if share, shareT are valid with respect to verificationValues
     * @param share
     * @param shareT
     * @param verificationValues
     * @param j
     * @return computeVerificationValue(j,verificationValues,group) == (g ^ share.y) * (h ^ shareT.y) mod q
     */
    public boolean isValidShare(Polynomial.Point share, Polynomial.Point shareT, ArrayList<T> verificationValues, int j){
        try {
            T v = computeVerificationValue(j, verificationValues, group);
            T exp = group.add(group.multiply(g, share.y), group.multiply(h, shareT.y));
            return exp.equals(v);
        }
        catch (NullPointerException e){
            return false;
        }
    }

    /**
     * create complaint message against i and broadcast it
     * @param share
     * @param shareT
     * @param i
     */
    private void broadcastComplaint(Polynomial.Point share, Polynomial.Point shareT, int i){
        DKG.ShareMessage complaint = createShareMessage(i,id,share,shareT);
        channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.COMPLAINT, complaint));
    }

    /**
     * stage4.3 according to the protocol
     *  if check fails for index i, Pj
     */
    public void computeAndBroadcastComplaints(Set<Integer> QUAL){
        Party party;
        for (int i : QUAL) {
            party = parties[i - 1];
            if (i != id) {
                if (!super.isValidShare(party.share, party.commitments, id)) {
                    broadcastComplaint(party.share, party.shareT, i);
                }
            }
        }
    }

    /**
     * compute verification values and broadcast them
     * verificationValues[k] = g ^ commitments [k] * h ^ maskingShares.commitments [k]
     */
    public void computeAndBroadcastVerificationValues(){
        ArrayList<T> verificationValues = new ArrayList<T>(t+1);
        ArrayList<T> hBaseCommitments = maskingShares.getCommitmentsArrayList();
        for (int k = 0 ; k <= t ; k++){
            verificationValues.add(k,group.add(commitmentsArrayList.get(k),hBaseCommitments.get(k)));
        }
        broadcastCommitments(verificationValues);
    }

    /**
     * pack share, shareT i,j to createShareMessage
     * @param i
     * @param j
     * @param share
     * @param shareT
     * @return
     */

    private DKG.ShareMessage createShareMessage(int i, int j, Polynomial.Point share, Polynomial.Point shareT){
        DKG.ShareMessage ShareMessage = DKG.ShareMessage.newBuilder()
                .setI(i)
                .setJ(j)
                .setShare(ByteString.copyFrom(share.y.toByteArray()))
                .setShareT(ByteString.copyFrom(shareT.y.toByteArray()))
                .build();
        return ShareMessage;
    }

    @Override
    public void broadcastComplaintAnswer(int j) {
        DKG.ShareMessage answer = createShareMessage(id,j,getShare(j)
                , maskingShares.getShare(j));
        channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.ANSWER, answer));
    }

    public void broadcastAnswer(Polynomial.Point secret, Polynomial.Point secretT, int i){
        DKG.ShareMessage complaint = createShareMessage(i,id,secret,secretT);
        channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.ANSWER,complaint));
    }

    /**
     * getter
     * @return h
     */
    public T getH() {
        return h;
    }
}
