/*
 * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package sun.net.httpserver;

import java.io.*;
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;

/**
 * a (filter) input stream which can tell us if bytes are "left over"
 * on the underlying stream which can be read (without blocking)
 * on another instance of this class.
 *
 * The class can also report if all bytes "expected" to be read
 * were read, by the time close() was called. In that case,
 * bytes may be drained to consume them (by calling drain() ).
 *
 * isEOF() returns true, when all expected bytes have been read
 */
abstract class LeftOverInputStream extends FilterInputStream {
    final ExchangeImpl t;
    final ServerImpl server;
    protected boolean closed = false;
    protected boolean eof = false;
    byte[] one = new byte [1];
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;

    public LeftOverInputStream (ExchangeImpl t, InputStream src) {
        super (src);
        this.t = t;
        this.server = t.getServerImpl();
    }
    /**
     * if bytes are left over buffered on *the UNDERLYING* stream
     */
    public boolean isDataBuffered () throws IOException {
        assert eof;
        return super.available() > 0;
    }

    public void close () throws IOException {
        if (closed) {
            return;
        }
        closed = true;
        if (!eof) {
            eof = drain (ServerConfig.getDrainAmount());
        }
    }

    public boolean isClosed () {
        return closed;
    }

    public boolean isEOF () {
        return eof;
    }

    protected abstract int readImpl (byte[]b, int off, int len) throws IOException;

    public synchronized int read () throws IOException {
        if (closed) {
            throw new IOException ("Stream is closed");
        }
        int c = readImpl (one, 0, 1);
        if (c == -1 || c == 0) {
            return c;
        } else {
            return one[0] & 0xFF;
        }
    }

    public synchronized int read (byte[]b, int off, int len) throws IOException {
        if (closed) {
            throw new IOException ("Stream is closed");
        }
        return readImpl (b, off, len);
    }

    @Override
    public synchronized long skip(long n) throws IOException {
        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }

        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            if (server.isFinishing()) {
                break;
            }
            nr = readImpl(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                eof = true;
                break;
            }
            remaining -= nr;
        }

        return n - remaining;
    }

    /**
     * read and discard up to l bytes or "eof" occurs,
     * (whichever is first). Then return true if the stream
     * is at eof (ie. all bytes were read) or false if not
     * (still bytes to be read)
     */
    public boolean drain (long l) throws IOException {
        while (l > 0) {
            long skip = skip(l);
            if (skip <= 0) break; // might return 0 if isFinishing or EOF
            l -= skip;
        }
        return eof;
    }
}
