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

import com.kenai.constantine.platform.Errno;
import java.io.InputStream;
import java.io.OutputStream;

import org.python.core.Py;
import org.python.core.PyException;

/**
 * Base class for all I/O classes.
 *
 * IOBase and its descendents in org.python.core.io are based off Python 3's new io module (PEP
 * 3116).
 *
 * This does not define read(), readinto() and write(), nor readline() and friends, since their
 * signatures vary per layer.
 *
 * @author Philip Jenvey
 */
public abstract class IOBase {

    /** The default size of generic buffers */
    public static final int DEFAULT_BUFFER_SIZE = 8192;

    /** Byte representation of the Line Feed character */
    protected static final byte LF_BYTE = 10;

    /** true if the file has been closed */
    private boolean closed = false;

    /**
     * Seek to byte offset pos relative to the start of the stream.
     *
     * Returns the new absolute position.
     *
     * @param pos
     *            a long position value
     * @return a long position value seeked to
     */
    public long seek(long pos) {
        return seek(pos, 0);
    }

    /**
     * Seek to byte offset pos relative to position indicated by whence: 0 Start of stream (the
     * default). pos should be >= 0; 1 Current position - whence may be negative; 2 End of stream -
     * whence usually negative.
     *
     * Returns the new absolute position.
     *
     * @param pos
     *            a long position value
     * @param whence
     *            an int whence value
     * @return a long position value seeked to
     */
    public long seek(long pos, int whence) {
        unsupported("seek");
        return -1;
    }

    /**
     * Return the current stream position.
     *
     * @return a long position value
     */
    public long tell() {
        return seek(0, 1);
    }

    /**
     * Truncate file to size in bytes.
     *
     * Returns the new size.
     *
     * @param size
     *            a long size to truncate to
     * @return a long size value the file was truncated to
     */
    public long truncate(long size) {
        unsupported("truncate");
        return -1;
    }

    /**
     * Flushes write buffers, if applicable.
     *
     * This is a no-op for read-only and non-blocking streams.
     *
     */
    public void flush() {}

    /**
     * Flushes and closes the IO object.
     *
     * This must be idempotent. It should also set a flag for the 'closed' property (see below) to
     * test.
     */
    public void close() {
        if (closed()) {
            return;
        }
        try {
            flush();
        } catch (PyException pye) {
            if (!pye.match(Py.IOError)) {
                throw pye;
            }
            // If flush() fails, just give up
        }
        closed = true;
    }

    /**
     * Returns underlying file descriptor if one exists.
     *
     * Raises IOError if the IO object does not use a file descriptor.
     *
     * @return a file descriptor
     */
    public RawIOBase fileno() {
        unsupported("fileno");
        return null;
    }

    /**
     * Returns whether this is an 'interactive' stream.
     *
     * Returns False if we don't know.
     *
     * @return a boolean, true if an 'interactive' stream
     */
    public boolean isatty() {
        checkClosed();
        return false;
    }

    /**
     * Return whether this file was opened for reading.
     *
     * @return true if the file was opened for reading
     */
    public boolean readable() {
        return false;
    }

    /**
     * Raise an IOError if the file is not readable.
     *
     */
    public void checkReadable() {
        if (!readable()) {
            throw Py.IOError(Errno.EBADF);
        }
    }

    /**
     * Return whether this file was opened for writing.
     *
     * @return true if the file was opened for writing
     */
    public boolean writable() {
        return false;
    }

    /**
     * Raise an IOError if the file is not writable.
     *
     */
    public void checkWritable() {
        if (!writable()) {
            throw Py.IOError(Errno.EBADF);
        }
    }

    /**
     * Return whether this file has been closed.
     *
     * @return true if the file has been closed
     */
    public boolean closed() {
        return closed;
    }

    /**
     * Raise a ValueError if the file is closed.
     *
     */
    public void checkClosed() {
        if (closed()) {
            throw Py.ValueError("I/O operation on closed file");
        }
    }

    /**
     * Coerce this into an OutputStream if possible, or return null.
     */
    public OutputStream asOutputStream() {
        return null;
    }

    /**
     * Coerce this into an InputStream if possible, or return null.
     */
    public InputStream asInputStream() {
        return null;
    }

    /**
     * Raise a TypeError indicating the specified operation is not supported.
     *
     * @param methodName
     *            the String name of the operation
     */
    protected void unsupported(String methodName) {
        String qualifiedName = getClass().getName();
        String className = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
        throw Py.IOError(String.format("%s.%s() not supported", className, methodName));
    }
}
