/*
 * ByteBuffer.java - This file is part of the Jakstab project.
 * 
 * Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
 * Copyright (C) 2003 The University of Arizona
 *
 * The original code for this class was taken from "MBEL: The Microsoft 
 * Bytecode Engineering Library" and modified for use with Jakstab.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
 */

package org.jakstab.loader.pe;

/** This class is used for temporary storage and manipulation of a stream
  * of bytes. All of the signature classes parse themselves out of ByteBuffers,
  * and the emitter makes large use of them for storing intermediate chunks of 
  * the final module file. This class is vaguely modelled after java.nio.ByteBuffer,
  * but it has some extra functionality that makes it specifically useful to MBEL.
  * The concept behind a ByteBuffer is that it is an infinitely large 0-based array 
  * of bytes. There is a current byte index pointer given by getPosition(). The user can 
  * also set the current position using setPosition(int). Setting the position to a 
  * very high value will not grow the underlying array, but writing to it will (i.e. 
  * space is not allocated until needed). Both reading and writing advance the current
  * position pointer by the specified amount. Since one main use of the ByteBuffer
  * is as a convenient way to write an array of bytes, the ByteBuffer maintains the 
  * index of the greatest position that has been written to. Everything up to this point is 
  * returned when you call toByteArray(). 
  * @author Michael Stepp
  */
public class ByteBuffer{
   private static final int SIZE = 1000;
   private byte[][] buffer;
   private int position;
   private int last;

   /** Makes a ByteBuffer that is initially intended to be read from, not written to.
     * This constructor sets the current position to 0.
     * @param init the initial bytes to put into the array, starting from index 0 (if null, and empty ByteBuffer is created)
     */
   public ByteBuffer(byte[] init){
      if (init==null || init.length==0){
         buffer = new byte[1][SIZE];
         last = -1;
      }else{
         buffer = new byte[init.length/SIZE+1][SIZE];
         for (int i=0;i<init.length;i++)
            buffer[i/SIZE][i%SIZE] = init[i];
         last = init.length-1;
      }
      position = 0;
   }

   /** Makes a ByteBuffer that is initially intended for writing.
     * The current position pointer is set to 0.
     * @param capacity the minimum capacity of this ByteBuffer (for convenience)
     */
   public ByteBuffer(int capacity){
      buffer = new byte[capacity/SIZE+1][SIZE];
      position = 0;
      last = -1;
   }

   /** Returns the current maximum capacity of the underlying array.
     * This value is not an absolute maximum, because ByteBuffers grow as needed.
     */
   public int getCapacity(){
      return (buffer.length*SIZE);
   }

   /** Returns the index of the current position in the ByteBuffer.
     * This value will be >=0.
     */
   public int getPosition(){
      return position;
   }
   
   /** Returns the index of the last position written to (initially -1).
     */
   public int getLast(){
      return last;
   }

   /** Returns the byte stored in this ByteBuffer at the current position.
     * Advances the current position by 1.
     * (note: if the current position is past the end of any user-written data, this will return 0)
     */
   public byte get(){
      if (position>=getCapacity()){
         position++;
         return 0;
      }
      byte value = buffer[position/SIZE][position%SIZE];
      position++;
      return value;
   }

   /** Returns a 2-byte unsigned little-endian integer, starting at the current position.
     * Advances the current position by 2 (see get())
     */
   public int getWORD(){
      int value = (get()&0xFF);
      value |= (get()&0xFF)<<8;
      value &= 0xFFFF;
      return value;
   }
   
   /** Returns a 4-byte unsigned little-endian integer, starting at the current position.
     * Advances the current position by 4 (see get())
     */
   public long getDWORD(){
      long value = (get()&0xFFL);
      value |= (get()&0xFFL)<<8;
      value |= (get()&0xFFL)<<16;
      value |= (get()&0xFFL)<<24;
      value &= 0xFFFFFFFFL;
      return value;
   }

   /** Returns the byte stored in this ByteBuffer at the current position.
     * This method does not advance the current position.
     * (note: if the current position is past the end of any user-written data, this will return 0)
     */
   public byte peek(){
      if (position>=getCapacity())
         return 0;
      return buffer[position/SIZE][position%SIZE];
   }

   /** Sets the current position of this ByteBuffer.
     * @param pos the new position (if <0, ignored)
     */
   public void setPosition(int pos){
      if (pos>=0)
         position = pos;
   }

   /** Decrements the current position by 1 (if >0).
     */
   public void back(){
      // moves the position back by one
      if (position>0)
         position--;
   }
   
   /** Returns the contents of this ByteBuffer as a single contiguous byte array.
     * This returns everything from byte 0 to byte getLast(), inclusive.
     */
   public byte[] toByteArray(){
      // gets everything up to 'last'
      byte[] newarr = new byte[last+1];
      for (int i=0;i<newarr.length;i++)
         newarr[i] = buffer[i/SIZE][i%SIZE];
      return newarr;
   }
   
