package com.limegroup.gnutella.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import com.limegroup.gnutella.ByteOrder;

/** For implementation details, please see:
 *  http://www.acm.org/sigcomm/sigcomm97/papers/p062.pdf 
 */
public class COBSUtil {
    
    /** Encode a byte array with COBS.  The non-allowable byte value is 0.
     *  PRE: src is not null.
     *  POST: the return array will be a cobs encoded version of src.  namely,
     *  cobsDecode(cobsEncode(src)) ==  src.
     *  @return a COBS encoded version of src.
     */
    public static byte[] cobsEncode(byte[] src) throws IOException {
        final int srcLen = src.length;
        int code = 1;
        int currIndex = 0;
        // COBS encoding adds no more than one byte of overhead for every 254
        // bytes of packet data
        final int maxEncodingLen = src.length + ((src.length+1)/254) + 1;
        ByteArrayOutputStream sink = new ByteArrayOutputStream(maxEncodingLen);
        int writeStartIndex = -1;

        while (currIndex < srcLen) {
            if (src[currIndex] == 0) {
                // currIndex was incremented so take 1 less
                code = finishBlock(code, sink, src, writeStartIndex,
                                   (currIndex-1));
                writeStartIndex = -1;
            }
            else {
                if (writeStartIndex < 0) writeStartIndex = currIndex;
                code++;
                if (code == 0xFF) {
                    code = finishBlock(code, sink, src, writeStartIndex,
                                       currIndex);
                    writeStartIndex = -1;
                }
            }
            currIndex++;
        }

        // currIndex was incremented so take 1 less
        finishBlock(code, sink, src, writeStartIndex, (currIndex-1));
        return sink.toByteArray();
    }

    private static int finishBlock(int code, ByteArrayOutputStream sink, 
                                   byte[] src, int begin, int end) 
        throws IOException {
        sink.write(code);
        if (begin > -1)
            sink.write(src, begin, (end-begin)+1);
        return (byte) 0x01;
    }

    /** Decode a COBS-encoded byte array.  The non-allowable byte value is 0.
     *  PRE: src is not null.
     *  POST: the return array will be a cobs decoded version of src.  namely,
     *  cobsDecode(cobsEncode(src)) ==  src.  
     *  @return the original COBS decoded string
     */
    public static byte[] cobsDecode(byte[] src) throws IOException {
        final int srcLen = src.length;
        int currIndex = 0;
        int code = 0;
        ByteArrayOutputStream sink = new ByteArrayOutputStream();        

        while (currIndex < srcLen) {
            code = ByteOrder.ubyte2int(src[currIndex++]);
            if ((currIndex+(code-2)) >= srcLen)
                throw new IOException();
            for (int i = 1; i < code; i++) {
                sink.write((int)src[currIndex++]);
            }
            if (currIndex < srcLen) // don't write this last one, it isn't used
                if (code < 0xFF) sink.write(0);
        }

        return sink.toByteArray();
    }

}
