package dk.alexandra.fresco.lib.common.math.bool.add;

import dk.alexandra.fresco.framework.DRes;
import dk.alexandra.fresco.framework.builder.Computation;
import dk.alexandra.fresco.framework.builder.binary.ProtocolBuilderBinary;
import dk.alexandra.fresco.framework.util.Pair;
import dk.alexandra.fresco.framework.value.SBool;
import dk.alexandra.fresco.lib.common.math.AdvancedBinary;
import java.util.ArrayList;
import java.util.List;

/**
 * Increment a binary vector with a secret boolean. The class uses the naive approach of linking
 * 1-Bit-Half Adders together to implement a generic length adder.
 *
 */
public class BitIncrementer
    implements Computation<List<DRes<SBool>>, ProtocolBuilderBinary> {

  private List<DRes<SBool>> base;
  private DRes<SBool> increment;

  public BitIncrementer(List<DRes<SBool>> base, DRes<SBool> increment) {
    this.base = base;
    this.increment = increment;
  }


  @Override
  public DRes<List<DRes<SBool>>> buildComputation(ProtocolBuilderBinary builder) {

    List<DRes<SBool>> result = new ArrayList<DRes<SBool>>();

    return builder.seq(seq -> {
      int idx = base.size() - 1;
      IterationState is =
          new IterationState(idx, AdvancedBinary.using(seq).oneBitHalfAdder(base.get(idx), increment));
      return is;
    }).whileLoop((state) -> state.round >= 1, (seq, state) -> {
      int idx = state.round - 1;

      result.add(0, state.value.out().getFirst());
      IterationState is = new IterationState(idx,
          AdvancedBinary.using(seq).oneBitHalfAdder(base.get(idx), state.value.out().getSecond()));
      return is;
    }).seq((seq, state) -> {
      result.add(0, state.value.out().getFirst());
      result.add(0, state.value.out().getSecond());
      return () -> result;
    });
  }

  private static final class IterationState implements DRes<IterationState> {

    private int round;
    private final DRes<Pair<SBool, SBool>> value;

    private IterationState(int round, DRes<Pair<SBool, SBool>> value) {
      this.round = round;
      this.value = value;
    }

    @Override
    public IterationState out() {
      return this;
    }
  }
}