   /** Pads this ByteBuffer with 0s up to the next multiple of 'align'.
     * @param align the value to align to (if <=0, ignored)
     */
   public void pad(int align){
      if (align<=0) 
         return;
      while((position%align)!=0)
         put(0);
   }
   
   /** Writes the lowest 8 bits of the given int to this ByteBuffer, at the current position.
     * Advances the current position by 1.
     */
   public void put(int b){
      put((byte)(b&0xFF));
   }

   /** Writes a single byte to this ByteBuffer, at the current position.
     * Advances the current position by 1.
     */
   public void put(byte data){
      if (position>=getCapacity()){
         byte[][] newbuf = new byte[position/SIZE+2][];
         for (int i=0;i<buffer.length;i++)
            newbuf[i] = buffer[i];
         for (int i=buffer.length;i<newbuf.length;i++)
            newbuf[i] = new byte[SIZE];
         buffer = newbuf;
      }

      buffer[position/SIZE][position%SIZE] = data;
      last = Math.max(last,(position++));
   }

   /** Writes the given byte array to this ByteBuffer, starting at the current position.
     * Advances the current position by data.length.
     * @param data the bytes to write in this buffer (if null, writes nothing)
     */
   public void put(byte[] data){
      if (data==null)
         return;
      if (data.length+position >= getCapacity()){
         byte[][] newbuf = new byte[(data.length+position)/SIZE+2][];
         for (int i=0;i<buffer.length;i++)
            newbuf[i] = buffer[i];
         for (int i=buffer.length;i<newbuf.length;i++)
            newbuf[i] = new byte[SIZE];
         buffer = newbuf;
      }

      for (int i=0;i<data.length;i++){
         buffer[position/SIZE][position%SIZE] = data[i];
         last = Math.max(last,(position++));
      }
   }
   
   /** Writes a signed little-endian int16 to this ByteBuffer, starting at the current position.
     * Advances the current position by 2.
     */
   public void putINT16(int int16){
      put((byte)(int16&0xFF));
      put((byte)((int16>>8)&0xFF));
   }
   
   /** Writes an unsigned little-endian int16 to this ByteBuffer, starting at the current position.
     * Advances the current position by 2.
     */
   public void putWORD(int uint16){
      put((byte)(uint16&0xFF));
      put((byte)((uint16>>8)&0xFF));
   }
   
   /** Writes a signed little-endian int32 to this ByteBuffer, starting at the current position.
     * Advances the current position by 4.
     */
   public void putINT32(int int32){
      put((byte)(int32&0xFF));
      put((byte)((int32>>8)&0xFF));
      put((byte)((int32>>16)&0xFF));
      put((byte)((int32>>24)&0xFF));
   }

   /** Writes an unsigned little-endian int32 to this ByteBuffer, starting at the current position.
     * Advances the current position by 4.
     */
   public void putDWORD(long uint32){
      put((byte)(uint32&0xFF));
      put((byte)((uint32>>8)&0xFF));
      put((byte)((uint32>>16)&0xFF));
      put((byte)((uint32>>24)&0xFF));
   }
   
   /** Writes metadata token encoded in a long to this ByteBuffer, starting at the current position.
     * Advances the current position by 4.
     */
   public void putTOKEN(long token){
      putDWORD(token);
   }
   
   /** Writes a signed little-endian int64 to this ByteBuffer, starting at the current position.
     * Advances the current position by 8.
     */
   public void putINT64(long int64){
      put((byte)(int64&0xFF));
      put((byte)((int64>>8)&0xFF));
      put((byte)((int64>>16)&0xFF));
      put((byte)((int64>>24)&0xFF));
      put((byte)((int64>>32)&0xFF));
      put((byte)((int64>>40)&0xFF));
      put((byte)((int64>>48)&0xFF));
      put((byte)((int64>>56)&0xFF));
   }

   /** Writes a float32 to this ByteBuffer, starting at the current position.
     * Advances the current position by 4.
     */
   public void putR4(float r4){
      int bits = Float.floatToIntBits(r4);
      putINT32(bits);
   }
   
   /** Writes a float64 to this ByteBuffer, starting at the current position.
     * Advances the current position by 8.
     */
   public void putR8(double r8){
      long bits = Double.doubleToLongBits(r8);
      putINT64(bits);
   }
   
   /** Concatenates the given ByteBuffer to the end of this ByteBuffer, 
     * starting from 0 and ending at buf.getLast(), inclusive.
     * @param buf the ByteBuffer to concatenate to the end of this one (if null, ignored)
     */
   public void concat(ByteBuffer buf){
      if (buf==null)
         return;
      for (int i=0;i<=buf.last;i++){
         put(buf.buffer[i/SIZE][i%SIZE]);
      }
   }
}
