package kr.util;
import javax.print.attribute.Size2DSyntax;
 
public final class ByteBuffer implements java.io.Serializable {
    /**
     * The value is used for byte storage.
     * 
     * @serial
     */
    private byte value[];

    /** 
     * The count is the number of bytes in the buffer.
     * 
     * @serial
     */
    private int count;

    /**
     * A flag indicating whether the buffer is shared 
     *
     * @serial
     */
    private boolean shared;

	private float multiplierAmount = 2;

    /**
     * Constructs a byte buffer with no bytes in it and an 
     * initial capacity of 16 bytes. 
     */
    public ByteBuffer() {
			this(16);
    }

    /**
     * Constructs a byte buffer with no bytes in it and an 
     * initial capacity specified by the <code>length</code> argument. 
     *
     * @param      length   the initial capacity.
     * @exception  NegativeArraySizeException  if the <code>length</code>
     *               argument is less than <code>0</code>.
     */
    public ByteBuffer(int length) {
	value = new byte[length];
	shared = false;
    }

    /**
     * @param multiplerAmount the percentage amount to increase the size of
     * the buffer if it overflows
     */
    public ByteBuffer(int length, double multiplierAmount) {
    	value = new byte[length];
    	this.multiplierAmount = (float)multiplierAmount;
    	shared = false;
        }

    /**
     * Constructs a byte buffer from an array of bytes. This uses the actual
	 * array, so don't change it.
     */
    public ByteBuffer(byte [] bytes) {
	value = bytes;
	count = bytes.length;
	shared = false;
    }

    /**
     * Returns the length (byte count) of this byte buffer.
     *
     * @return  the length of the sequence of bytes currently 
     *          represented by this byte buffer.
     */
    public int length() {
	return count;
    }

    /**
     * Returns the current capacity of the Byte buffer. The capacity
     * is the amount of storage available for newly inserted
     * bytes; beyond which an allocation will occur.
     *
     * @return  the current capacity of this byte buffer.
     */
    public int capacity() {
	return value.length;
    }

    /**
     * Copies the buffer value.  This is normally only called when shared
     * is true.  It should only be called from a synchronized method.
     */
    private final void copy() {
	byte newValue[] = new byte[value.length];
	System.arraycopy(value, 0, newValue, 0, count);
	value = newValue;
	shared = false;
    }

    /**
     * Ensures that the capacity of the buffer is at least equal to the
     * specified minimum.
     * If the current capacity of this byte buffer is less than the 
     * argument, then a new internal buffer is allocated with greater 
     * capacity. The new capacity is the larger of: 
     * <ul>
     * <li>The <code>minimumCapacity</code> argument. 
     * <li>Twice the old capacity, plus <code>2</code>. 
     * </ul>
     * If the <code>minimumCapacity</code> argument is nonpositive, this
     * method takes no action and simply returns.
     *
     * @param   minimumCapacity   the minimum desired capacity.
     */
    public synchronized void ensureCapacity(int minimumCapacity) {
	if (minimumCapacity > value.length) {
	    expandCapacity(minimumCapacity);
	}
    }

    /**
     * This implements the expansion semantics of ensureCapacity but is
     * unsynchronized for use internally by methods which are already
     * synchronized.
     *
     * @see java.lang.ByteBuffer#ensureCapacity(int)
     */
    private void expandCapacity(int minimumCapacity) {
	int newCapacity = (int) ((value.length + 1) * multiplierAmount);
	if (minimumCapacity > newCapacity) {
	    newCapacity = minimumCapacity;
	}

	System.out.println("expanding capacity to "+newCapacity);
	
	byte newValue[] = new byte[newCapacity];
	System.arraycopy(value, 0, newValue, 0, count);
	value = newValue;
	shared = false;
    }

