/*
 This file is part of GNUnet.
  (C) 2012, 2013 Christian Grothoff (and other contributing authors)

  GNUnet is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published
  by the Free Software Foundation; either version 3, or (at your
  option) any later version.

  GNUnet is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with GNUnet; see the file COPYING.  If not, write to the
  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  Boston, MA 02111-1307, USA.
 */

package org.gnunet.voting;

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import com.google.common.primitives.Longs;
import org.gnunet.secretsharing.Parameters;
import org.gnunet.secretsharing.ThresholdPublicKey;
import org.gnunet.util.AbsoluteTime;
import org.gnunet.util.Configuration;
import org.gnunet.util.HashCode;
import org.gnunet.util.PeerIdentity;
import org.gnunet.util.crypto.EcdsaPrivateKey;
import org.gnunet.util.crypto.EcdsaPublicKey;
import org.gnunet.util.crypto.EcdsaSignature;
import org.gnunet.util.crypto.EddsaSignature;
import org.gnunet.voting.messages.KeyQueryResponseMessage;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Public information about an election.  The ballot can be serialized / deserialized in form
 * of a GNUnet configuration file.
 */
public class Ballot {
    /**
     * Topic of the election.
     */
    String topic;
    /**
     * Elegibility group for the election.
     */
    String group;
    /**
     * When will the distributed key generation among the authorities start?
     */
    AbsoluteTime keygenStartTime;
    /**
     * When will the distributed key generation among the authorities be finished?
     */
    AbsoluteTime keygenEndTime;
    /**
     * List of choices for the election.
     */
    List<String> choices;
    /**
     * When does the election start?
     */
    AbsoluteTime startTime;
    /**
     * When are no further votes accepted?
     */
    AbsoluteTime closingTime;
    /**
     * When must the consensus between the peers be finished?
     */
    AbsoluteTime concludeTime;
    /**
     * When can results be queried?
     */
    AbsoluteTime queryTime;
    /**
     * When are the election results discarded by the authority?
     */
    AbsoluteTime endTime;
    /**
     * Mapping from authority alias to peer identity.
     */
    BiMap<String,PeerIdentity> authorities;
    /**
     * Signatures from the authority certifying that it
     * will be available for this election.
     */
    SortedMap<String,EddsaSignature> registrationSigs;
    /**
     * Public key of the certificate authority for group membership.
     */
    EcdsaPublicKey caPub;
    /**
     * Public key of the issues.
     */
    EcdsaPublicKey issuerPub;
    /**
     * Signature of the issuer on the basic data of the ballot.
     */
    EcdsaSignature issuerSig;
    /**
     * Public key of the voter that fills out this ballot.
     */
    EcdsaPublicKey voterPub;
    /**
     * Confirmation for the fact that a vote was accepted by an authority.
     */
    SortedMap<String,EddsaSignature> confirmationSigs;
    /**
     * Certificate that a voter belongs to a certain group.
     */
    GroupCert groupCert;
    /**
     * Threshold public keys per authority alias.
     * Ideally this should be the same for each authority, but with malicious authorities
     * we can't trust that, so we must collect all the keys.
     */
    SortedMap<String,KeyQueryResponseMessage> thresholdPublicKeys;

    /**
     * Generators, one for each choice.
     */
    BigInteger generators[];

    /**
     * Threshold for the election.
     */
    public int threshold;

    EncryptedVote encryptedVote;

    /**
     * Load a ballot from file.
     *
     * @param filename name of file to load ballot from
     * @throws InvalidBallotException
     */
    public Ballot(String filename) {
        Configuration cfg = new Configuration();
        cfg.parse(filename);
        fillBallot(cfg);
    }

    /**
     * Load a ballot from a configuration.
     *
     * @param cfg configuration to load ballot from
     * @throws InvalidBallotException
     */
    public Ballot(Configuration cfg) {
        fillBallot(cfg);
    }

