/***
 * Copyright 2002-2010 jamod development team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ***/

package net.wimpi.modbus.util;


/**
 * Class that implements a collection for
 * bits, storing them packed into bytes.
 * Per default the access operations will index from
 * the LSB (rightmost) bit.
 *
 * @author Dieter Wimberger
 * @version @version@ (@date@)
 */
public final class BitVector {

  //instance attributes
  private int m_Size;
  private byte[] m_Data;
  private boolean m_MSBAccess = false;

  /**
   * Constructs a new <tt>BitVector</tt> instance
   * with a given size.
   * <p>
   * @param size the number of bits the <tt>BitVector</tt>
   *        should be able to hold.
   */
  public BitVector(int size) {
    //store bits
    m_Size = size;

    //calculate size in bytes
    if ((size % 8) > 0) {
      size = (size / 8) + 1;
    } else {
      size = (size / 8);
    }
    m_Data = new byte[size];
  }//constructor

  /**
   * Toggles the flag deciding whether the LSB
   * or the MSB of the byte corresponds to the
   * first bit (index=0).
   */
  public void toggleAccess() {
    m_MSBAccess = !m_MSBAccess;
  }//toggleAccess

  /**
   * Toggles the flag deciding whether the LSB
   * or the MSB of the byte corresponds to the
   * first bit (index=0).
   *
   * @param b true if LSB=0 up to MSB=7, false otherwise.
   */
  public void toggleAccess(boolean b) {
    m_MSBAccess = b;
  }//toggleAccess

  /**
   * Tests if this <tt>BitVector</tt> has
   * the LSB (rightmost) as the first bit
   * (i.e. at index 0).
   *
   * @return true if LSB=0 up to MSB=7, false otherwise.
   */
  public boolean isLSBAccess() {
    return !m_MSBAccess;
  }//isLSBAccess

  /**
   * Tests if this <tt>BitVector</tt> has
   * the MSB (leftmost) as the first bit
   * (i.e. at index 0).
   *
   * @return true if LSB=0 up to MSB=7, false otherwise.
   */
  public boolean isMSBAccess() {
    return m_MSBAccess;
  }//isMSBAccess

  /**
   * Returns the <tt>byte[]</tt> which is used to store
   * the bits of this <tt>BitVector</tt>.
   * <p>
   * @return the <tt>byte[]</tt> used to store the bits.
   */
  public final byte[] getBytes() {
    return m_Data;
  }//getBytes

  /**
   * Sets the <tt>byte[]</tt> which stores
   * the bits of this <tt>BitVector</tt>.
   * <p>
   * @param data a <tt>byte[]</tt>.
   */
  public final void setBytes(byte[] data) {
    System.arraycopy(data, 0, m_Data, 0, data.length);
  }//setBytes

  /**
   * Sets the <tt>byte[]</tt> which stores
   * the bits of this <tt>BitVector</tt>.
   * <p>
   * @param data a <tt>byte[]</tt>.
   */
  public final void setBytes(byte[] data, int size) {
    System.arraycopy(data, 0, m_Data, 0, data.length);
    m_Size = size;
  }//setBytes

  /**
   * Returns the state of the bit at the given index of this
   * <tt>BitVector</tt>.
   * <p>
   * @param index the index of the bit to be returned.
   *
   * @return true if the bit at the specified index is set,
   *         false otherwise.
   *
   * @throws IndexOutOfBoundsException if the index is out of bounds.
   */
  public final boolean getBit(int index)
      throws IndexOutOfBoundsException {
    index = translateIndex(index);
    //System.out.println("Get bit #" + index);
    return (
        (m_Data[byteIndex(index)]
        & (0x01 << bitIndex(index))) != 0
        ) ? true : false;
  }//getBit


  /**
   * Sets the state of the bit at the given index of
   * this <tt>BitVector</tt>.
   * <p>
   * @param index the index of the bit to be set.
   * @param b true if the bit should be set, false if it should be reset.
   *
   * @throws IndexOutOfBoundsException if the index is out of bounds.
   */
  public final void setBit(int index, boolean b)
      throws IndexOutOfBoundsException {
    index = translateIndex(index);
    //System.out.println("Set bit #"+index);
    int value = ((b) ? 1 : 0);
    int byteNum = byteIndex(index);
    int bitNum = bitIndex(index);
    m_Data[byteNum] = (byte) (
        (m_Data[byteNum] & ~(0x01 << bitNum))
        | ((value & 0x01) << bitNum)
        );
  }//setBit

  /**
   * Returns the number of bits in this <tt>BitVector</tt>
   * as <tt>int</tt>.
   * <p>
   * @return the number of bits in this <tt>BitVector</tt>.
   */
  public final int size() {
    return m_Size;
  }//size