    /**
     * Sets the length of this Byte buffer.
     * This byte buffer is altered to represent a new byte sequence 
     * whose length is specified by the argument. For every nonnegative 
     * index <i>k</i> less than <code>newLength</code>, the byte at 
     * index <i>k</i> in the new byte sequence is the same as the 
     * byte at index <i>k</i> in the old sequence if <i>k</i> is less 
     * than the length of the old byte sequence; otherwise, it is the 
     * null byte <code>'\u0000'</code>. 
     *  
     * In other words, if the <code>newLength</code> argument is less than 
     * the current length of the byte buffer, the byte buffer is 
     * truncated to contain exactly the number of bytes given by the 
     * <code>newLength</code> argument. 
     * <p>
     * If the <code>newLength</code> argument is greater than or equal 
     * to the current length, sufficient null bytes 
     * (<code>'&#92;u0000'</code>) are appended to the byte buffer so that 
     * length becomes the <code>newLength</code> argument. 
     * <p>
     * The <code>newLength</code> argument must be greater than or equal 
     * to <code>0</code>. 
     *
     * @param      newLength   the new length of the buffer.
     * @exception  IndexOutOfBoundsException  if the
     *               <code>newLength</code> argument is negative.
     * @see        java.lang.ByteBuffer#length()
     */
    public synchronized void setLength(int newLength) {
	if (newLength < 0) {
	    throw new IndexOutOfBoundsException(String.valueOf(newLength));
	}
	
	if (newLength > value.length) {
	    expandCapacity(newLength);
	}

	if (count < newLength) {
	    if (shared) copy();
	    for (; count < newLength; count++) {
		value[count] = '\0';
	    }
	} else {
            count = newLength;
            if (shared) copy();
        }
    }

    /**
     * The specified byte of the sequence currently represented by 
     * the byte buffer, as indicated by the <code>index</code> argument, 
     * is returned. The first byte of a byte buffer is at index 
     * <code>0</code>, the next at index <code>1</code>, and so on, for 
     * array indexing. 
     * <p>
     * The index argument must be greater than or equal to 
     * <code>0</code>, and less than the length of this byte buffer. 
     *
     * @param      index   the index of the desired byte.
     * @return     the byte at the specified index of this byte buffer.
     * @exception  IndexOutOfBoundsException  if <code>index</code> is 
     *             negative or greater than or equal to <code>length()</code>.
     * @see        java.lang.ByteBuffer#length()
     */
    public synchronized byte byteAt(int index) {
	if ((index < 0) || (index >= count)) {
	    throw new IndexOutOfBoundsException(String.valueOf(index));
	}
	return value[index];
    }

