package org.bucksch.zeipis.recorder;
import org.bucksch.zeipis.all.Util;
import java.nio.ByteBuffer;
import java.util.Vector;

public class BufferPart
{
  /**
   * @param buffer The data
   * @param startPos index in buffer which should appear as pos 0 in this BufferPart
   * @param length number of bytes to be considered part of this BufferPart
   */
  public BufferPart(byte[] buffer, int startPos, int length)
  {
    if (startPos < 0)
      throw new RuntimeException("Buffer start pos is negative");
    if (length < 0)
      throw new RuntimeException("Buffer length is negative");
    if (startPos + length > buffer.length)
      throw new RuntimeException("Buffer length beyond end of buffer");
    theBuffer = buffer;
    myStartAbsPos = startPos;
    myLength = length;
    myEndAbsPos = myStartAbsPos + myLength;
    myCurrentAbsPos = myStartAbsPos;
  }
  /**
   * Create a BufferPart that is a "substring" of another BufferPart
   * @param startPos relative to biggerPart
   */
  public BufferPart(BufferPart biggerPart, int startPos, int length)
  {
    if (startPos < 0)
      throw new RuntimeException("Buffer start pos is negative");
    if (length < 0)
      throw new RuntimeException("Buffer length is negative");
    theBuffer = biggerPart.theBuffer;
    myStartAbsPos = biggerPart.myStartAbsPos + startPos;
    myLength = length;
    myEndAbsPos = myStartAbsPos + myLength;
    myCurrentAbsPos = myStartAbsPos;
    if (myEndAbsPos > biggerPart.myEndAbsPos)
      throw new RuntimeException("Buffer length beyond end of buffer: part length " + length);
    assert (myEndAbsPos <= theBuffer.length); // should be covered by above check
  }
  /**
   * Create a copy of org, but with currentPos() reset.
   */
  public BufferPart(BufferPart org)
  {
    theBuffer = org.theBuffer;
    myStartAbsPos = org.myStartAbsPos;
    myLength = org.myLength;
    myEndAbsPos = org.myEndAbsPos;

    myCurrentAbsPos = myStartAbsPos;
  }
  protected int checkPos(int pos)
  {
    if (pos < 0)
      throw new RuntimeException("Requested buffer pos is negative");
    if (pos >= myEndAbsPos)
      throw new RuntimeException("Requested buffer pos is beyond the end of this buffer part");
    return pos;
  }
  protected int absPos(int relPos)
  {
    return checkPos(myStartAbsPos + relPos);
  }
  // does not check, assumes absolutes are OK
  protected int relPos(int absPos)
  {
    return absPos - myStartAbsPos;
  }

  public byte next()
  {
    return theBuffer[checkPos(myCurrentAbsPos++)];
  }
  public byte readInt8()
  {
    return next();
  }
  public int readInt16()
  {
    return convInt16(next(), next());
  }
  public int readInt12from16()
  {
    return convInt12from16(next(), next());
  }
  public int readInt13from16()
  {
    return convInt13from16(next(), next());
  }
  public int readInt32BDC()
  {
    return convInt32BDC(next(), next(), next(), next());
  }
  public String readString(int length)
  {
    String result = readString(relPos(myCurrentAbsPos), length);
    myCurrentAbsPos += length;
    return result;
  }
  public ByteBuffer readBytes(int length)
  {
    ByteBuffer result = readBytes(relPos(myCurrentAbsPos), length);
    myCurrentAbsPos += length;
    return result;
  }
  public byte readInt8(int pos)
  {
    return theBuffer[absPos(pos)];
  }
  public int readInt16(int startPos)
  {
    return convInt16(readInt8(startPos), readInt8(++startPos));
  }
  public int readInt12from16(int startPos)
  {
    return convInt12from16(readInt8(startPos), readInt8(++startPos));
  }
  public int readInt13from16(int startPos)
  {
    return convInt13from16(readInt8(startPos), readInt8(++startPos));
  }
  public int readInt32BDC(int startPos)
  {
    return convInt32BDC(readInt8(startPos), readInt8(++startPos), readInt8(++startPos), readInt8(++startPos));
  }
  public String readString(int startPos, int length)
  {
    if (length == 0)
      return "";
    if (length < 0)
      throw new RuntimeException("Negative length requested");
    startPos = absPos(startPos);
    int endPos = startPos + length;
    checkPos(endPos - 1);
    String result = "";
    while (startPos < endPos)
      result += (char) theBuffer[startPos++]; // TODO cast correct? charset?
    return result;
  }
  public ByteBuffer readBytes(int startPos, int length)
  {
    startPos = absPos(startPos);
    int endPos = startPos + length;
    checkPos(endPos - 1);
    return ByteBuffer.wrap(theBuffer, startPos, length).asReadOnlyBuffer();
  }
  static public int convInt16(int first, int second)
  {
    return (first & 0xFF) << 8 | (second & 0xFF);
  }
  static public int convInt12from16(int first, int second)
  {
    return (first & 0x0F) << 8 | (second & 0xFF);
  }
  static public int convInt13from16(int first, int second)
  {
    return (first & 0x1F) << 8 | (second & 0xFF);
  }
  static public int convInt32BDC(int first, int second, int third, int forth)
  {
    return
      (first >> 4 & 0x0F) * 10000000 + (first & 0x0F) * 1000000 +
      (second >> 4 & 0x0F) * 100000 + (second & 0x0F) * 10000 +
      (third >> 4 & 0x0F) * 1000 + (third & 0x0F) * 100 +
      (forth >> 4 & 0x0F) * 10 + (forth & 0x0F);
  }
  