    /**
     * Parse the specified time value from the ballot,
     * converting a human-readable time value to human time when no
     * timestamp is given.
     * @param cfg configuration to read the value from
     * @param timeValueName name suffix of the time option
     * @return the time value
     */
    private AbsoluteTime getTime(Configuration cfg, String timeValueName) {
        Optional<String> optTimeHuman = cfg.getValueString("election", "TIME_" + timeValueName);
        Optional<String> optTimeStamp = cfg.getValueString("election", "TIMESTAMP_" + timeValueName);
        if (optTimeStamp.isPresent()) {
            try {
                long stamp = Long.parseLong(optTimeStamp.get());
                return AbsoluteTime.fromSeconds(stamp);
            } catch (NumberFormatException e) {
                throw new InvalidBallotException("time value " + timeValueName + " malformed");
            }
        } else if (optTimeHuman.isPresent()) {
            AbsoluteTime time = AbsoluteTime.fromString(optTimeHuman.get());
            if (null == time) {
                throw new InvalidBallotException("timestamp value " + timeValueName + " malformed");
            }
            return time;
        }
        throw new InvalidBallotException("time value " + timeValueName + " missing");
    }

    /**
     * Fill the ballot with information from the given configuration.
     *
     * @param cfg configuration to read from
     */
    private void fillBallot(Configuration cfg) {
        Optional<String> optTopic = cfg.getValueString("election", "TOPIC");
        if (!optTopic.isPresent()) {
            throw new InvalidBallotException("ballot has no topic");
        }
        topic = optTopic.get();
        Optional<String> optChoices = cfg.getValueString("election", "CHOICES");
        if (!optChoices.isPresent()) {
            throw new InvalidBallotException("ballot has no choices");
        }
        choices = Arrays.asList(optChoices.get().split(Pattern.quote("//")));
        if (choices.size() < 2) {
            throw new InvalidBallotException("less than two choices present");
        }
        Optional<String> optGroup = cfg.getValueString("election", "GROUP");
        if (!optGroup.isPresent()) {
            throw new InvalidBallotException("ballot must have elegibility group");
        }
        group = optGroup.get();
        Optional<Long> optThreshold = cfg.getValueNumber("election", "THRESHOLD");
        if (!optThreshold.isPresent()) {
            throw new InvalidBallotException("ballot must have threshold");
        }
        if (optThreshold.get() <= 0) {
            throw new InvalidBallotException("threshold must be positive");
        }
        threshold = optThreshold.get().intValue();
        authorities = HashBiMap.create();
        for (Map.Entry<String,String> e : cfg.getSection("authorities").entrySet()) {
            String alias = e.getKey();
            PeerIdentity peerIdentity = PeerIdentity.fromString(e.getValue());
            if (peerIdentity == null) {
                throw new InvalidBallotException(
                        String.format("authority %s has invalid peer identity", alias));
            }
            authorities.put(alias, peerIdentity);
        }
        if (authorities.size() < 1) {
            throw new InvalidBallotException("ballot must specify at least one authority");
        }
        Optional<String> optCaPub = cfg.getValueString("election", "CA_PUB");
        if (!optCaPub.isPresent()) {
            throw new InvalidBallotException("no CA pub key given");
        }
        caPub = EcdsaPublicKey.fromString(optCaPub.get());
        if (null == caPub) {
            throw new InvalidBallotException("CA pub key invalid");
        }
        Optional<String> optIssuerPub = cfg.getValueString("election", "ISSUER_PUB");
        if (optIssuerPub.isPresent()) {
            issuerPub = EcdsaPublicKey.fromString(optIssuerPub.get());
            Optional<String> optIssuerSig = cfg.getValueString("election", "ISSUER_SIG");
            if (!optIssuerSig.isPresent()) {
                throw new InvalidBallotException("issuer public key present, but no signature");
            }
            issuerSig = EcdsaSignature.fromString(optIssuerSig.get());
        }
        registrationSigs = new TreeMap<String, EddsaSignature>();
        for (Map.Entry<String,String> e : cfg.getSection("registration-signatures").entrySet()) {
            EddsaSignature sig = EddsaSignature.fromString(e.getValue());
            if (null == sig) {
                throw new InvalidBallotException("registration signature has invalid format");
            }
            if (!authorities.containsKey(e.getKey())) {
                throw new InvalidBallotException("ballot contains superfluous registration signature");
            }
            registrationSigs.put(e.getKey(), sig);
        }
        confirmationSigs = new TreeMap<String, EddsaSignature>();
        for (Map.Entry<String,String> e : cfg.getSection("vouchers").entrySet()) {
            EddsaSignature sig = EddsaSignature.fromString(e.getValue());
            if (null == sig) {
                throw new InvalidBallotException("voucher signature has invalid format");
            }
            if (!authorities.containsKey(e.getKey())) {
                throw new InvalidBallotException("ballot contains superfluous voucher signature");
            }
            confirmationSigs.put(e.getKey(), sig);
        }
        Optional<String> optVoterPub = cfg.getValueString("vote", "VOTER_PUB");
        if (optVoterPub.isPresent()) {
            voterPub = EcdsaPublicKey.fromString(optVoterPub.get());
            if (null == voterPub) {
                throw new InvalidBallotException("voter public key present but invalid");
            }
            encryptedVote = EncryptedVote.parseFromConfiguration(cfg, voterPub);
        }

        startTime = getTime(cfg, "START");
        closingTime = getTime(cfg, "CLOSING");
        concludeTime = getTime(cfg, "CONCLUDE");
        queryTime = getTime(cfg, "QUERY");
        endTime = getTime(cfg, "END");
        keygenStartTime = getTime(cfg, "KEYGEN_START");
        keygenEndTime = getTime(cfg, "KEYGEN_END");

        if (cfg.haveValue("vote", "GROUP_SIG")) {
            groupCert = GroupCert.fromBallotConfig(this, cfg);
        }

        thresholdPublicKeys = new TreeMap<String, KeyQueryResponseMessage>();
        for (Map.Entry<String,String> e : cfg.getSection("threshold-pubkeys").entrySet()) {
            String alias = e.getKey();
            if (!authorities.containsKey(alias)) {
                throw new InvalidBallotException(String.format(
                        "Alias '%s' has threshold pubkey, but alias does not belong to any authority.", alias));
            }
            Optional<String> optSig = cfg.getValueString("threshold-pubkey-sigs", alias);
            if (!optSig.isPresent()) {
                throw new InvalidBallotException(String.format(
                        "no signature present for threshold pubkey from authority '%s'", alias));
            }

            KeyQueryResponseMessage m = new KeyQueryResponseMessage();
            m.signature = EddsaSignature.fromString(optSig.get());
            m.signedGuidKey = new KeyQueryResponseMessage.BallotPublicKey();
            m.signedGuidKey.ballotGuid = getBallotGuid();
            m.signedGuidKey.publicKey = ThresholdPublicKey.fromString(e.getValue());
            thresholdPublicKeys.put(alias, m);
        }
        // If there's one generator, the others have to be there, too.
        if (cfg.haveValue("generators", "g0")) {
            generators = new BigInteger[choices.size()];
            for (int i = 0; i < choices.size(); i++) {
                Optional<String> optG = cfg.getValueString("generators", "g"+i);
                if (!optG.isPresent()) {
                    throw new InvalidBallotException(
                            String.format("Generator %s is missing.  Please issue the ballot correctlly", i));
                }
                generators[i] = new BigInteger(optG.get(), 16);
            }
        }

    }

