/*
 * $ProjectName$
 * $ProjectRevision$
 * -----------------------------------------------------------
 * $Id: ByteArrayBitInputStream.java,v 1.3 2003/04/10 19:48:31 jarnbjo Exp $
 * -----------------------------------------------------------
 *
 * $Author: jarnbjo $
 *
 * Description:
 *
 * Copyright 2002-2003 Tor-Einar Jarnbjo
 * -----------------------------------------------------------
 *
 * Change History
 * -----------------------------------------------------------
 * $Log: ByteArrayBitInputStream.java,v $
 * Revision 1.3  2003/04/10 19:48:31  jarnbjo
 * no message
 *
 * Revision 1.2  2003/03/16 01:11:39  jarnbjo
 * no message
 *
 * Revision 1.1  2003/03/03 21:02:20  jarnbjo
 * no message
 *
 */

package de.jarnbjo.util.io;

import java.io.IOException;

/**
 *  Implementation of the <code>BitInputStream</code> interface,
 *  using a byte array as data source.
*/

public class ByteArrayBitInputStream implements BitInputStream {

   private byte[] source;
   private byte currentByte;

   private int endian;

   private int byteIndex=0;
   private int bitIndex=0;

   public ByteArrayBitInputStream(byte[] source) {
      this(source, LITTLE_ENDIAN);
   }

   public ByteArrayBitInputStream(byte[] source, int endian) {
      this.endian=endian;
      this.source=source;
      currentByte=source[0];
      bitIndex=(endian==LITTLE_ENDIAN)?0:7;
   }

   public boolean getBit() throws IOException {
      if(endian==LITTLE_ENDIAN) {
         if(bitIndex>7) {
            bitIndex=0;
            currentByte=source[++byteIndex];
         }
         return (currentByte&(1<<(bitIndex++)))!=0;
      }
      else {
         if(bitIndex<0) {
            bitIndex=7;
            currentByte=source[++byteIndex];
         }
         return (currentByte&(1<<(bitIndex--)))!=0;
      }
   }

   public int getInt(int bits) throws IOException {
      if(bits>32) {
         throw new IllegalArgumentException("Argument \"bits\" must be <= 32");
      }
      int res=0;
      if(endian==LITTLE_ENDIAN) {
         for(int i=0; i<bits; i++) {
            if(getBit()) {
               res|=(1<<i);
            }
         }
      }
      else {
         if(bitIndex<0) {
            bitIndex=7;
            currentByte=source[++byteIndex];
         }
         if(bits<=bitIndex+1) {
            int ci=((int)currentByte)&0xff;
            int offset=1+bitIndex-bits;
            int mask=((1<<bits)-1)<<offset;
            res=(ci&mask)>>offset;
            bitIndex-=bits;
         }
         else {
            res=(((int)currentByte)&0xff&((1<<(bitIndex+1))-1))<<(bits-bitIndex-1);
            bits-=bitIndex+1;
            currentByte=source[++byteIndex];
            while(bits>=8) {
               bits-=8;
               res|=(((int)source[byteIndex])&0xff)<<bits;
               currentByte=source[++byteIndex];
            }
            if(bits>0) {
               int ci=((int)source[byteIndex])&0xff;
               res|=(ci>>(8-bits))&((1<<bits)-1);
               bitIndex=7-bits;
            }
            else {
               currentByte=source[--byteIndex];
               bitIndex=-1;
            }
         }
      }

      return res;
   }

   public int getSignedInt(int bits) throws IOException {
      int raw=getInt(bits);
      if(raw>=1<<(bits-1)) {
         raw-=1<<bits;
      }
      return raw;
   }

   public int getInt(HuffmanNode root) throws IOException {
      while(root.value==null) {
         if(bitIndex>7) {
            bitIndex=0;
            currentByte=source[++byteIndex];
         }
         root=(currentByte&(1<<(bitIndex++)))!=0?root.o1:root.o0;
      }
      return root.value.intValue();
   }

   public long getLong(int bits) throws IOException {
      if(bits>64) {
         throw new IllegalArgumentException("Argument \"bits\" must be <= 64");
      }
      long res=0;
      if(endian==LITTLE_ENDIAN) {
         for(int i=0; i<bits; i++) {
            if(getBit()) {
               res|=(1L<<i);
            }
         }
      }
      else {
         for(int i=bits-1; i>=0; i--) {
            if(getBit()) {
               res|=(1L<<i);
            }
         }
      }
      return res;
   }

	/**
	 *  <p>reads an integer encoded as "signed rice" as described in
	 *  the FLAC audio format specification</p>
	 *	
	 *  <p><b>not supported for little endian</b></p>
	 *
	 *  @param order 
	 *  @return the decoded integer value read from the stream
	 *
	 *  @throws IOException if an I/O error occurs
	 *  @throws UnsupportedOperationException if the method is not supported by the implementation
	 */
    
