/**
 * This class implements an inefficient fixed size mutable binary string. The
 * bits of a BitString are indexed by non-negative integers. Individual bits 
 * can be examined, set (made true), cleared (made false), or flipped (set to
 * the opposite value). The logical AND, logical inclusive OR, and and other
 * operations can be used to combine two BitStrings of the same size and to 
 * manipulate BitStrings. By default, all bits in the string initially are 
 * false. Unlike text strings, bit strings indexes increase from right to left
 * i.e. the most significant bits have larger indexes. For example, the 
 * BitString 1011 has ones in indexes 0, 1, and 3. Do not use integer types, 
 * BitSet, or any other Java class to implement the missing methods. You may use
 * the Math Class if needed. 
 */
public class BitString implements Cloneable {
  // An array to hold the bits that make up the bit string.
  private boolean bits[];
  /**
   *  A constant that defines the size of the default bit string.
   */
  public static final int DEFAULT_SIZE = 8;

  /**
   * Creates a new, all false, bit string of the given size. 
   */
  public BitString(int size) {
    if(size < 1) throw new IllegalArgumentException("Size must be positive");
    bits = new boolean[size];
  }

  /**
   * Creates a new all false bit string of size DEFAULT_SIZE.
   */
  public BitString() {
    this(DEFAULT_SIZE);
  }

  /**
   * Creates a new bit string from the give text string of ones and zeros. This
   * method is only to be used for displaying BitStrings and for testing.
   * It is not to be use in implementing other methods.
   */
   public BitString(String s) {
     this(s.length());

     int index = s.length() - 1;
     for (char digit : s.toCharArray())
     {
       switch(digit)
       {
         case '0':
           bits[index] = false;
           break;
         case '1':
           bits[index] = true;
           break;
         default:
           throw new IllegalArgumentException("String " + s + 
                                              "may only contain 0's and 1's.");                 
       }
       --index;
     }
   }
   
  /**
   * Creates a copy of the given bit string.
   */
  @Override
  public Object clone() {
    BitString copy = new BitString(bits.length);
    copy.bits = Arrays.copyOf(bits, bits.length);
    return copy;
  }

  /**
   * Returns a representation of this BitString as a string of 1's and 0's.
   * This method is only to be used for displaying BitStrings and for testing.
   * It is not to be use in implementing other methods.
   */
  @Override
  public String toString() {
    StringBuilder out = new StringBuilder(bits.length);

    for (int index = bits.length - 1; index >= 0; --index) {
      out.append(bits[index] ? 1 : 0);
    }

    return out.toString();
  }

  /**
   * Return the value of a bit string at the given index.
   */
  public boolean get(int index) {
    return bits[index];
  }

  /**
   * Set the value of a bit string at the given index to true.
   */
  public void set(int index) {
    bits[index] = true;
  }

  /**
   * Set the value of a bit string at the given index to false.
   */
  public void clear(int index) {
    bits[index] = false;
  }

  /**
   * Set the value of a bit string at the given index to the opposite value.
   */
  public void flip(int index) {
    bits[index] = !bits[index];
  }

  /**
   * Returns the number of bits in this bit string.
   */
  public int size() {
    return bits.length;
  }

  /**
   * Returns the number of true bits.
   */
  public int populationCount() {
    int count = 0;

    // For each true bit increment count. 
    for (boolean bit : bits) {
      if (bit) {
        ++count;
      }
    }

    return count;
  }

  /**
   * Basic hash code that is compatible with equals.
   * @return 
   */
  @Override
  public int hashCode() {
    int hash = 5;
    hash = 29 * hash + Arrays.hashCode(this.bits);
    return hash;
  }

  /**
   * Two bit strings are equal if they have the same size and same bits.
   */
  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof BitString)) {
      throw new IllegalArgumentException("obj must be a BitString");
    }

    return Arrays.equals(bits, ((BitString) obj).bits);
  }

  /**
   * An object factory method that creates a bit string corresponding to the 
   * non-negative decimal value n, using size bits. If size is too small to 
   * hold n an InsuffisantNumberOfBitsException is thrown.
   */
  public static BitString decimalToUnsigned(int n, int size) {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Turns the bit string into its binary successor. For example, the successor
   * of 101011 is 101100. Returns reference to self for object chains.
   */
  public BitString successor() {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Turns bit string into its binary complement. For example, the complement of
   * 101011 is 010100. Returns reference to self for object chains.
   */
  public BitString complement() {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Turns bit string into its two's complement. For example, the two's 
   * complement of 101011 is 010101. Returns reference to self for object 
   * chains.
   */
  public BitString twosComplement() {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Returns the value of this bit string when interpreted as an unsigned
   * decimal. For example, the string 111 has unsigned value 7.
   */
  public int unsignedValue() {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Returns the value of this bit string when interpreted as a signed
   * decimal with the MSB being the sign bit. For example, the string 111 has
   * unsigned value -3.
   */
  public int signedValue() {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Returns the value of this bit string when interpreted as a decimal in 
   * one's complement form. For example, in one's complement form the string
   * 111 has the value 0.
   */
  public int onesComplementValue() {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Returns the value of this bit string when interpreted as a decimal in 
   * two's complement form. For example, in two's complement form the string
   * 111 has the value -1.
   */
  public int twosComplementValue() {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Performs binary addition on bit strings of the same size. If they are
   * not of the same size then a BitSizeMismatchException is thrown. Does not
   * change the receiver.
   */
  public BitString add(BitString rightSummand) {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Performs binary subtraction on bit strings of the same size. If they are
   * not of the same size then a BitSizeMismatchException is thrown. Does not
   * change the receiver.
   */
  public BitString subtract(BitString subtrahend) {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Performs "logical and" on bit strings of the same size. If they are
   * not of the same size then a BitSizeMismatchException is thrown. Does not
   * change the receiver.
   */
  public BitString logicalAnd(BitString operand) {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Performs "logical inclusive or" on bit strings of the same size. If they
   * are not of the same size then a BitSizeMismatchException is thrown. Does not
   * change the receiver.
   */
  public BitString logicalOr(BitString operand) {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Performs a left shift by the given non negative amount. For example, the 
   * left shift of 01101 by 2 is 10100. Returns reference to self for object 
   * chains. 
   */
  public BitString leftShift(int amount) {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * Performs a sign extension by the given non negative amount. For example,
   * the extension of 10101 by 2 is 1110101. Returns reference to self for object 
   * chains.
   */
  public BitString signExtension(int amount) {
    throw new UnsupportedOperationException("This function needs to be completed!");
  }

  /**
   * A run time exception that should be thrown whenever a method does not have
   * enough bits to successfully execute.
   */
  public static class InsufficientNumberOfBitsException extends RuntimeException {}

  /**
   * A run time exception that should be thrown whenever two bit strings should 
   * be of the same size but are not.
   */
  public static class BitSizeMismatchException extends RuntimeException {}
}
