package org.postgresql.largeobject;

import java.io.*;
import java.lang.*;
import java.net.*;
import java.util.*;
import java.sql.*;

import org.postgresql.fastpath.*;

/*
 * This class implements the large object interface to org.postgresql.
 *
 * <p>It provides the basic methods required to run the interface, plus
 * a pair of methods that provide InputStream and OutputStream classes
 * for this object.
 *
 * <p>Normally, client code would use the getAsciiStream, getBinaryStream,
 * or getUnicodeStream methods in ResultSet, or setAsciiStream,
 * setBinaryStream, or setUnicodeStream methods in PreparedStatement to
 * access Large Objects.
 *
 * <p>However, sometimes lower level access to Large Objects are required,
 * that are not supported by the JDBC specification.
 *
 * <p>Refer to org.postgresql.largeobject.LargeObjectManager on how to gain access
 * to a Large Object, or how to create one.
 *
 * @see org.postgresql.largeobject.LargeObjectManager
 * @see org.postgresql.ResultSet#getAsciiStream
 * @see org.postgresql.ResultSet#getBinaryStream
 * @see org.postgresql.ResultSet#getUnicodeStream
 * @see org.postgresql.PreparedStatement#setAsciiStream
 * @see org.postgresql.PreparedStatement#setBinaryStream
 * @see org.postgresql.PreparedStatement#setUnicodeStream
 * @see java.sql.ResultSet#getAsciiStream
 * @see java.sql.ResultSet#getBinaryStream
 * @see java.sql.ResultSet#getUnicodeStream
 * @see java.sql.PreparedStatement#setAsciiStream
 * @see java.sql.PreparedStatement#setBinaryStream
 * @see java.sql.PreparedStatement#setUnicodeStream
 *
 */
public class LargeObject
{
	/*
	 * Indicates a seek from the begining of a file
	 */
	public static final int SEEK_SET = 0;

	/*
	 * Indicates a seek from the current position
	 */
	public static final int SEEK_CUR = 1;

	/*
	 * Indicates a seek from the end of a file
	 */
	public static final int SEEK_END = 2;

	private Fastpath	fp; // Fastpath API to use
	private int oid;	// OID of this object
	private int fd; // the descriptor of the open large object

	private BlobOutputStream os;  // The current output stream

	private boolean closed = false; // true when we are closed

	/*
	 * This opens a large object.
	 *
	 * <p>If the object does not exist, then an SQLException is thrown.
	 *
	 * @param fp FastPath API for the connection to use
	 * @param oid of the Large Object to open
	 * @param mode Mode of opening the large object
	 * (defined in LargeObjectManager)
	 * @exception SQLException if a database-access error occurs.
	 * @see org.postgresql.largeobject.LargeObjectManager
	 */
	protected LargeObject(Fastpath fp, int oid, int mode) throws SQLException
	{
		this.fp = fp;
		this.oid = oid;

		FastpathArg args[] = new FastpathArg[2];
		args[0] = new FastpathArg(oid);
		args[1] = new FastpathArg(mode);
		this.fd = fp.getInteger("lo_open", args);
	}

	/* Release large object resources during garbage cleanup */
	protected void finalize() throws SQLException
	{
	    //This code used to call close() however that was problematic
	    //because the scope of the fd is a transaction, thus if commit
	    //or rollback was called before garbage collection ran then 
	    //the call to close would error out with an invalid large object
	    //handle.  So this method now does nothing and lets the server
	    //handle cleanup when it ends the transaction.
	}

	/*
	 * @return the OID of this LargeObject
	 */
	public int getOID()
	{
		return oid;
	}

	/*
	 * This method closes the object. You must not call methods in this
	 * object after this is called.
	 * @exception SQLException if a database-access error occurs.
	 */
	public void close() throws SQLException
	{
		if (!closed)
		{
			// flush any open output streams
			if (os != null)
			{
				try
				{
					// we can't call os.close() otherwise we go into an infinite loop!
					os.flush();
				}
				catch (IOException ioe)
				{
					throw new SQLException(ioe.getMessage());
				}
				finally
				{
					os = null;
				}
			}

			// finally close
			FastpathArg args[] = new FastpathArg[1];
			args[0] = new FastpathArg(fd);
			fp.fastpath("lo_close", false, args); // true here as we dont care!!
			closed = true;
		}
	}

