package dk.alexandra.fresco.suite.spdz2k.datatypes;

import dk.alexandra.fresco.framework.builder.numeric.field.FieldDefinition;
import dk.alexandra.fresco.framework.builder.numeric.field.FieldElement;
import dk.alexandra.fresco.framework.network.serializers.ByteSerializer;
import dk.alexandra.fresco.framework.util.StrictBitVector;
import java.math.BigInteger;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Factory for {@link CompT} instances.
 */
public interface CompUIntFactory<CompT extends CompUInt<?, ?, CompT>> extends FieldDefinition {

  /**
   * Creates random {@link CompT}.
   */
  CompT createRandom();

  /**
   * Creates serializer for {@link CompT} instances.
   */
  ByteSerializer<CompT> getSerializer();

  /**
   * Get length of most significant bits which represent the masking portion.
   */
  int getHighBitLength();

  /**
   * Get length of least significant bits which represent the data portion.
   */
  int getLowBitLength();

  /**
   * Get total bit length.
   */
  default int getCompositeBitLength() {
    return getHighBitLength() + getLowBitLength();
  }

  /**
   * Creates element whose value is zero.
   */
  CompT zero();

  /**
   * Creates new {@link CompT} from a {@link BigInteger}.
   */
  @Override
  CompT createElement(BigInteger value);

  @Override
  CompT createElement(long value);

  @Override
  default CompT createElement(String value) {
    return createElement(new BigInteger(value));
  }

  @Override
  BigInteger getModulus();

  @Override
  default int getBitLength() {
    return getCompositeBitLength();
  }

  /**
   * {@inheritDoc}
   *
   * Note that this method does not convert the entire element but only the lower k bits, since the
   * top s bits do not represent the actual value we are computing over.
   */
  @Override
  BigInteger convertToUnsigned(FieldElement value);

  /**
   * {@inheritDoc}
   *
   * See {@link #convertToUnsigned(FieldElement)}.
   * @param asUnsigned
   */
  @Override
  BigInteger convertToSigned(BigInteger asUnsigned);

  /**
   * Note that this method is consistent with {@link #convertToUnsigned(FieldElement)} and {@link
   * #convertToSigned(BigInteger)} in that in only converts the lower k bits and discards the top s
   * bits.
   */
  @Override
  StrictBitVector convertToBitVector(FieldElement fieldElement);

  @Override
  CompT deserialize(byte[] bytes);

  @Override
  default byte[] serialize(FieldElement object) {
    return ((CompT) object).toByteArray();
  }

  @Override
  default byte[] serialize(List<FieldElement> objects) {
    // TODO hack for now until we figure out a clean way to do serialization using the factory only
    // while keeping things compliant with FieldDefinition interface
    List<CompT> correctType = objects.stream().map(x -> (CompT) x).collect(Collectors.toList());
    return getSerializer().serialize(correctType);
  }

  @Override
  default List<FieldElement> deserializeList(byte[] bytes) {
    // TODO hack for now until we figure out a clean way to do serialization using the factory only
    // while keeping things compliant with FieldDefinition interface
    List<CompT> wrongType = getSerializer().deserializeList(bytes);
    return wrongType.stream().map(x -> (FieldElement) x).collect(Collectors.toList());
  }

}