    /**
     * Bytes are copied from this byte buffer into the 
     * destination byte array <code>dst</code>. The first byte to 
     * be copied is at index <code>srcBegin</code>; the last byte to 
     * be copied is at index <code>srcEnd-1</code>. The total number of 
     * bytes to be copied is <code>srcEnd-srcBegin</code>. The 
     * bytes are copied into the subarray of <code>dst</code> starting 
     * at index <code>dstBegin</code> and ending at index:
     * <p><blockquote><pre>
     * dstbegin + (srcEnd-srcBegin) - 1
     * </pre></blockquote>
     *
     * @param      srcBegin   start copying at this offset in the byte buffer.
     * @param      srcEnd     stop copying at this offset in the byte buffer.
     * @param      dst        the array to copy the data into.
     * @param      dstBegin   offset into <code>dst</code>.
     * @exception  NullPointerException if <code>dst</code> is 
     *             <code>null</code>.
     * @exception  IndexOutOfBoundsException  if any of the following is true:
     *             <ul><li><code>srcBegin</code> is negative
     *             <li>the <code>srcBeing</code> argument is greater than 
     *             the <code>srcEnd</code> argument.
     *             <li><code>srcEnd</code> is greater than 
     *             <code>this.length()</code>, the current length of this 
     *             byte buffer.
     *             <li><code>dstBegin+srcEnd-srcBegin</code> is greater than 
     *             <code>dst.length</code></ul>
     */
    public synchronized void getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin) {
	if ((srcBegin < 0) || (srcBegin >= count)) {
	    throw new IndexOutOfBoundsException(String.valueOf(srcBegin));
	}
	if ((srcEnd < 0) || (srcEnd > count)) {
	    throw new IndexOutOfBoundsException(String.valueOf(srcEnd));
	}
	if (srcBegin < srcEnd) {
	    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
	} else {
	    if (srcBegin > srcEnd) {
			throw new IndexOutOfBoundsException("begin greater than end");
	    }
	    /* We do nothing when srcBegin == srcEnd. */
	}
    }

    /**
     * The byte at the specified index of this byte buffer is set 
     * to <code>ch</code>. The byte buffer is altered to represent a new 
     * byte sequence that is identical to the old byte sequence, 
     * except that it contains the byte <code>ch</code> at position 
     * <code>index</code>. 
     * <p>
     * The offset argument must be greater than or equal to 
     * <code>0</code>, and less than the length of this byte buffer. 
     *
     * @param      index   the index of the byte to modify.
     * @param      ch      the new byte.
     * @exception  IndexOutOfBoundsException  if <code>index</code> is 
     *             negative or greater than or equal to <code>length()</code>.
     * @see        java.lang.ByteBuffer#length()
     */
    public synchronized void setByteAt(int index, byte ch) {
	if ((index < 0) || (index >= count)) {
	    throw new IndexOutOfBoundsException(String.valueOf(index));
	}
	if (shared) copy();
	value[index] = ch;
    }

    /**
     * Appends the byte representation of the <code>byte</code> array 
     * argument to this byte buffer. 
     * <p>
     * The bytes of the array argument are appended, in order, to 
     * the contents of this byte buffer. The length of this byte 
     * buffer increases by the length of the argument. 
     * <p>
     * The overall effect is exactly as if the argument were converted to 
     * a byte by the method {@link Byte#valueOf(byte[])} and the 
     * bytes of that byte were then {@link #append(Byte) appended} 
     * to this <code>ByteBuffer</code> object.
     *
     * @param   str   the bytes to be appended.
     * @return  a reference to this <code>ByteBuffer</code> object.
     */
    public synchronized ByteBuffer append(byte str[]) {
	int len = str.length;
	int newcount = count + len;
	if (newcount > value.length)
	    expandCapacity(newcount);
	System.arraycopy(str, 0, value, count, len);
	count = newcount;
	return this;
    }

    /**
     * Appends the byte representation of a subarray of the 
     * <code>byte</code> array argument to this byte buffer. 
     * <p>
     * Bytes of the byte array <code>str</code>, starting at 
     * index <code>offset</code>, are appended, in order, to the contents 
     * of this byte buffer. The length of this byte buffer increases 
     * by the value of <code>len</code>. 
     * <p>
     * The overall effect is exactly as if the arguments were converted to 
     * a byte by the method {@link Byte#valueOf(byte[],int,int)} and the
     * bytes of that byte were then {@link #append(Byte) appended} 
     * to this <code>ByteBuffer</code> object.
     *
     * @param   str      the bytes to be appended.
     * @param   offset   the index of the first byte to append.
     * @param   len      the number of bytes to append.
     * @return  a reference to this <code>ByteBuffer</code> object.
     */
    public synchronized ByteBuffer append(byte str[], int offset, int len) {
        int newcount = count + len;
	if (newcount > value.length)
	    expandCapacity(newcount);
	System.arraycopy(str, offset, value, count, len);
	count = newcount;
	return this;
    }

    /**
     * Appends the byte representation of the <code>byte</code> 
     * argument to this byte buffer. 
     * <p>
     * The argument is appended to the contents of this byte buffer. 
     * The length of this byte buffer increases by <code>1</code>. 
     * <p>
     * The overall effect is exactly as if the argument were converted to 
     * a byte by the method {@link Byte#valueOf(byte)} and the byte 
     * in that byte were then {@link #append(Byte) appended} to this 
     * <code>ByteBuffer</code> object.
     *
     * @param   ch   a <code>byte</code>.
     * @return  a reference to this <code>ByteBuffer</code> object.
     */
    public synchronized ByteBuffer append(byte c) {
        int newcount = count + 1;
	if (newcount > value.length)
	    expandCapacity(newcount);
	value[count++] = c;
	return this;
    }

    /**
     * Removes the bytes in a subbyte of this <code>ByteBuffer</code>.
     * The subbyte begins at the specified <code>start</code> and extends to
     * the byte at index <code>end - 1</code> or to the end of the
     * <code>ByteBuffer</code> if no such byte exists. If
     * <code>start</code> is equal to <code>end</code>, no changes are made.
     *
     * @param      start  The beginning index, inclusive.
     * @param      end    The ending index, exclusive.
     * @return     This byte buffer.
     * @exception  ByteIndexOutOfBoundsException  if <code>start</code>
     *             is negative, greater than <code>length()</code>, or
     *		   greater than <code>end</code>.
     * @since      JDK1.2
     */
    public synchronized ByteBuffer delete(int start, int end) {
	if (start < 0)
	    throw new IndexOutOfBoundsException(String.valueOf(start));
	if (end > count)
	    end = count;
	if (start > end)
	    throw new IndexOutOfBoundsException();

        int len = end - start;
        if (len > 0) {
            if (shared)
                copy();
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return this;
    }

    /**
     * Removes the byte at the specified position in this
     * <code>ByteBuffer</code> (shortening the <code>ByteBuffer</code>
     * by one byte).
     *
     * @param       index  Index of byte to remove
     * @return      This byte buffer.
     * @exception   ByteIndexOutOfBoundsException  if the <code>index</code>
     *		    is negative or greater than or equal to
     *		    <code>length()</code>.
     * @since       JDK1.2
     */
    public synchronized ByteBuffer deleteByteAt(int index) {
        if ((index < 0) || (index >= count))
	    throw new IndexOutOfBoundsException();
	if (shared)
	    copy();
	System.arraycopy(value, index+1, value, index, count-index-1);
	count--;
        return this;
    }

    /**
     * Inserts the byte representation of a subarray of the <code>str</code>
     * array argument into this byte buffer. The subarray begins at the
     * specified <code>offset</code> and extends <code>len</code> bytes.
     * The bytes of the subarray are inserted into this byte buffer at
     * the position indicated by <code>index</code>. The length of this
     * <code>ByteBuffer</code> increases by <code>len</code> bytes.
     *
     * @param      index    position at which to insert subarray.
     * @param      str       A byte array.
     * @param      offset   the index of the first byte in subarray to
     *		   to be inserted.
     * @param      len      the number of bytes in the subarray to
     *		   to be inserted.
     * @return     This byte buffer.
     * @exception  ByteIndexOutOfBoundsException  if <code>index</code>
     *             is negative or greater than <code>length()</code>, or
     *		   <code>offset</code> or <code>len</code> are negative, or
     *		   <code>(offset+len)</code> is greater than
     *		   <code>str.length</code>.
     * @since JDK1.2
     */
    public synchronized ByteBuffer insert(int index, byte str[], int offset,
                                                                   int len) {
        if ((index < 0) || (index > count))
	    throw new IndexOutOfBoundsException();
	if ((offset < 0) || (offset + len > str.length))
	    throw new IndexOutOfBoundsException(String.valueOf(offset));
	if (len < 0)
	    throw new IndexOutOfBoundsException(String.valueOf(len));
	int newCount = count + len;
	if (newCount > value.length)
	    expandCapacity(newCount);
	else if (shared)
	    copy();
	System.arraycopy(value, index, value, index + len, count - index);
	System.arraycopy(str, offset, value, index, len);
	count = newCount;
	return this;
    }

    /**
     * Inserts the byte representation of the <code>byte</code> array 
     * argument into this byte buffer. 
     * <p>
     * The bytes of the array argument are inserted into the 
     * contents of this byte buffer at the position indicated by 
     * <code>offset</code>. The length of this byte buffer increases by 
     * the length of the argument. 
     * <p>
     * The overall effect is exactly as if the argument were converted to 
     * a byte by the method {@link Byte#valueOf(byte[])} and the 
     * bytes of that byte were then 
     * {@link #insert(int,Byte) inserted} into this 
     * <code>ByteBuffer</code>  object at the position indicated by
     * <code>offset</code>.
     *
     * @param      offset   the offset.
     * @param      ch       a byte array.
     * @return     a reference to this <code>ByteBuffer</code> object.
     * @exception  ByteIndexOutOfBoundsException  if the offset is invalid.
     */
    public synchronized ByteBuffer insert(int offset, byte str[]) {
	if ((offset < 0) || (offset > count)) {
	    throw new IndexOutOfBoundsException();
	}
	int len = str.length;
	int newcount = count + len;
	if (newcount > value.length)
	    expandCapacity(newcount);
	else if (shared)
	    copy();
	System.arraycopy(value, offset, value, offset + len, count - offset);
	System.arraycopy(str, 0, value, offset, len);
	count = newcount;
	return this;
    }

   /**
     * Inserts the byte representation of the <code>byte</code> 
     * argument into this byte buffer. 
     * <p>
     * The second argument is inserted into the contents of this byte 
     * buffer at the position indicated by <code>offset</code>. The length 
     * of this byte buffer increases by one. 
     * <p>
     * The overall effect is exactly as if the argument were converted to 
     * a byte by the method {@link Byte#valueOf(byte)} and the byte 
     * in that byte were then {@link #insert(int, Byte) inserted} into 
     * this <code>ByteBuffer</code> object at the position indicated by
     * <code>offset</code>.
     * <p>
     * The offset argument must be greater than or equal to 
     * <code>0</code>, and less than or equal to the length of this 
     * byte buffer. 
     *
     * @param      offset   the offset.
     * @param      ch       a <code>byte</code>.
     * @return     a reference to this <code>ByteBuffer</code> object.
     * @exception  ByteIndexOutOfBoundsException  if the offset is invalid.
     * @see        java.lang.ByteBuffer#length()
     */
    public synchronized ByteBuffer insert(int offset, byte c) {
	int newcount = count + 1;
	if (newcount > value.length)
	    expandCapacity(newcount);
	else if (shared)
	    copy();
	System.arraycopy(value, offset, value, offset + 1, count - offset);
	value[offset] = c;
	count = newcount;
	return this;
    }

    /**
     * The byte sequence contained in this byte buffer is 
     * replaced by the reverse of the sequence. 
     * <p>
     * Let <i>n</i> be the length of the old byte sequence, the one 
     * contained in the byte buffer just prior to execution of the 
     * <code>reverse</code> method. Then the byte at index <i>k</i> in 
     * the new byte sequence is equal to the byte at index 
     * <i>n-k-1</i> in the old byte sequence.
     *
     * @return  a reference to this <codeByteBuffer</code> object..
     * @since   JDK1.0.2
     */
    public synchronized ByteBuffer reverse() {
	if (shared) copy();
	int n = count - 1;
	for (int j = (n-1) >> 1; j >= 0; --j) {
	    byte temp = value[j];
	    value[j] = value[n - j];
	    value[n - j] = temp;
	}
	return this;
    }

    //
    // The following two methods are needed by Byte to efficiently
    // convert a ByteBuffer into a Byte.  They are not public.
    // They shouldn't be called by anyone but Byte.
    final void setShared() { shared = true; } 
    final byte[] getValue() { return value; }

    /**
     * readObject is called to restore the state of the ByteBuffer from
     * a stream.
     */
    private void readObject(java.io.ObjectInputStream s)
         throws java.io.IOException, ClassNotFoundException {
	s.defaultReadObject();
	value = (byte[]) value.clone();
	shared = false;
    }

	public void copyTo(int offset, int length, byte[] dest, int destOffset) {
		if(offset + length > length()) throw new IllegalStateException("Array out of bounds");
		System.arraycopy(value, offset, dest, destOffset, length);
	}

	public void copyFrom(int offset, int length, byte[] src, int srcOffset) {
		if(offset + length > length()) throw new IllegalStateException("Array out of bounds");
		System.arraycopy(src, srcOffset, value, offset, length);		
	}

	public byte[] toByteArray() {
		byte [] dest = new byte[count];
		copyTo(0,count, dest, 0);
		return dest;
	}
	
	public byte [] getInternalArray()
	{
		return value;
	}
}