	/*
	 * Reads some data from the object, and return as a byte[] array
	 *
	 * @param len number of bytes to read
	 * @return byte[] array containing data read
	 * @exception SQLException if a database-access error occurs.
	 */
	public byte[] read(int len) throws SQLException
	{
		// This is the original method, where the entire block (len bytes)
		// is retrieved in one go.
		FastpathArg args[] = new FastpathArg[2];
		args[0] = new FastpathArg(fd);
		args[1] = new FastpathArg(len);
		return fp.getData("loread", args);

		// This version allows us to break this down into 4k blocks
		//if (len<=4048) {
		//// handle as before, return the whole block in one go
		//FastpathArg args[] = new FastpathArg[2];
		//args[0] = new FastpathArg(fd);
		//args[1] = new FastpathArg(len);
		//return fp.getData("loread",args);
		//} else {
		//// return in 4k blocks
		//byte[] buf=new byte[len];
		//int off=0;
		//while (len>0) {
		//int bs=4048;
		//len-=bs;
		//if (len<0) {
		//bs+=len;
		//len=0;
		//}
		//read(buf,off,bs);
		//off+=bs;
		//}
		//return buf;
		//}
	}

	/*
	 * Reads some data from the object into an existing array
	 *
	 * @param buf destination array
	 * @param off offset within array
	 * @param len number of bytes to read
	 * @return the number of bytes actually read
	 * @exception SQLException if a database-access error occurs.
	 */
	public int read(byte buf[], int off, int len) throws SQLException
	{
		byte b[] = read(len);
		if (b.length < len)
			len = b.length;
		System.arraycopy(b, 0, buf, off, len);
		return len;
	}

	/*
	 * Writes an array to the object
	 *
	 * @param buf array to write
	 * @exception SQLException if a database-access error occurs.
	 */
	public void write(byte buf[]) throws SQLException
	{
		FastpathArg args[] = new FastpathArg[2];
		args[0] = new FastpathArg(fd);
		args[1] = new FastpathArg(buf);
		fp.fastpath("lowrite", false, args);
	}

	/*
	 * Writes some data from an array to the object
	 *
	 * @param buf destination array
	 * @param off offset within array
	 * @param len number of bytes to write
	 * @exception SQLException if a database-access error occurs.
	 */
	public void write(byte buf[], int off, int len) throws SQLException
	{
		byte data[] = new byte[len];
		System.arraycopy(buf, off, data, 0, len);
		write(data);
	}

	/*
	 * Sets the current position within the object.
	 *
	 * <p>This is similar to the fseek() call in the standard C library. It
	 * allows you to have random access to the large object.
	 *
	 * @param pos position within object
	 * @param ref Either SEEK_SET, SEEK_CUR or SEEK_END
	 * @exception SQLException if a database-access error occurs.
	 */
	public void seek(int pos, int ref) throws SQLException
	{
		FastpathArg args[] = new FastpathArg[3];
		args[0] = new FastpathArg(fd);
		args[1] = new FastpathArg(pos);
		args[2] = new FastpathArg(ref);
		fp.fastpath("lo_lseek", false, args);
	}

	/*
	 * Sets the current position within the object.
	 *
	 * <p>This is similar to the fseek() call in the standard C library. It
	 * allows you to have random access to the large object.
	 *
	 * @param pos position within object from begining
	 * @exception SQLException if a database-access error occurs.
	 */
	public void seek(int pos) throws SQLException
	{
		seek(pos, SEEK_SET);
	}

	/*
	 * @return the current position within the object
	 * @exception SQLException if a database-access error occurs.
	 */
	public int tell() throws SQLException
	{
		FastpathArg args[] = new FastpathArg[1];
		args[0] = new FastpathArg(fd);
		return fp.getInteger("lo_tell", args);
	}

	/*
	 * This method is inefficient, as the only way to find out the size of
	 * the object is to seek to the end, record the current position, then
	 * return to the original position.
	 *
	 * <p>A better method will be found in the future.
	 *
	 * @return the size of the large object
	 * @exception SQLException if a database-access error occurs.
	 */
	public int size() throws SQLException
	{
		int cp = tell();
		seek(0, SEEK_END);
		int sz = tell();
		seek(cp, SEEK_SET);
		return sz;
	}

	/*
	 * Returns an InputStream from this object.
	 *
	 * <p>This InputStream can then be used in any method that requires an
	 * InputStream.
	 *
	 * @exception SQLException if a database-access error occurs.
	 */
	public InputStream getInputStream() throws SQLException
	{
		return new BlobInputStream(this);
	}

	/*
	 * Returns an OutputStream to this object
	 *
	 * <p>This OutputStream can then be used in any method that requires an
	 * OutputStream.
	 *
	 * @exception SQLException if a database-access error occurs.
	 */
	public OutputStream getOutputStream() throws SQLException
	{
		if (os == null)
			os = new BlobOutputStream(this);
		return os;
	}

}
