/*
 * Copyright (c) 2003, PostgreSQL Global Development Group
 * See the LICENSE file in the project root for more information.
 */

package org.postgresql.largeobject;

import org.postgresql.core.BaseConnection;
import org.postgresql.fastpath.Fastpath;
import org.postgresql.fastpath.FastpathArg;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;

/**
 * This class implements the large object interface to org.postgresql.
 *
 * <p>It provides methods that allow client code to create, open and delete large objects from the
 * database. When opening an object, an instance of org.postgresql.largeobject.LargeObject is
 * returned, and its methods then allow access to the object.</p>
 *
 * <p>This class can only be created by {@link BaseConnection}</p>
 *
 * <p>To get access to this class, use the following segment of code:</p>
 *
 * <pre>
 * import org.postgresql.largeobject.*;
 *
 * Connection  conn;
 * LargeObjectManager lobj;
 *
 * ... code that opens a connection ...
 *
 * lobj = ((org.postgresql.PGConnection)myconn).getLargeObjectAPI();
 * </pre>
 *
 * <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>
 *
 * <p>However, sometimes lower level access to Large Objects are required, that are not supported by
 * the JDBC specification.</p>
 *
 * <p>Refer to org.postgresql.largeobject.LargeObject on how to manipulate the contents of a Large
 * Object.</p>
 *
 * @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 LargeObjectManager {
  // the fastpath api for this connection
  private Fastpath fp;
  private BaseConnection conn;

  /**
   * This mode indicates we want to write to an object.
   */
  public static final int WRITE = 0x00020000;

  /**
   * This mode indicates we want to read an object.
   */
  public static final int READ = 0x00040000;

  /**
   * This mode is the default. It indicates we want read and write access to a large object.
   */
  public static final int READWRITE = READ | WRITE;

  /**
   * This prevents us being created by mere mortals.
   */
  private LargeObjectManager() {
  }

  /**
   * <p>Constructs the LargeObject API.</p>
   *
   * <p><b>Important Notice</b> <br>
   * This method should only be called by {@link BaseConnection}</p>
   *
   * <p>There should only be one LargeObjectManager per Connection. The {@link BaseConnection} class
   * keeps track of the various extension API's and it's advised you use those to gain access, and
   * not going direct.</p>
   *
   * @param conn connection
   * @throws SQLException if something wrong happens
   */
  public LargeObjectManager(BaseConnection conn) throws SQLException {
    this.conn = conn;
    // We need Fastpath to do anything
    this.fp = conn.getFastpathAPI();

    // Now get the function oid's for the api
    //
    // This is an example of Fastpath.addFunctions();
    //
    String sql;
    if (conn.getMetaData().supportsSchemasInTableDefinitions()) {
      sql = "SELECT p.proname,p.oid "
          + " FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "
          + " WHERE p.pronamespace=n.oid AND n.nspname='pg_catalog' AND (";
    } else {
      sql = "SELECT proname,oid FROM pg_proc WHERE ";
    }
    sql += " proname = 'lo_open'"
        + " or proname = 'lo_close'"
        + " or proname = 'lo_creat'"
        + " or proname = 'lo_unlink'"
        + " or proname = 'lo_lseek'"
        + " or proname = 'lo_lseek64'"
        + " or proname = 'lo_tell'"
        + " or proname = 'lo_tell64'"
        + " or proname = 'loread'"
        + " or proname = 'lowrite'"
        + " or proname = 'lo_truncate'"
        + " or proname = 'lo_truncate64'";

    if (conn.getMetaData().supportsSchemasInTableDefinitions()) {
      sql += ")";
    }

    Statement stmt = conn.createStatement();
    ResultSet res = stmt.executeQuery(sql);

    if (res == null) { // NOSONAR
      throw new PSQLException(GT.tr("Failed to initialize LargeObject API"),
          PSQLState.SYSTEM_ERROR);
    }

    fp.addFunctions(res);
    res.close();
    stmt.close();

    conn.getLogger().log(Level.FINE, "Large Object initialised");
  }

  /**
   * This opens an existing large object, based on its OID. This method assumes that READ and WRITE
   * access is required (the default).
   *
   * @param oid of large object
   * @return LargeObject instance providing access to the object
   * @throws SQLException on error
   * @deprecated As of 8.3, replaced by {@link #open(long)}
   */
  @Deprecated
  public LargeObject open(int oid) throws SQLException {
    return open((long) oid, false);
  }

  /**
   * This opens an existing large object, same as previous method, but commits the transaction on
   * close if asked. This is useful when the LOB is returned to a caller which won't take care of
   * transactions by itself.
   *
   * @param oid of large object
   * @param commitOnClose commit the transaction when this LOB will be closed
   * @return LargeObject instance providing access to the object
   * @throws SQLException on error
   */

  public LargeObject open(int oid, boolean commitOnClose) throws SQLException {
    return open((long) oid, commitOnClose);
  }

  /**
   * This opens an existing large object, based on its OID. This method assumes that READ and WRITE
   * access is required (the default).
   *
   * @param oid of large object
   * @return LargeObject instance providing access to the object
   * @throws SQLException on error
   */
  public LargeObject open(long oid) throws SQLException {
    return open(oid, READWRITE, false);
  }

  /**
   * This opens an existing large object, same as previous method, but commits the transaction on
   * close if asked.
   *
   * @param oid of large object
   * @param commitOnClose commit the transaction when this LOB will be closed
   * @return LargeObject instance providing access to the object
   * @throws SQLException on error
   */

  public LargeObject open(long oid, boolean commitOnClose) throws SQLException {
    return open(oid, READWRITE, commitOnClose);
  }

  /**
   * This opens an existing large object, based on its OID.
   *
   * @param oid of large object
   * @param mode mode of open
   * @return LargeObject instance providing access to the object
   * @throws SQLException on error
   * @deprecated As of 8.3, replaced by {@link #open(long, int)}
   */
  @Deprecated
  public LargeObject open(int oid, int mode) throws SQLException {
    return open((long) oid, mode, false);
  }

  /**
   * This opens an existing large object, same as previous method, but commits the transaction on
   * close if asked.
   *
   * @param oid of large object
   * @param mode mode of open
   * @param commitOnClose commit the transaction when this LOB will be closed
   * @return LargeObject instance providing access to the object
   * @throws SQLException on error
   */

  public LargeObject open(int oid, int mode, boolean commitOnClose) throws SQLException {
    return open((long) oid, mode, commitOnClose);
  }

  /**
   * This opens an existing large object, based on its OID.
   *
   * @param oid of large object
   * @param mode mode of open
   * @return LargeObject instance providing access to the object
   * @throws SQLException on error
   */
  public LargeObject open(long oid, int mode) throws SQLException {
    return open(oid, mode, false);
  }

  /**
   * This opens an existing large object, based on its OID.
   *
   * @param oid of large object
   * @param mode mode of open
   * @param commitOnClose commit the transaction when this LOB will be closed
   * @return LargeObject instance providing access to the object
   * @throws SQLException on error
   */
  public LargeObject open(long oid, int mode, boolean commitOnClose) throws SQLException {
    if (conn.getAutoCommit()) {
      throw new PSQLException(GT.tr("Large Objects may not be used in auto-commit mode."),
          PSQLState.NO_ACTIVE_SQL_TRANSACTION);
    }
    return new LargeObject(fp, oid, mode, conn, commitOnClose);
  }

  /**
   * <p>This creates a large object, returning its OID.</p>
   *
   * <p>It defaults to READWRITE for the new object's attributes.</p>
   *
   * @return oid of new object
   * @throws SQLException on error
   * @deprecated As of 8.3, replaced by {@link #createLO()}
   */
  @Deprecated
  public int create() throws SQLException {
    return create(READWRITE);
  }

  /**
   * <p>This creates a large object, returning its OID.</p>
   *
   * <p>It defaults to READWRITE for the new object's attributes.</p>
   *
   * @return oid of new object
   * @throws SQLException if something wrong happens
   */
  public long createLO() throws SQLException {
    return createLO(READWRITE);
  }

  /**
   * This creates a large object, returning its OID.
   *
   * @param mode a bitmask describing different attributes of the new object
   * @return oid of new object
   * @throws SQLException on error
   */
  public long createLO(int mode) throws SQLException {
    if (conn.getAutoCommit()) {
      throw new PSQLException(GT.tr("Large Objects may not be used in auto-commit mode."),
          PSQLState.NO_ACTIVE_SQL_TRANSACTION);
    }
    FastpathArg[] args = new FastpathArg[1];
    args[0] = new FastpathArg(mode);
    return fp.getOID("lo_creat", args);
  }

  /**
   * This creates a large object, returning its OID.
   *
   * @param mode a bitmask describing different attributes of the new object
   * @return oid of new object
   * @throws SQLException on error
   * @deprecated As of 8.3, replaced by {@link #createLO(int)}
   */
  @Deprecated
  public int create(int mode) throws SQLException {
    long oid = createLO(mode);
    return (int) oid;
  }

  /**
   * This deletes a large object.
   *
   * @param oid describing object to delete
   * @throws SQLException on error
   */
  public void delete(long oid) throws SQLException {
    FastpathArg[] args = new FastpathArg[1];
    args[0] = Fastpath.createOIDArg(oid);
    fp.fastpath("lo_unlink", args);
  }

  /**
   * <p>This deletes a large object.</p>
   *
   * <p>It is identical to the delete method, and is supplied as the C API uses unlink.</p>
   *
   * @param oid describing object to delete
   * @throws SQLException on error
   * @deprecated As of 8.3, replaced by {@link #unlink(long)}
   */
  @Deprecated
  public void unlink(int oid) throws SQLException {
    delete((long) oid);
  }

  /**
   * <p>This deletes a large object.</p>
   *
   * <p>It is identical to the delete method, and is supplied as the C API uses unlink.</p>
   *
   * @param oid describing object to delete
   * @throws SQLException on error
   */
  public void unlink(long oid) throws SQLException {
    delete(oid);
  }

  /**
   * This deletes a large object.
   *
   * @param oid describing object to delete
   * @throws SQLException on error
   * @deprecated As of 8.3, replaced by {@link #delete(long)}
   */
  @Deprecated
  public void delete(int oid) throws SQLException {
    delete((long) oid);
  }
}