    /**
     * Get a hash code that uniquely defines the election information of
     * the ballot.
     *
     * @return GUID of this ballot
     */
    public HashCode getBallotGuid() {
        if (issuerPub == null) {
            throw new InvalidBallotException("can't compute GUID of a ballot without issuer");
        }
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("SHA-512");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("crypto algorithm required but not provided");
        }
        digest.update(topic.getBytes());
        for (String choice : choices) {
            digest.update(choice.getBytes());
        }
        for (SortedMap.Entry<String,PeerIdentity> x : authorities.entrySet()) {
            digest.update(x.getKey().getBytes());
            digest.update(x.getValue().data);
        }
        digest.update(issuerPub.y);
        digest.update(caPub.y);
        digest.update(Longs.toByteArray(keygenStartTime.getSeconds()));
        digest.update(Longs.toByteArray(keygenEndTime.getSeconds()));
        digest.update(Longs.toByteArray(startTime.getSeconds()));
        digest.update(Longs.toByteArray(endTime.getSeconds()));
        digest.update(Longs.toByteArray(closingTime.getSeconds()));
        digest.update(Longs.toByteArray(queryTime.getSeconds()));
        return HashCode.fromHashCode(digest.digest());
    }

    /**
     * Get the threshold public key that the majority of authorities
     * advertise.  The majority key must have at least 'threshold' peers that
     * advertise it in order to be valid
     *
     * @return the majority threshold public key
     */
    public ThresholdPublicKey getMajorityThresholdPublicKey() {
        HashMap<ThresholdPublicKey,Integer> counts = Maps.newHashMap();
        for (Map.Entry<String,KeyQueryResponseMessage> e : thresholdPublicKeys.entrySet()) {
            ThresholdPublicKey pk = e.getValue().signedGuidKey.publicKey;
            if (counts.containsKey(pk)) {
                counts.put(pk, counts.get(pk) + 1);
            } else {
                counts.put(pk, 1);
            }
        }
        int maxCount = 0;
        ThresholdPublicKey bestKey = null;
        for (Map.Entry<ThresholdPublicKey, Integer> e : counts.entrySet()) {
            if (e.getValue() > maxCount) {
                maxCount = e.getValue();
                bestKey = e.getKey();
            }
        }
        if (maxCount < threshold) {
            return null;
        }
        return bestKey;
    }

    /**
     * Encode the given choice permanently in the ballot.
     * Also encodes the voter's public key.
     *
     * @param choice the choice to encode the ballot
     * @param voterPrivateKey the private key to use for encoding
     */
    public void encodeChoice(String choice, ThresholdPublicKey thresholdPublicKey,
                             EcdsaPrivateKey voterPrivateKey) {
        if (null == generators || generators.length != choices.size()) {
            throw new InvalidBallotException("Can't encrypt vote without valid generators.");
        }
        int choiceId = -1;
        int i = 0;
        for (String possibleChoice : choices) {
            if (choice.equals(possibleChoice)) {
                choiceId = i;
            }
            i++;
        }
        voterPub = voterPrivateKey.getPublicKey();
        encryptedVote = EncryptedVote.fromChoice(choiceId, thresholdPublicKey, voterPub,
                generators);
        System.out.println("voter encrypted vote, ciphertext: " + encryptedVote.v.toString());
        System.out.println("threshold key (of voter): " + thresholdPublicKey.toString());
    }

    /**
     * Write the current state of the ballot to a configuration.
     *
     * @return a configuration with the state of this ballot
     */
    public Configuration toConfiguration() {
        Configuration cfg = new Configuration();
        cfg.setValueString("election", "TOPIC", topic);
        cfg.setValueString("election", "GROUP", group);
        cfg.setValueString("election", "CHOICES", Joiner.on("//").join(choices));
        cfg.setValueString("election", "CA_PUB", caPub.toString());
        cfg.setValueNumber("election", "THRESHOLD", threshold);
        cfg.setValueNumber("election", "TIMESTAMP_KEYGEN_START", keygenStartTime.getSeconds());
        cfg.setValueNumber("election", "TIMESTAMP_KEYGEN_END", keygenEndTime.getSeconds());
        cfg.setValueNumber("election", "TIMESTAMP_START", startTime.getSeconds());
        cfg.setValueNumber("election", "TIMESTAMP_CLOSING", closingTime.getSeconds());
        cfg.setValueNumber("election", "TIMESTAMP_QUERY", queryTime.getSeconds());
        cfg.setValueNumber("election", "TIMESTAMP_CONCLUDE", concludeTime.getSeconds());
        cfg.setValueNumber("election", "TIMESTAMP_END", endTime.getSeconds());
        for (Map.Entry<String, PeerIdentity> e : authorities.entrySet()) {
            cfg.setValueString("authorities", e.getKey(), e.getValue().toString());
        }
        if (null != registrationSigs) {
            for (Map.Entry<String, EddsaSignature> e : registrationSigs.entrySet()) {
                cfg.setValueString("registration-signatures", e.getKey(), e.getValue().toString());
            }
        }
        if (null != confirmationSigs) {
            for (Map.Entry<String, EddsaSignature> e : confirmationSigs.entrySet()) {
                cfg.setValueString("vouchers", e.getKey(), e.getValue().toString());
            }
        }
        if (null != issuerPub) {
            cfg.setValueString("election", "ISSUER_PUB", issuerPub.toString());
            cfg.setValueString("election", "ISSUER_SIG", issuerSig.toString());
        }
        if (null != encryptedVote) {
            encryptedVote.writeToConfiguration(cfg);
        }
        if (null != voterPub) {
            cfg.setValueString("vote", "VOTER_PUB", voterPub.toString());
        }
        if (null != groupCert) {
            groupCert.writeBallotConfig(cfg);
        }
        for (Map.Entry<String,KeyQueryResponseMessage> e : thresholdPublicKeys.entrySet()) {
            cfg.setValueString("threshold-pubkeys", e.getKey(),
                    e.getValue().signedGuidKey.publicKey.toString());
            cfg.setValueString("threshold-pubkey-sigs", e.getKey(),
                    e.getValue().signature.toString());
        }
        if (generators != null) {
            for (int i = 0; i < generators.length; i++) {
                cfg.setValueString("generators", "g"+i, generators[i].toString(16));
            }
        }
        return cfg;
    }

    /**
     * Serialize the ballot to a string
     *
     * @return the serialized ballot
     */
    public String serialize() {
        return toConfiguration().serialize();
    }


    /**
     * Get a human readable description of the ballot's current state.
     *
     * @return the ballot description
     */
    public String describe() {
        StringBuilder buf = new StringBuilder();

        buf.append("Topic: ");
        buf.append("'");
        buf.append(topic);
        buf.append("'\n");

        buf.append("Voter Group: ");
        buf.append(group);
        buf.append("\n");

        buf.append("Threshold: ");
        buf.append(threshold);
        buf.append("\n");

        buf.append("Start Time: ");
        buf.append(startTime.toFancyString());
        buf.append("\n");
        buf.append("Closing Time: ");
        buf.append(closingTime.toFancyString());
        buf.append("\n");
        buf.append("Conclude Time: ");
        buf.append(concludeTime.toFancyString());
        buf.append("\n");
        buf.append("Query Time: ");
        buf.append(queryTime.toFancyString());
        buf.append("\n");
        buf.append("End Time: ");
        buf.append(endTime.toFancyString());
        buf.append("\n");

        buf.append("Choices:\n");
        for (int i = 0; i < choices.size(); i++) {
            buf.append(i + 1);
            buf.append(". '");
            buf.append(choices.get(i));
            buf.append("'\n");
        }
        buf.append("Authorities:\n");
        for (Map.Entry<String, PeerIdentity> e : authorities.entrySet()) {
            buf.append("'");
            buf.append(e.getKey());
            buf.append("', ");
            buf.append(e.getValue().toString());
            buf.append("\n");
        }
        if (issuerPub == null) {
            buf.append("issue status: not issued\n");
        } else {
            buf.append("issue status: issued, GUID ");
            buf.append(getBallotGuid().toString());
            buf.append("\n");
        }
        if (!registrationSigs.isEmpty()) {
            buf.append("ballot is registered with the following authorities:\n");
            for (Map.Entry<String, EddsaSignature> e : registrationSigs.entrySet()) {
                buf.append(e.getKey());
                buf.append(" ");
            }
            buf.append("\n");
        } else {
            buf.append("ballot not registered");
        }
        if (!confirmationSigs.isEmpty()) {
            buf.append("ballot's vote has been submitted to with the following authorities:\n");
            for (Map.Entry<String, EddsaSignature> e : confirmationSigs.entrySet()) {
                buf.append(e.getKey());
                buf.append(" ");
            }
            buf.append("\n");
        }
        else {
            buf.append("ballot not submitted\n");
        }
        if (encryptedVote != null) {
            buf.append("choice selected\n");
        } else {
            buf.append("no choice selected\n");
        }
        if (voterPub != null) {
            buf.append("voter: ");
            buf.append(voterPub.toString());
            buf.append("\n");
            if (groupCert == null) {
                buf.append("voter not in group\n");
            } else {
                buf.append("voter in group\n");
            }
        } else {
            buf.append("no voter\n");
        }
        if (thresholdPublicKeys != null && thresholdPublicKeys.size() != 0) {
            buf.append("Authorities with threshold public key:\n");
            for (String s : thresholdPublicKeys.keySet()) {
                buf.append(s);
                buf.append("\n");
            }
        } else {
            buf.append("no threshold public keys");
        }
        return buf.toString();
    }


    /**
     * Get the list of authorities that did not yet receive the ballot's vote.
     *
     * @return list of unsubmitted-to authorities
     */
    public List<PeerIdentity> getRemainingSubmitAuthorities() {
        LinkedList<PeerIdentity> remaining = new LinkedList<PeerIdentity>();
        for (SortedMap.Entry<String,PeerIdentity> x : authorities.entrySet()) {
            if (!confirmationSigs.containsKey(x.getKey()))
                remaining.add(x.getValue());
        }
        return remaining;
    }

    /**
     * Get a list of authorities that did not receive the registration for this
     * ballot yet.
     *
     * @return list of authorities that don't know about this ballot
     */
    public List<PeerIdentity> getRemainingRegisterAuthorities() {
        LinkedList<PeerIdentity> remaining = new LinkedList<PeerIdentity>();
        for (SortedMap.Entry<String,PeerIdentity> x : authorities.entrySet()) {
            if (!registrationSigs.containsKey(x.getKey()))
                remaining.add(x.getValue());
        }
        return remaining;
    }

    /**
     * Add the issuer to the ballot, and sign the election information in the ballot.
     * Also selects the generators for every choice.
     *
     * @param privateKey private key of the issuer
     */
    public void issue(EcdsaPrivateKey privateKey) {
        generators = new BigInteger[choices.size()];
        for (int i = 0; i < choices.size(); i++) {
            generators[i] = VotingParameters.selectSubgroupGenerator(Parameters.elgamalP, Parameters.elgamalQ);
        }
        issuerPub = privateKey.getPublicKey();
        // FIXME: purpose
        issuerSig = privateKey.sign(getBallotGuid().data, 0);
    }

    /**
     * Add an authorities registration signature to the list of known registrations.
     *
     * @param currentAuthority authority we registered with
     * @param registrationSignature signature over this ballot's GUID from the authority
     */
    public void addRegistrationSignature(PeerIdentity currentAuthority,
                                         EddsaSignature registrationSignature) {
        String alias = authorities.inverse().get(currentAuthority);
        registrationSigs.put(alias, registrationSignature);
    }

    /**
     * Add a voucher, that is, a signature about the fact that an authority received a vote,
     * to the list of vouchers
     *
     * @param currentAuthority authority that received the vote
     * @param confirmationSignature signature from the authority
     */
    public void addConfirmation(PeerIdentity currentAuthority, EddsaSignature confirmationSignature) {
        String alias = authorities.inverse().get(currentAuthority);
        confirmationSigs.put(alias, confirmationSignature);
    }

    /**
     * Get a list of all authorities.
     *
     * @return list of all authorities
     */
    public List<PeerIdentity> getAuthorities() {
        return new ArrayList<PeerIdentity>(authorities.values());
    }

    public void encodeGroup(GroupCert groupCert) {
        Preconditions.checkNotNull(groupCert);
        if (this.groupCert != null) {
            throw new InvalidBallotException("ballot already has group information");
        }
        // FIXME: add this check again once the crypto is in sync
        /*
        if (voterPub != null && !groupCert.getMemberPublicKey().equals(voterPub)) {
            throw new InvalidBallotException("group and voter public key do not match");
        }
        */
        voterPub = groupCert.getMemberPublicKey();
        this.groupCert = groupCert;
    }

    /**
     * Get the list of authorities that this ballot does not have a signature
     * on the threshold key from.
     */
    public List<PeerIdentity> getRemainingKeyAuthorities() {
        LinkedList<PeerIdentity> remaining = new LinkedList<PeerIdentity>();
        for (Map.Entry<String,PeerIdentity> x : authorities.entrySet()) {
            if (!thresholdPublicKeys.containsKey(x.getKey()))
                remaining.add(x.getValue());
        }
        return remaining;
    }

    public void addThresholdPublicKey(PeerIdentity currentAuthority, KeyQueryResponseMessage m) {
        String alias = authorities.inverse().get(currentAuthority);
        thresholdPublicKeys.put(alias, m);
    }
}
