package com.jscape.inet.http.protocol.marshaling;

import com.jscape.inet.http.protocol.messages.Headers;
import com.jscape.util.io.ByteArrayInputStream;
import com.jscape.util.io.ByteArrayOutputStream;

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

import static com.jscape.util.Parameters.assertGreater;
import static com.jscape.util.Parameters.assertNotNull;

/**
 * @author Alex Usun
 */
public class ChunkEncodingInputStream
        extends InputStream {
    
    private final InputStream base;
    private final byte[] buffer;
    private final Headers trailerHeaders;
    private final ByteArrayOutputStream writingBuffer;
    private final ByteArrayInputStream readingBuffer;
    private boolean eofReached;

    public ChunkEncodingInputStream(InputStream base, int chunkSize, Headers trailerHeaders) {
        assertNotNull(base);
        this.base = base;

        assertGreater(chunkSize, 0, "Invalid chunk size value.");
        this.buffer = new byte[chunkSize];

        assertNotNull(trailerHeaders);
        this.trailerHeaders = trailerHeaders;

        this.writingBuffer = new ByteArrayOutputStream();
        this.readingBuffer = ByteArrayInputStream.empty();
    }

    @Override
    public int read()
            throws IOException {
        adjustBuffer();
        return this.readingBuffer.read();
    }

    @Override
    public int read(byte[] buffer, int offset, int length)
            throws IOException {
        adjustBuffer();
        return this.readingBuffer.read(buffer, offset, length);
    }

    private void adjustBuffer()
            throws IOException {
        if (this.readingBuffer.available() > 0 || this.eofReached) {
            return;
        }

        this.writingBuffer.reset();

        int read = this.base.read(this.buffer);

        if (read != -1) {
            ChunkCodec.writeValue(new Chunk(this.buffer, 0, read), this.writingBuffer);
            this.readingBuffer.put(this.writingBuffer.buffer(), 0, this.writingBuffer.position());
        } else {
            ChunkCodec.writeValue(Chunk.empty(), this.writingBuffer);
            this.readingBuffer.put(this.writingBuffer.buffer(), 0, this.writingBuffer.position());
            
            HeadersCodec.writeValue(this.trailerHeaders.toArray(), this.writingBuffer);
            this.readingBuffer.put(this.writingBuffer.buffer(), 0, this.writingBuffer.position());
            this.eofReached = true;
        }
    }
}