   public int readSignedRice(int order) throws IOException {

      int msbs=-1, lsbs=0, res=0;

      if(endian==LITTLE_ENDIAN) {
         // little endian
         throw new UnsupportedOperationException("ByteArrayBitInputStream.readSignedRice() is only supported in big endian mode");
      }
      else {
         // big endian

         byte cb=source[byteIndex];
         do {
            msbs++;
            if(bitIndex<0) {
               bitIndex=7;
               byteIndex++;
               cb=source[byteIndex];
            }
         } while((cb&(1<<bitIndex--))==0);

         int bits=order;

         if(bitIndex<0) {
            bitIndex=7;
            byteIndex++;
         }
         if(bits<=bitIndex+1) {
            int ci=((int)source[byteIndex])&0xff;
            int offset=1+bitIndex-bits;
            int mask=((1<<bits)-1)<<offset;
            lsbs=(ci&mask)>>offset;
            bitIndex-=bits;
         }
         else {
            lsbs=(((int)source[byteIndex])&0xff&((1<<(bitIndex+1))-1))<<(bits-bitIndex-1);
            bits-=bitIndex+1;
            byteIndex++;
            while(bits>=8) {
               bits-=8;
               lsbs|=(((int)source[byteIndex])&0xff)<<bits;
               byteIndex++;
            }
            if(bits>0) {
               int ci=((int)source[byteIndex])&0xff;
               lsbs|=(ci>>(8-bits))&((1<<bits)-1);
               bitIndex=7-bits;
            }
            else {
               byteIndex--;
               bitIndex=-1;
            }
         }

         res=(msbs<<order)|lsbs;
      }

      return (res&1)==1?-(res>>1)-1:(res>>1);
   }

	/**
	 *  <p>fills the array from <code>offset</code> with <code>len</code> 
	 *  integers encoded as "signed rice" as described in
	 *  the FLAC audio format specification</p>
	 *	
	 *  <p><b>not supported for little endian</b></p>
	 *
	 *  @param order 
	 *  @param buffer
	 *  @param offset
	 *  @param len 
	 *  @return the decoded integer value read from the stream
	 *
	 *  @throws IOException if an I/O error occurs
	 *  @throws UnsupportedOperationException if the method is not supported by the implementation
	 */

   public void readSignedRice(int order, int[] buffer, int off, int len) throws IOException {

      if(endian==LITTLE_ENDIAN) {
         // little endian
         throw new UnsupportedOperationException("ByteArrayBitInputStream.readSignedRice() is only supported in big endian mode");
      }
      else {
         // big endian
         for(int i=off; i<off+len; i++) {

            int msbs=-1, lsbs=0;

            byte cb=source[byteIndex];
            do {
               msbs++;
               if(bitIndex<0) {
                  bitIndex=7;
                  byteIndex++;
                  cb=source[byteIndex];
               }
            } while((cb&(1<<bitIndex--))==0);

            int bits=order;

            if(bitIndex<0) {
               bitIndex=7;
               byteIndex++;
            }
            if(bits<=bitIndex+1) {
               int ci=((int)source[byteIndex])&0xff;
               int offset=1+bitIndex-bits;
               int mask=((1<<bits)-1)<<offset;
               lsbs=(ci&mask)>>offset;
               bitIndex-=bits;
            }
            else {
               lsbs=(((int)source[byteIndex])&0xff&((1<<(bitIndex+1))-1))<<(bits-bitIndex-1);
               bits-=bitIndex+1;
               byteIndex++;
               while(bits>=8) {
                  bits-=8;
                  lsbs|=(((int)source[byteIndex])&0xff)<<bits;
                  byteIndex++;
               }
               if(bits>0) {
                  int ci=((int)source[byteIndex])&0xff;
                  lsbs|=(ci>>(8-bits))&((1<<bits)-1);
                  bitIndex=7-bits;
               }
               else {
                  byteIndex--;
                  bitIndex=-1;
               }
            }

            int res=(msbs<<order)|lsbs;
            buffer[i]=(res&1)==1?-(res>>1)-1:(res>>1);
         }
      }
   }

   public void align() {
      if(endian==BIG_ENDIAN && bitIndex>=0) {
         bitIndex=7;
         byteIndex++;
      }
      else if(endian==LITTLE_ENDIAN && bitIndex<=7) {
         bitIndex=0;
         byteIndex++;
      }
   }

   public void setEndian(int endian) {
      if(this.endian==BIG_ENDIAN && endian==LITTLE_ENDIAN) {
         bitIndex=0;
         byteIndex++;
      }
      else if(this.endian==LITTLE_ENDIAN && endian==BIG_ENDIAN) {
         bitIndex=7;
         byteIndex++;
      }
      this.endian=endian;
   }

	/**
	 *  @return the byte array used as a source for this instance
	 */

   public byte[] getSource() {
      return source;
   }
}