/*


    ========== licence begin GPL
    Copyright (C) 2002-2003 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end


*/


package com.sap.dbtech.jdbc;

import java.sql.*;
import java.util.*;
import com.sap.dbtech.jdbc.translators.*;
import com.sap.dbtech.jdbc.packet.*;
import com.sap.dbtech.vsp001.*;

/**
 *
 */
public class FetchInfo
{

    private ConnectionSapDB     connection;            // current connection
    private String              cursorName;            // cursor
    private DBTechTranslator[]  columnInfo;            // short info of all columns
    private int                 recordSize;            // physical row size
    private Hashtable           columnMapping = null;  // mapping from column names to short infos
    private String              _fetchparamstring;     // cache for fetch parameters
    private boolean             packetEncodingUnicode = false;
//    private String[]            columnNames;


    /**
     * Constructor. Creates a new fetch info for a specific
     * previously issued command that will produce a result set.
     * If <code>infos</code> and/or <code>columnNames</code> is
     * null, a describe on the result table (<code>cursorName</code>)
     * is executed to retrieve the information.
     * @param connection the connection.
     * @param cursorName the name of the result table.
     * @param infos the short infos of the rows in the result.
     * @param columnNames the names of the rows in the result.
     * @exception java.sql.SQLException if a database error occurs.
     */
    public FetchInfo(ConnectionSapDB connection,
                     String cursorName,
                     DBTechTranslator[] infos,
                     String[] columnNames,
                     boolean apacketEncodingUnicode)
        throws SQLException
    {
        this.connection=connection;
        this.cursorName=cursorName;
        this.packetEncodingUnicode = apacketEncodingUnicode;
        if(infos==null || columnNames==null) {
//            describe();
          this.columnInfo = null;
          return;
        } else {
            setMetaData(infos, columnNames);
        }
    }


    /**
     * Executes a describe operation on the result table.
     * @exception java.sql.SQLException if a database error occurs.
     */
    private void describe()
        throws SQLException
    {
        ConnectionSapDB c=this.connection;
        DBTechTranslator[] infos=null;
        String[] columnNames=null;

        RequestPacket request=c.getRequestPacket(this.packetEncodingUnicode);
        request.initDbsCommand(false, "DESCRIBE \""+this.cursorName+"\"", ResultSet.TYPE_FORWARD_ONLY);
        ReplyPacket reply=c.execute(request, this, ConnectionSapDB.GC_ALLOWED);
        PartEnumeration parts=reply.partEnumeration();
        while(parts.hasMoreElements()) {
            parts.nextElement();
            int partKind=parts.partKind();
            if(partKind==PartKind.Columnnames_C) {
                columnNames=reply.parseColumnNames();
            } else if(partKind==PartKind.Shortinfo_C) {
                infos=reply.parseShortFields(this.connection.isSpaceoptionSet,false, null, false);
	        } else if(partKind==PartKind.Vardata_Shortinfo_C) {
	            infos=reply.parseShortFields(this.connection.isSpaceoptionSet,false, null, true);
	        }
        }
        this.setMetaData(infos, columnNames);
    }

    /**
     *
     * @exception java.sql.SQLException The exception description.
     */
    private void setMetaData (
            DBTechTranslator [] info,
            String [] colName)
            throws SQLException
    {
        int colCount = info.length;
        DBTechTranslator currentInfo;
        int currentFieldEnd;

        this.recordSize = 0;

        if (colCount == colName.length) {
            this.columnInfo = info;
            for (int i = 0; i < colCount; ++i) {
                currentInfo = info [i];
                currentInfo.setColName (colName [i]);
                currentInfo.setColIndex (i);
                currentFieldEnd = currentInfo.getPhysicalLength () + currentInfo.getBufpos () - 1;
                this.recordSize = Math.max (this.recordSize, currentFieldEnd);
            }
        }
        else {
          int outputColCnt = 0;
          this.columnInfo = new DBTechTranslator [colName.length];
          for (int i = 0; i < colCount; ++i) {
            if (info [i].isOutput()){
                currentInfo = this.columnInfo[outputColCnt] = info [i];
                currentInfo.setColName (colName [outputColCnt]);
                currentInfo.setColIndex (outputColCnt++);
              currentFieldEnd = currentInfo.getPhysicalLength () + currentInfo.getBufpos () - 1;
              this.recordSize = Math.max (this.recordSize, currentFieldEnd);
            }
          }
        }
    }


    private void setColMapping ()
    {
        int colCnt = this.columnInfo.length;
        this.columnMapping = new java.util.Hashtable (2 * colCnt);
        DBTechTranslator currentInfo;

        for (int i = 0; i < colCnt; i++) {
          currentInfo = this.columnInfo [i];
          this.columnMapping.put(currentInfo.getColumnName(), currentInfo);
        }
    }

    /**
     * Gets the cursor name.
     */
    public String getCursorName()
    {
        return this.cursorName;
    }


