package org.maachang.comet.net;

import java.io.IOException;
import java.io.OutputStream;

/**
 * Chunked用OutputStream.
 * 
 * @version 2007/08/26
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
class ChunkedOutputStream extends OutputStream {
    private static final int DEFAULT_BUF = 32768 ;
    private static final int DEFAULT_MIN = 512 ;
    private static final int DEFAULT_MAX = 65535 ;
    private static final String CHARSET = "US-ASCII" ;
    private static final byte ENTER[] = new byte[] {(byte) 13, (byte) 10};
    private static final byte ENDCHUNK[] = ENTER;
    private static final byte ZERO[] = new byte[] {(byte) '0'};
    private static final String ENTER_STR = "\r\n" ;
    private OutputStream stream = null;
    private byte[] cache;
    private int cachePosition = 0;
    private boolean wroteLastChunk = false;
    
    public ChunkedOutputStream(OutputStream stream, int bufferSize) throws IOException {
        if( bufferSize <= DEFAULT_MIN ) {
            bufferSize = DEFAULT_MIN ;
        }
        else if( bufferSize >= DEFAULT_MAX ) {
            bufferSize = DEFAULT_MAX ;
        }
        this.cache = new byte[bufferSize];
        this.stream = stream;
    }
    public ChunkedOutputStream(OutputStream stream) throws IOException {
        this(stream, DEFAULT_BUF);
    }
    protected void flushCache() throws IOException {
        if (cachePosition > 0) {
            StringBuilder buf = new StringBuilder() ;
            buf.append( Integer.toHexString(cachePosition) ) ;
//System.out.println( "flushCache():" + cachePosition ) ;
            buf.append( ENTER_STR ) ;
            
            byte chunkHeader[] = buf.toString().getBytes( CHARSET ) ;
            buf = null ;
            stream.write(chunkHeader, 0, chunkHeader.length);
            stream.write(cache, 0, cachePosition);
            stream.write(ENDCHUNK, 0, ENDCHUNK.length);
            cachePosition = 0;
        }
    }
    protected void writeClosingChunk() throws IOException {
//System.out.println( "writeClosingChunk():" + 0 ) ;
        stream.write(ZERO, 0, ZERO.length);
        stream.write(ENTER, 0, ENTER.length);
        stream.write(ENDCHUNK, 0, ENDCHUNK.length);
    }
    public void finish() throws IOException {
        if (!wroteLastChunk) {
            flushCache();
            writeClosingChunk();
            wroteLastChunk = true;
        }
    }
    public void write(int b) throws IOException {
        cache[cachePosition] = (byte) b;
        cachePosition++;
        if (cachePosition == cache.length) flushCache();
    }
    public void write(byte b[]) throws IOException {
        this.write(b, 0, b.length);
    }
    public void write(byte src[], int off, int len) throws IOException {
        if (len >= cache.length - cachePosition) {
            flushCacheWithAppend(src, off, len);
        } else {
            System.arraycopy(src, off, cache, cachePosition, len);
            cachePosition += len;
        }
    }
    protected void flushCacheWithAppend(byte bufferToAppend[], int off, int len) throws IOException {
        StringBuilder buf = new StringBuilder() ;
        buf.append( Integer.toHexString(cachePosition + len) ) ;
//System.out.println( "flushCache():" + (cachePosition + len) ) ;
        buf.append( ENTER_STR ) ;
        
        byte chunkHeader[] = buf.toString().getBytes( CHARSET ) ;
        buf = null ;
        stream.write(chunkHeader, 0, chunkHeader.length);
        stream.write(cache, 0, cachePosition);
        stream.write(bufferToAppend, off, len);
        stream.write(ENDCHUNK, 0, ENDCHUNK.length);
        cachePosition = 0;
    }
    public void flush() throws IOException {
        stream.flush();
    }
    public void close() throws IOException {
        finish();
        super.close();
    }
}
