/* Copyright (c) 2007 Jython Developers */
package org.python.core.io;

import java.nio.ByteBuffer;
import java.nio.channels.Channel;

import org.python.core.Py;

/**
 * Base class for raw binary I/O.
 *
 * RawIOBases wrap raw Java I/O objects (typically nio Channels). They
 * provide a convenient means of handling raw Java I/O objects in the
 * context of Python files.
 *
 * RawIOBases maintain state about their underlying I/O objects (such
 * as their mode) and translate Java exceptions into PyExceptions.
 *
 * The read() method is implemented by calling readinto(); derived
 * classes that want to support read() only need to implement
 * readinto() as a primitive operation. In general, readinto() can be
 * more efficient than read().
 *
 * @author Philip Jenvey
 */
public abstract class RawIOBase extends IOBase {

    /**
     * Read and return up to size bytes, contained in a ByteBuffer.
     *
     * ByteBuffers returned from read are already flip()'d.
     *
     * Returns an empty ByteBuffer on EOF
     *
     * @param size the number of bytes to read
     * @return a ByteBuffer containing the bytes read
     */
    public ByteBuffer read(int size) {
        if (size < 0) {
            return readall();
        }

        ByteBuffer buf = ByteBuffer.allocate(size);
        readinto(buf);
        // flip()ing here is more convenient as there's no real use
        // case for appending to buffers returned from read. readinto
        // doesn't/shouldn't flip()
        buf.flip();
        return buf;
    }

    /**
     * Read until EOF, using multiple read() calls.
     *
     * @return a ByteBuffer containing the bytes read
     */
    public ByteBuffer readall() {
        ByteBuffer all = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
        ByteBuffer readBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);

        for (int readCount = 0; (readCount = readinto(readBuffer)) > 0;) {
            if (all.remaining() < readCount) {
                long newSize = (long) all.position() + readCount;
                if (newSize > Integer.MAX_VALUE) {
                    throw Py.OverflowError("requested number of bytes is more than a Python "
                                           + "string can hold");
                }

                ByteBuffer old = all;
                all = ByteBuffer.allocate(Math.max(old.capacity() * 2, (int) newSize));
                old.flip();
                all.put(old);
            }
            readBuffer.flip();
            all.put(readBuffer);
            readBuffer.clear();
        }

        all.flip();
        return all;
    }

    /**
     * Read up to buf.remaining() bytes into buf.
     *
     * Returns number of bytes read (0 for EOF).
     *
     * @param buf a ByteBuffer to read bytes into
     * @return the amount of data read as an int
     */
    public int readinto(ByteBuffer buf) {
        unsupported("readinto");
        return -1;
    }

    /**
     * Read bytes into each of the specified ByteBuffers.
     *
     * Returns number of bytes read (0 for EOF).
     *
     * @param bufs an array of ByteBuffers to read bytes into
     * @return the amount of data read as a long
     */
    public long readinto(ByteBuffer[] bufs) {
        long count = 0;
        int bufCount;
        for (ByteBuffer buf : bufs) {
            if (!buf.hasRemaining()) {
                continue;
            }
            if ((bufCount = readinto(buf)) == 0) {
                break;
            }
            count += bufCount;
        }
        return count;
    }

    /**
     * Write the given ByteBuffer to the IO stream.
     *
     * Returns the number of bytes written, which may be less than
     * buf.remaining().
     *
     * @param buf a ByteBuffer value
     * @return the number of bytes written as an int
     */
    public int write(ByteBuffer buf) {
        unsupported("write");
        return -1;
    }

    /**
     * Write the given ByteBuffers to the IO stream.
     *
     * Returns the number of bytes written, which may be less than the
     * combined value of all the buf.remaining()'s.
     *
     * @param bufs an array of  ByteBuffers
     * @return the number of bytes written as a long
     */
    public long write(ByteBuffer[] bufs) {
        long count = 0;
        int bufCount;
        for (ByteBuffer buf : bufs) {
            if (!buf.hasRemaining()) {
                continue;
            }
            if ((bufCount = write(buf)) == 0) {
                break;
            }
            count += bufCount;
        }
        return count;
    }

    @Override
    public RawIOBase fileno() {
        checkClosed();
        return this;
    }

    /**
     * Return the underlying Java nio Channel.
     *
     * @return the underlying Java nio Channel
     */
    public abstract Channel getChannel();
}