    /**
     * Executes an FETCH NEXT.
     * @param fetchSize The number of records to include in this fetch.
     * @return the reply packet returned from the database.
     */
    public ReplyPacket executeFetchNext(int fetchSize)
        throws SQLException
    {
        String cmd="FETCH NEXT \"" + this.cursorName +
            "\" INTO " + getFetchParamString();
        ReplyPacket r = executeFetchCommand(cmd, fetchSize);
        return r;
    }

    /**
     * Executes an FETCH ABSOLUTE.
     * @param position The absolute position.
     * @param fetchSize The number of records to include in this fetch.
     * @return the reply packet returned from the database.
     */
    public ReplyPacket executeFetchAbsolute(long position, int fetchSize)
        throws SQLException
    {
        String cmd="FETCH ABSOLUTE " + position + " \"" + this.cursorName +
            "\" INTO " + getFetchParamString();
        ReplyPacket r = executeFetchCommand(cmd, fetchSize);
        return r;
    }

     /**
      * Executes an FETCH RELATIVE.
      * @param position The relative position.
      * @param fetchSize The number of records to include in this fetch.
      * @return the reply packet returned from the database.
      */
     public ReplyPacket executeFetchRelative(long position, int fetchSize)
         throws SQLException
     {
         String cmd="FETCH RELATIVE " + position + " \"" + this.cursorName +
             "\" INTO " + getFetchParamString();
         return executeFetchCommand(cmd, fetchSize);
     }

    /**
     * Executes an FETCH FIRST.
     * @param fetchSize The number of records to include in this fetch.
     * @return the reply packet returned from the database.
     */
    public ReplyPacket executeFetchFirst(int fetchSize)
        throws SQLException
    {
        String cmd="FETCH FIRST \"" + this.cursorName +
            "\" INTO " + getFetchParamString();
        return executeFetchCommand(cmd, fetchSize);
    }


    /**
     * Executes an FETCH LAST.
     * @param fetchSize The number of records to include in this fetch.
     * @return the reply packet returned from the database.
     */
    public ReplyPacket executeFetchLast(int fetchSize)
        throws SQLException
    {
        String cmd="FETCH LAST \"" + this.cursorName +
            "\" INTO " + getFetchParamString();
        return executeFetchCommand(cmd, fetchSize);
    }

    boolean marker=false;

    /**
     * Executes a given FETCH command.
     * @param command the command.
     * @param fetchSize The number of records to include in this fetch.
     * @return the reply packet returned from the database.
     */
    private ReplyPacket executeFetchCommand(String command, int fetchSize)
        throws SQLException
    {
        // System.err.println("FETCH COMMAND: " + command + " USING FETCH SIZE " + fetchSize);
        RequestPacket request=this.connection.getRequestPacket(this.packetEncodingUnicode);
        int currentSQLMode=request.switchSqlMode(SqlMode.Internal_C);
        request.initDbsCommand(this.connection.getAutoCommit(), command, ResultSet.TYPE_FORWARD_ONLY);
        if(fetchSize > 1) {
            request.setMassCommand();
        } else {
            fetchSize = 1;
        }
        request.addResultCount(fetchSize);
        try {
            return this.connection.execute(request, this, ConnectionSapDB.GC_DELAYED);
        } finally {
            request.switchSqlMode(currentSQLMode);
        }
    }


    /**
     * Retrieves a string that contains comma separated '?' for
     * use in the fetch command.
     * @return a string containing comma separated '?', in the number
     *          of columns returned.
     */
    private String getFetchParamString() throws SQLException
    {
        if (this.columnInfo == null){
          this.describe();
        }
        if(_fetchparamstring==null) {
            StringBuffer tmp=new StringBuffer("?");
            for(int i=1; i<this.columnInfo.length; ++i) {
                tmp.append(", ?");
            }
            _fetchparamstring=tmp.toString();
        }
        return _fetchparamstring;
    }

    /**
     * Retrieves the short infos of a named column.
     * @param name The name of the column. It is treated
     *   case-insensitive.
     * @return the short info of the column, or <code>null</code> if
     *   nothing was found.
     */
    public final DBTechTranslator getColumnInfo(String name)
    throws SQLException
    {
        if (this.columnInfo == null){
          this.describe();
        }
        if (this.columnMapping == null)
          this.setColMapping();
        Object o=columnMapping.get(name);
        if(o==null) {
            String uc=name.toUpperCase();
            o=columnMapping.get(uc);
            if(o!=null) {
                columnMapping.put(uc, o);
            }
        }
        return (DBTechTranslator)o;
    }

    /**
     * @deprecated
     */
    public DBTechTranslator[] getColInfo()
        throws SQLException
    {
        if (this.columnInfo == null){
          this.describe();
        }
        return this.columnInfo;
    }

    int numberOfColumns()
    {
        return this.columnInfo.length;
    }

    int getRecordSize()
    {
        return recordSize;
    }

}
