package dk.alexandra.fresco.lib.generic;

import dk.alexandra.fresco.framework.MaliciousException;
import dk.alexandra.fresco.framework.network.Network;
import dk.alexandra.fresco.framework.util.ExceptionConverter;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.List;

/**
 * Utility for running secure broadcast.
 */
public class SecureBroadcastUtil {

  private final Network network;
  private final MessageDigest messageDigest;

  /**
   * Creates new {@link SecureBroadcastUtil}. <p>Requires SHA-256 message digest.</p>
   */
  public SecureBroadcastUtil(Network network) {
    this(network, ExceptionConverter.safe(() -> MessageDigest.getInstance("SHA-256"),
        "Configuration error, SHA-256 is needed for SecureBroadcastUtil"));
  }

  private SecureBroadcastUtil(Network network, MessageDigest messageDigest) {
    this.network = network;
    this.messageDigest = messageDigest;
  }

  /**
   * Computes digest of messages and resets md.
   */
  private byte[] computeDigest(List<byte[]> messages) {
    for (byte[] message : messages) {
      messageDigest.update(message);
    }
    byte[] digest = messageDigest.digest();
    messageDigest.reset();
    return digest;
  }

  /**
   * Compares all other digests with own digest and throws if any are not equal.
   *
   * @param ownDigest hash of messages sent
   * @param otherDigests hashes received from other parties
   * @throws MaliciousException if validation fails
   */
  private void validateDigests(byte[] ownDigest, List<byte[]> otherDigests) {
    for (byte[] otherDigest : otherDigests) {
      if (!Arrays.equals(ownDigest, otherDigest)) {
        throw new MaliciousException("Broadcast validation failed");
      }
    }
  }

  /**
   * Computes digest of received messages and sends digest to other parties.
   */
  public byte[] computeAndSendDigests(List<byte[]> messages) {
    byte[] digest = computeDigest(messages);
    network.sendToAll(digest);
    return digest;
  }

  /**
   * Receives digests from other parties and checks that these are consistent with own digest.
   */
  public void receiveAndValidateDigests(byte[] ownDigest) {
    List<byte[]> received = network.receiveFromAll();
    validateDigests(ownDigest, received);
  }

}