  /**
   * Forces the number of bits in this <tt>BitVector</tt>.
   * 
   * @param size
   * @throws IllegalArgumentException if the size exceeds
   *         the byte[] store size multiplied by 8.
   */
  public final void forceSize(int size) {
    if(size > m_Data.length * 8) {
      throw new IllegalArgumentException("Size exceeds byte[] store.");
    } else {
      m_Size = size;
    }
  }//forceSize

  /**
   * Returns the number of bytes used to store the
   * collection of bits as <tt>int</tt>.
   * <p>
   * @return the number of bits in this <tt>BitVector</tt>.
   */
  public final int byteSize() {
    return m_Data.length;
  }//byteSize

  /**
   * Returns a <tt>String</tt> representing the
   * contents of the bit collection in a way that
   * can be printed to a screen or log.
   * <p>
   * Note that this representation will <em>ALLWAYS</em>
   * show the MSB to the left and the LSB to the right
   * in each byte.
   *
   * @return a <tt>String</tt> representing this <tt>BitVector</tt>.
   */
  public String toString() {
    StringBuffer sbuf = new StringBuffer();
    for (int i = 0; i < size(); i++) {
      sbuf.append(
          ((((m_Data[byteIndex(i)]
          & (0x01 << bitIndex(i))) != 0
          ) ? true : false) ? '1' : '0')
      );
      if (((i + 1) % 8) == 0) {
        sbuf.append(" ");
      }
    }
    return sbuf.toString();
  }//toString


  /**
   * Returns the index of the byte in the the byte array
   * that contains the given bit.
   * <p>
   * @param index the index of the bit.
   *
   * @return the index of the byte where the given bit is stored.
   *
   * @throws IndexOutOfBoundsException if index is
   *         out of bounds.
   */
  private final int byteIndex(int index)
      throws IndexOutOfBoundsException {

    if (index < 0 || index >= m_Data.length * 8) {
      throw new IndexOutOfBoundsException();
    } else {
      return index / 8;
    }
  }//byteIndex

  /**
   * Returns the index of the given bit in the byte
   * where it it stored.
   * <p>
   * @param index the index of the bit.
   *
   * @return the bit index relative to the position in the byte
   *         that stores the specified bit.
   *
   * @throws IndexOutOfBoundsException if index is
   *         out of bounds.
   */
  private final int bitIndex(int index)
      throws IndexOutOfBoundsException {

    if (index < 0 || index >= m_Data.length * 8) {
      throw new IndexOutOfBoundsException();
    } else {
      return index % 8;
    }
  }//bitIndex

  private final int translateIndex(int idx) {

    if (m_MSBAccess) {
      int mod4 = idx % 4;
      int div4 = idx / 4;

      if ((div4 % 2) != 0) {
        //odd
        return (idx + ODD_OFFSETS[mod4]);
      } else {
        //straight
        return (idx + STRAIGHT_OFFSETS[mod4]);
      }
    } else {
      return idx;
    }
  }//translateIndex

  /**
   * Factory method for creating a <tt>BitVector</tt> instance
   * wrapping the given byte data.
   *
   * @param data a byte[] containing packed bits.
   * @return the newly created <tt>BitVector</tt> instance.
   */
  public static BitVector createBitVector(byte[] data, int size) {
    BitVector bv = new BitVector(data.length * 8);
    bv.setBytes(data);
    bv.m_Size = size;
    return bv;
  }//createBitVector

  /**
   * Factory method for creating a <tt>BitVector</tt> instance
   * wrapping the given byte data.
   *
   * @param data a byte[] containing packed bits.
   * @return the newly created <tt>BitVector</tt> instance.
   */
  public static BitVector createBitVector(byte[] data) {
    BitVector bv = new BitVector(data.length * 8);
    bv.setBytes(data);
    return bv;
  }//createBitVector

  public static void main(String[] args) {
    BitVector test = new BitVector(24);
    System.out.println(test.isLSBAccess());
    test.setBit(7, true);
    System.out.println(test.getBit(7));
    test.toggleAccess(true);
    System.out.println(test.getBit(7));

    test.toggleAccess(true);
    test.setBit(6, true);
    test.setBit(3, true);
    test.setBit(2, true);

    test.setBit(0, true);
    test.setBit(8,true);
    test.setBit(10,true);

    System.out.println(test);
    test.toggleAccess(true);
    System.out.println(test);
    test.toggleAccess(true);
    System.out.println(test);

    System.out.println(ModbusUtil.toHex(test.getBytes()));
  }


  private static final int[] ODD_OFFSETS = {-1, -3, -5, -7};
  private static final int[] STRAIGHT_OFFSETS = {7, 5, 3, 1};
}//class BitVector
