/*
 *	The contents of this file are subject to the Netscape Public License 
 *	Version 1.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.mozilla.org/NPL/. 
 *	
 *	Software distributed under the License is distributed on an "AS IS" basis, 
 *	WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
 *	for the specific language governing rights and limitations under the License. 
 *	
 *	The Original Code is the Netscape Messaging Access SDK Version 3.5 code, 
 *	released on or about June 15, 1998. 
 *	
 *	The Initial Developer of the Original Code is Netscape Communications 
 *	Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape 
 *	Communications Corporation. All Rights Reserved. 
 *	
 *	Contributor(s): ______________________________________. 
*/
  
/*
* Copyright (c) 1997 and 1998 Netscape Communications Corporation
* (http://home.netscape.com/misc/trademarks.html)
*/

package netscape.messaging.mime;

import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;

/**
 * Mutable subclass of ByteString.
 */
public final class ByteBuffer extends ByteString implements Cloneable {
    boolean wasCloned;

    /**
     * Construct a ByteBuffer of the given capacity
     */
    public ByteBuffer (int capacity) {
        super();
        buffer  = new byte[capacity];
	wasCloned = false;
    }

    /**
     * Construct a ByteBuffer of the given capacity
     */
    public ByteBuffer (ByteString bs) {
        super();
        buffer  = bs.buffer;
        hi      = bs.hi;
        lo      = bs.lo;
	wasCloned = false;
    }

    /**
     * Construct a ByteBuffer of the given capacity
     * no clone version
     */
    public ByteBuffer (byte[] b)
    {
        super();
        buffer  = b;
        hi      = b.length;
        lo      = 0;
    	wasCloned = false;
    }

    /**
     * Construct a ByteBuffer of the given capacity
     * no clone version
     */
    public ByteBuffer (byte[] b, int len)
    {
        super();
        buffer  = b;
        hi      = len;
        lo      = 0;
    	wasCloned = false;
    }

    /**/

    /**
     * Ensure there is capacity for appending num bytes.
     */
    private final void ensureCapacity (int num) {
        byte[] newBuffer = null;
	int newsize;

	if (hi == lo) erase();
	if (buffer.length - hi >= num) return;

	newsize = num + (hi-lo);
	if (newsize*2 < buffer.length) {
	    newsize = buffer.length;
	    if (!wasCloned) newBuffer = buffer;
	}
	else if (newsize < buffer.length*2) newsize = buffer.length*2;

	if (newBuffer == null) 
	    newBuffer = new byte[newsize];

    System.arraycopy(buffer, lo, newBuffer, 0, hi-lo );

    buffer = newBuffer;
	wasCloned = false;
	hi -= lo;
	lo = 0;
    }

    /**
     * Append a ByteString. Not MT-safe.
     */
    public final void append (ByteString that) {
	ensureCapacity(that.size());
        System.arraycopy(that.buffer, that.lo, this.buffer, this.hi, that.size());
        this.hi += that.size();
    }

    /**
     * Append a substring of a ByteString. Not MT-safe.
     */
    public final void append (ByteString that, int start) {
	int substrSize = that.size() - start;

	if (substrSize < 0) throw new IndexOutOfBoundsException();
	ensureCapacity(substrSize);
        System.arraycopy(that.buffer, that.lo+start, this.buffer, this.hi,
			 substrSize);
        this.hi += substrSize;
    }

    /**
     * Append a substring of a ByteString. Not MT-safe.
     */
    public final void append (ByteString that, int start, int finish) {
	if (start > finish || finish > that.size())
	    throw new IndexOutOfBoundsException();
	ensureCapacity(finish - start);
        System.arraycopy(that.buffer, that.lo+start, this.buffer, this.hi, finish-start);
        this.hi += (finish-start);
    }

    /**
     * Append a byte[]. Not MT-safe.
     */
    public final void append (byte[] that) {
	ensureCapacity(that.length);
        System.arraycopy(that, 0, this.buffer, this.hi, that.length);
        this.hi += that.length;
    }

    /**
     * Append a byte[]. Not MT-safe.
     */
    public final void append (byte[] that, int len ) 
    {
	    ensureCapacity(len);
        System.arraycopy(that, 0, this.buffer, this.hi, len);
        this.hi += len;
    }

    /**
     * Add a long
     */
    public final void append (long n) {
	ensureCapacity(8);
        for (int i=hi+8; --i>=hi; ) {
            buffer[i] = (byte)(n & 0xff);
            n >>= 8;
        }
        hi += 8;
    }

    /**
     * Add an int
     */
    public final void append (int n) {
	ensureCapacity(4);
        for (int i=hi+4; --i>=hi; ) {
            buffer[i] = (byte)(n & 0xff);
            n >>= 8;
        }
        hi += 4;
    }

    /**
     * Add a byte
     */
    public final void append (byte n) {
	ensureCapacity(1);
	buffer[hi] = n;
	hi++;
    }

    /**
     * Erase the contents of the ByteBuffering. Not MT safe.
     * Will not shrink capacity.
     */
    public final void erase () {
	if (!wasCloned) hi = 0;
        lo = hi;
    }

    /**
     * Defragment the contents of buffer. Not MT safe.
     */
    public final void cleanup () {
        if (wasCloned || lo+lo <= buffer.length)
            return;
        System.arraycopy(buffer, lo, buffer, 0, hi-lo);
        hi -= lo;
        lo = 0;
    }

    /**
     * Return substring from offset start (inclusive) till offset
     * finish (exclusive).
     */
    public ByteString substring (int start, int finish) {
        ByteString result = super.substring(start, finish);
	wasCloned = true;
        return result;
    }

    /**
     * Return substring to the right of the given offset (inclusive).
     */
    public ByteString substring (int start) {
        ByteString result = super.substring(start);
	wasCloned = true;
        return result;
    }

    /**
     * Add a line. Not MT-safe.
     */
    public final void addLine (ByteString line) {
        append(line);
        append(CRLF);
    }

    /**
     * Add a line. Not MT-safe.
     */
    public final void addLine (byte[] line) {
        append(line);
        append(CRLF);
    }

    /**
     * Read a line read (and maybe more) from a stream. Not MT-safe.
     * Blocks until one line is read.  May read multiple lines.
     */
    public final void readLine (InputStream stream)
        throws IOException {
	    while (noLines()) {
		bulkRead(stream);
	    }
    }

    /**
     * Bulk read from a stream. Not MT-safe.
     * Returns the number of bytes read.
     */
    public final int bulkRead (InputStream stream)
        throws IOException {

        int n = -1;
	ensureCapacity(1024);	// TODO: is this a good number?
        for (int attempt = 0; attempt < 10; attempt++) {
            n = stream.read(buffer, hi, buffer.length-hi);
            if (n > 0) {
                hi += n;
                return n;
            }
            else if (n == 0) {
                System.err.println("BufferedInputStream.read(byte[], int, int) returns 0 on attempt #" + attempt);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    System.err.println(e + " while waiting in bulkRead");
                }
            }
            else {
                throw new IOException("Bulk read returns " + n + " bytes");
            }
        }
        throw new IOException("Bulk read returns " + n + " bytes");
    }

    /**
     * Clones an instance of the ByteBuffer object.
     * @exception CloneNotSupportedException could be thrown by constituent components.
     */
    public Object clone () throws CloneNotSupportedException
    {
         ByteBuffer l_theClone = (ByteBuffer) super.clone();
 
         return (l_theClone);
    }
    
    public InputStream getInputStream()
    {
        return new ByteArrayInputStream( buffer, lo, hi );
    }

    public void setSize( int len ) { ensureCapacity( len ); }
}