  /**
   * Returns a "substring" of this buffer as new BufferPart.
   * The new BufferPart will have the length you spec here.
   * Does the same as the BufferPart(BufferPart, int, int) ctor.
   */
  public BufferPart subBuffer(int startPos, int length)
  {
    return new BufferPart(this, startPos, length);
  }

  /**
   * @see subBuffer(), just that is starts at currentPos()
   * and advanced it by |length| 
   */
  public BufferPart subBuffer(int length)
  {
    BufferPart result = subBuffer(relPos(myCurrentAbsPos), length);
    myCurrentAbsPos += length;
    return result;
  }

  /**
   * Treats the remainder of the buffer as a list of fixed-size entries,
   * and returns a BufferPart for each.
   *
   * @param startPos
   * @param chunkLength How long (in bytes) each BufferPart should be
   * @return One BufferPart per chunk
   */
  public Vector<BufferPart> split(int startPos, int chunkLength)
  {
    int remainingLength = myEndAbsPos - absPos(startPos);
    int count = remainingLength / chunkLength;
    if (count * chunkLength != remainingLength)
      throw new RuntimeException("buffer: list: length not correct"); // TODO just process as much as possible?
    Vector<BufferPart> result = Util.newVector();
    for (int i = 0; i < count; i++)
      result.add(subBuffer(startPos + i * chunkLength, chunkLength));
    return result;
  }

  /**
   * @see split(), just that it starts at currentPos()
   * and advances it to the end.
   */
  public Vector<BufferPart> split(int chunkLength)
  {
    Vector<BufferPart> result = split(relPos(myCurrentAbsPos), chunkLength);
    myCurrentAbsPos += myEndAbsPos;
    return result;
  }


  // Getters
  public int startPos() { return relPos(myStartAbsPos); }
  public int endPos() { return relPos(myEndAbsPos); }
  public int length() { return myLength; }
  public int currentPos() { return relPos(myCurrentAbsPos); }
  protected void dump(String prefix)
  {
    Util.debug(prefix + "part length: " + myLength);
    Util.debug(prefix + "start pos in buffer: " + myStartAbsPos);
    Util.debug(prefix + "end pos in buffer: " + myEndAbsPos);
    Util.debug(prefix + "cur pos in buffer: " + myCurrentAbsPos);
  }
  public String contentAsHexString()
  {
    String result = "";
    for (int i = 0; i < myLength; i++)
      result += "0x" + Integer.toHexString(readInt8(i)) + " ";
    return result;
  }
  public String contentAsBitString()
  {
    String result = "";
    for (int i = 0; i < myLength; i++)
    {
      byte b = readInt8(i);
      for (int i2 = 7; i2 >= 0; i2--)
      {
        int bit = b & 1 << i2;
        result += bit == 0 ? "0" : "1";
      }      
    }
    return result;
  }
  public void dumpContent()
  {
    Util.debug("Buffer contents, " + myLength + " bytes: " + readString(0, myLength) + "\nSame in hex: " + contentAsHexString());
  }


  // Data
  protected byte[] theBuffer;
  protected int myStartAbsPos;
  protected int myEndAbsPos;
  protected int myLength;
  protected int myCurrentAbsPos;
}
