/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.util;

import java.io.Serializable;

/**
 * BitVector.java
 *
 * implements a bit vector
 */
public final class BitVector implements Serializable {
  /** Support for serialization */
  static final long serialVersionUID = 6961578653974090041L;

  private static final int LOG_BITS_PER_UNIT = 5;
  private static final int MASK = 0xffffffff;
  private static final int LOW_MASK = 0x1f;
  private final int[] bits;
  private final int nbits;

  /**
   * Convert bitIndex to a subscript into the bits[] array.
   */
  private static int subscript(int bitIndex) {
    return bitIndex >> LOG_BITS_PER_UNIT;
  }

  /**
   * Creates an empty string with the specified size.
   * @param nbits the size of the string
   */
  public BitVector(int nbits) {
    // subscript(nbits) is the length of the array needed to
    // hold nbits
    bits = new int[subscript(nbits) + 1];
    this.nbits = nbits;
  }

  /**
   * Creates a copy of a Bit String
   * @param s the string to copy
   */
  public BitVector(BitVector s) {
    bits = new int[s.bits.length];
    this.nbits = s.nbits;
    System.arraycopy(s.bits, 0, this.bits, 0, s.bits.length);
  }

  /**
   * Sets all bits.
   */
  public void setAll() {
    for (int i = 0; i < bits.length; i++) {
      bits[i] = MASK;
    }
  }

  /**
   * Sets a bit.
   * @param bit the bit to be set
   */
  public void set(int bit) {
    int shiftBits = bit & LOW_MASK;
    bits[subscript(bit)] |= (1 << shiftBits);
  }

  /**
   * Clears all bits.
   */
  public void clearAll() {
    for (int i = 0; i < bits.length; i++) {
      bits[i] = 0;
    }
  }

  /**
   * Clears a bit.
   * @param bit the bit to be cleared
   */
  public void clear(int bit) {
    int shiftBits = bit & LOW_MASK;
    bits[subscript(bit)] &= ~(1 << shiftBits);
  }

  /**
   * Gets a bit.
   * @param bit the bit to be gotten
   */
  public boolean get(int bit) {
    int shiftBits = bit & LOW_MASK;
    int n = subscript(bit);
    return ((bits[n] & (1 << shiftBits)) != 0);
  }

  /**
   * Logically NOT this bit string
   */
  public void not() {
    for (int i = 0; i < bits.length; i++) {
      bits[i] ^= MASK;
    }
  }

  /**
   * Return the NOT of a bit string
   */
  public static BitVector not(BitVector s) {
    BitVector b = new BitVector(s);
    b.not();
    return b;
  }

  /**
   * Logically ANDs this bit set with the specified set of bits.
   * @param set the bit set to be ANDed with
   */
  public void and(BitVector set) {
    if (this == set) {
      return;
    }
    int n = bits.length;
    for (int i = n; i-- > 0;) {
      bits[i] &= set.bits[i];
    }
  }

  /**
   * Return a new bit string as the AND of two others.
   */
  public static BitVector and(BitVector b1, BitVector b2) {
    BitVector b = new BitVector(b1);
    b.and(b2);
    return b;
  }

  /**
   * Logically ORs this bit set with the specified set of bits.
   * @param set the bit set to be ORed with
   */
  public void or(BitVector set) {
    if (this == set) { // should help alias analysis
      return;
    }
    int setLength = set.bits.length;
    for (int i = setLength; i-- > 0;) {
      bits[i] |= set.bits[i];
    }
  }

  /**
   * Return a new BitVector as the OR of two others
   */
  public static BitVector or(BitVector b1, BitVector b2) {
    BitVector b = new BitVector(b1);
    b.or(b2);
    return b;
  }

  /**
   * Logically XORs this bit set with the specified set of bits.
   * @param set the bit set to be XORed with
   */
  public void xor(BitVector set) {
    int setLength = set.bits.length;
    for (int i = setLength; i-- > 0;) {
      bits[i] ^= set.bits[i];
    }
  }

  /**
   * Check if the intersection of the two sets is empty
   * @param other the set to check intersection with
   */
  public boolean intersectionEmpty(BitVector other) {
    int n = bits.length;
    for (int i = n; i-- > 0;) {
      if ((bits[i] & other.bits[i]) != 0) return false;
    }
    return true;
  }

  /**
   * Copies the values of the bits in the specified set into this set.
   * @param set the bit set to copy the bits from
   */
  public void copyBits(BitVector set) {
    System.arraycopy(set.bits, 0, this.bits, 0, set.bits.length);
  }

  /**
   * Gets the hashcode.
   */
  public int hashCode() {
    int h = 1234;
    for (int i = bits.length; --i >= 0;) {
      h ^= bits[i] * (i + 1);
    }
    return h;
  }

  /**
   * How many bits are set?
   */
  public int populationCount() {
    int count = 0;
    for (int bit : bits) {
      count += Integer.bitCount(bit);
    }
    return count;
  }

  /**
   * Calculates and returns the set's size in bits.
   * The maximum element in the set is the size - 1st element.
   */
  public int length() {
    return bits.length << LOG_BITS_PER_UNIT;
  }

  /**
   * Compares this object against the specified object.
   * @param obj the object to compare with
   * @return true if the objects are the same; false otherwise.
   */
  public boolean equals(Object obj) {
    if ((obj != null) && (obj instanceof BitVector)) {
      if (this == obj) { // should help alias analysis
        return true;
      }
      BitVector set = (BitVector) obj;
      int n = bits.length;
      if (n != set.bits.length) return false;
      for (int i = n; i-- > 0;) {
        if (bits[i] != set.bits[i]) {
          return false;
        }
      }
      return true;
    }
    return false;
  }

  public boolean isZero() {
    int setLength = bits.length;
    for (int i = setLength; i-- > 0;) {
      if (bits[i] != 0) return false;
    }
    return true;
  }

  public BitVector dup() {
    return new BitVector(this);
  }

  /**
   * Converts the BitVector to a String.
   */
  public String toString() {
    StringBuilder buffer = new StringBuilder();
    boolean needSeparator = false;
    buffer.append('{');
//    int limit = length();
    int limit = this.nbits;
    for (int i = 0; i < limit; i++) {
      if (get(i)) {
        if (needSeparator) {
          buffer.append(", ");
        } else {
          needSeparator = true;
        }
        buffer.append(i);
      }
    }
    buffer.append('}');
    return buffer.toString();
  }
}
