package dk.alexandra.fresco.lib.common.compare;

import dk.alexandra.fresco.framework.DRes;
import dk.alexandra.fresco.framework.builder.Computation;
import dk.alexandra.fresco.framework.builder.numeric.ProtocolBuilderNumeric;
import dk.alexandra.fresco.framework.value.SInt;
import dk.alexandra.fresco.lib.common.math.AdvancedNumeric;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

/**
 * Computes the Hamming distance between an array of shared bits and a public value.
 *
 */
public class HammingDistance implements Computation<SInt, ProtocolBuilderNumeric> {

  private final List<DRes<SInt>> bits;
  private final BigInteger publicValue;

  public HammingDistance(
      List<DRes<SInt>> bits, BigInteger publicValue) {
    this.bits = bits;
    this.publicValue = publicValue;
  }

  @Override
  public DRes<SInt> buildComputation(ProtocolBuilderNumeric builder) {
    BigInteger one = BigInteger.ONE;
    int length = bits.size();
    BigInteger m = publicValue;
    if (length == 1) {
      if (m.testBit(0)) {
        return builder.numeric().sub(one, bits.get(0));
      } else {
        return bits.get(0);
      }
    } else {
      return builder.par((par) -> {
        List<DRes<SInt>> xor = new ArrayList<>();
        // for each bit i of m negate r_i if m_i is set
        for (int i = 0; i < length; i++) {
          if (m.testBit(i)) {
            xor.add(par.numeric().sub(one, bits.get(i)));
          } else {
            xor.add(bits.get(i));
          }
        }
        return () -> xor;
      }).seq((seq, list) -> AdvancedNumeric.using(seq).sum(list)
      );
    }
  }
}
