package com.jscape.util.io;

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

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

/**
 * @author Alex Usun
 */
public class CountingInputStream
        extends InputStream {

    private final InputStream base;
    private final boolean owner;
    private final Listener listener;
    private long readBytes;
    
    public CountingInputStream(InputStream base, boolean owner, Listener listener) {
        assertNotNull(base);
        this.base = base;
        
        this.owner = owner;

        assertNotNull(listener);
        this.listener = listener;

        this.readBytes = 0L;
    }

    public CountingInputStream(InputStream base, boolean owner) {
        this(base, owner, Listener.NULL);
    }

    public CountingInputStream(InputStream base) {
        this(base, true);
    }

    @Override
    public int read()
            throws IOException {
        int ret = this.base.read();

        if (ret != -1) {
            ++this.readBytes;
            this.listener.onDataRead(this, this.readBytes);
        }

        return ret;
    }

    @Override
    public int read(byte[] buffer, int offset, int length)
            throws IOException {
        int read = this.base.read(buffer, offset, length);

        if (read != -1) {
            this.readBytes += read;
            this.listener.onDataRead(this, this.readBytes);
        }

        return read;
    }

    @Override
    public long skip(long n)
            throws IOException {
        long skipped = this.base.skip(n);
        this.readBytes += skipped;
        this.listener.onDataRead(this, this.readBytes);

        return skipped;
    }

    @Override
    public int available()
            throws IOException {
        return this.base.available();
    }

    @Override
    public void mark(int readlimit) {
        this.base.mark(readlimit);
    }

    @Override
    public void reset()
            throws IOException {
        this.base.reset();
    }

    @Override
    public boolean markSupported() {
        return this.base.markSupported();
    }

    @Override
    public void close()
            throws IOException {
        if (this.owner) {
            this.base.close();
        }

        this.listener.onClosed(this, this.readBytes);
    }

    public long readBytes() {
        return this.readBytes;
    }
    
    @Override
    public String toString() {
        return "CountingInputStream {"
               + "base=" + this.base
               + ", owner=" + this.owner
               + ", readBytes=" + this.readBytes
               + '}';
    }

    public interface Listener {

        Listener NULL = new Listener() {
            @Override
            public void onDataRead(CountingInputStream stream, long readBytes) {
                // empty
            }

            @Override
            public void onClosed(CountingInputStream stream, long readBytes) {
                // empty
            }
        };

        void onDataRead(CountingInputStream stream, long readBytes);

        void onClosed(CountingInputStream stream, long readBytes);
    }
}
