/*


    ========== 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.packet;

import java.io.*;
import java.sql.SQLException;
import java.util.Vector;

import com.sap.dbtech.util.*;
import com.sap.dbtech.vsp001.*;
import com.sap.dbtech.vsp00.*;
import com.sap.dbtech.jdbc.DBProcParameterInfo;
import com.sap.dbtech.jdbc.translators.*;
/**
 *
 */
public class ReplyPacket extends SQLPacket {
    private static PartNotFound thePartNotFound=new PartNotFound();

    private int   cachedResultCount=Integer.MIN_VALUE;
    private int   cachedPartCount=Integer.MIN_VALUE;
    private int[] partIndices;
    private int   partIdx=-1;
    private int   currentSegment;

    /**
     * ReplyPacket constructor comment.
     * @param rawPacket com.sap.dbtech.util.StructuredMem
     */
    public ReplyPacket(com.sap.dbtech.util.StructuredMem rawPacket) {
        super(rawPacket);
        this.segmOffs = Packet.Segment_O;
        this.currentSegment=1;
        clearPartCache();
    }

    /**
     *
     * @return DatabaseException
     */
    public com.sap.dbtech.jdbc.exceptions.DatabaseException createException () {
        String state = this.sqlState ();
        int rc = this.returnCode ();
        String errmsg = this.getErrorMsg ();
        int errorPos = this.errorPos ();

        if (rc == -8000) {
            errmsg=MessageTranslator.translate(MessageKey.ERROR_RESTARTREQUIRED);
        }
        return new com.sap.dbtech.jdbc.exceptions.DatabaseException (errmsg, state, rc, errorPos);
    }

    /**
     * Delivers the number of parts in the current segment.
     * <i>The number is cached for optimisation.</i>
     * @return The number of parts in the current segment.
     */
    public int partCount()
    {
        if(this.cachedPartCount==Integer.MIN_VALUE) {
            return this.cachedPartCount=super.partCount();
        } else {
            return this.cachedPartCount;
        }
    }


    /**
     *
     * @param stream PrintStream
     * @param maxBuf int
     */
    int dumpSegment (PrintStream stream, int maxBuf, int segmPos) {
        /*
         * dump segment header
         */
        stream.println ("   <SEGMENT ");
        stream.println ("    type=\"REPLY\"");
        stream.println ("    index=\""+this.getInt2 (segmPos + Segment.OwnIndex_O)+"\"");
        stream.println ("    offset=\""+this.getInt4 (segmPos + Segment.Offs_O)+"\"");
        stream.println ("    length=\""+this.getInt4 (segmPos + Segment.Len_O)+"\"");
        stream.println ("    number_of_parts=\""+this.getInt2 (segmPos + Segment.NoOfParts_O)+"\"");
//        StringUtil.fprintfs (stream, "    REPLY %d at %d for %d bytes, %d Parts\n",
//            new Object [] {
//            new Integer (this.getInt2 (segmPos + Segment.OwnIndex_O)),
//                new Integer (this.getInt4 (segmPos + Segment.Offs_O)),
//                new Integer (this.getInt4 (segmPos + Segment.Len_O)),
//                new Integer (this.getInt2 (segmPos + Segment.NoOfParts_O)),
//        });
        StringUtil.fprintfs (stream, "    <! %s (%d) Pos: %d  Warnings (%x, %x)  Function: %d >\n",
            new Object [] {
            this.getString (segmPos + Segment.SqlState_O, 5),
                new Integer (this.getInt2 (segmPos + Segment.Returncode_O)),
                new Integer (this.getInt4 (segmPos + Segment.ErrorPos_O)),
                new Integer (this.getInt2 (segmPos + Segment.ExternWarning_O)),
                new Integer (this.getInt2 (segmPos + Segment.InternWarning_O)),
                new Integer (this.getInt2 (segmPos + Segment.FunctionCode_O)),
        });
        /*
         * dump parts
         */
        int partCount = this.getInt2 (segmPos + Segment.NoOfParts_O);
        int pos = segmPos + Segment.Part_O;
        for (int i = 0; i < partCount; ++i) {
            pos += this.dumpPart (stream, maxBuf, pos);
        }
        stream.println ("   </SEGMENT> ");
        return this.getInt4 (segmPos + Segment.Len_O);
    }
    /**
     *
     * @return int
     */
    public int errorPos () {
        return this.mem.getInt4 (this.segmOffs + Segment.ErrorPos_O);
    }
    /**
     *
     * @return int
     * @param requestedKind int
     */
    public boolean existsPart (int requestedKind) {
        boolean result;

        try {
            this.findPart (requestedKind);
            result = true;
        }
        catch (PartNotFound exc) {
            result = false;
        }
        return result;
    }
    /**
     *
     * @return int
     * @param partKind int
     * @exception com.sap.dbtech.jdbc.packet.PartNotFound The exception description.
     */
    public int findPart (int requestedKind) throws PartNotFound
    {
        this.partOffs = -1;
        this.partIdx  = -1;
        int partsLeft=partCount();
        while(partsLeft > 0) {
            this.nextPart();
            --partsLeft;
            if(this.partKind() == requestedKind) {
                return this.partPos();
            }
        }
        throw thePartNotFound;
    }

    /**
     *
     * @return int
     */
    private int firstPart () {
        this.partIdx=0;
        this.partOffs=partIndices[partIdx];
        return this.partOffs;

//         int result;

//         if (this.partCount () > 0) {
//             result = this.segmOffs + Segment.Part_O;
//         }
//         else {
//             result = -1;
//         }
//         this.partOffs = result;
//         return result;
    }



    /**
     *
     * @return int
     */
    public int firstSegment () {
        int result;

        if (this.segmCount () > 0) {
            result = Packet.Segment_O;
        }
        else {
            result = -1;
        }
        this.segmOffs = result;
        this.currentSegment=1;
        clearPartCache();
        return result;
    }

    /**
     *
     * @return int
     */
    public int functionCode () {
        return this.mem.getInt2 (this.segmOffs + Segment.FunctionCode_O);
    }
    /**
     *
     * @return byte[]
     * @param pos int
     * @param len int
     */
    public byte [] getDataBytes (int pos, int len) {
        int defByte;
        byte [] result;

        defByte = this.mem.getInt1 (pos);
        if (defByte == 0xff) {
            return null;
        }
        result = this.mem.getBytes (pos + 1, len - 1);
        return result;
    }
    /**
     *
     * @return java.lang.String
     */
    public String getErrorMsg () {
        String result;
        int dataPos;
        int partLen;

        try {
            this.findPart (PartKind.Errortext_C);
            dataPos = this.getPartDataPos ();
            partLen = this.partLength ();
            result = this.getString (dataPos, partLen).trim ();
        }
        catch (PartNotFound exc) {
            result = MessageTranslator.translate(MessageKey.ERROR);
        }
        return result;
    }
    /**
     *
     * @return int
     */
    public int getPartDataPos () {
        return this.partOffs + Part.Data_O;
    }
    /**
     *
     * @return int
     */
    int nextPart () {
        partIdx++;
        return this.partOffs=partIndices[partIdx];

//         if (this.partOffs == -1) {
//             return this.firstPart ();
//         }
//         this.partOffs += this.aligned (this.partLength () + Part.Data_O);
//         return this.partOffs;
    }




    /**
     *
     * @return int
     */
    public int nextSegment () {
        if (this.segmCount() <= this.currentSegment++)
          return -1;
        this.segmOffs += this.segmLength ();
        clearPartCache();
        return this.segmOffs;
    }
    /**
     *
     * @return java.lang.String[]
     */
    public String[] parseColumnNames () {
        int columnCount;
        String [] result;
        int nameLen;
        int pos;

        columnCount = this.partArguments ();
        result = new String [columnCount];
        pos = this.getPartDataPos ();
        for (int i = 0; i < columnCount; ++i) {
            nameLen = this.getInt1 (pos);
            result [i] = this.getString (pos + 1, nameLen);
            pos += nameLen + 1;
        }
        return result;
    }
    /**
     *
     * @return byte[][]
     */
    public byte [][] parseLongDescriptors () {
        if (!this.existsPart (PartKind.Longdata_C)) {
            return null;
        }
        int argCount = this.partArguments ();
        byte [][] result = new byte [argCount] [];
        for (int i = 0; i < argCount; ++i) {
            int pos = (i * (LongDesc.Size_C + 1)) + 1;
            result [i] = this.getBytes (this.getPartDataPos () +  pos, LongDesc.Size_C);
        }
        return result;
    }
    /**
     * Extracts the short field info, and creates translator instances.
     * @param b
     * @return com.sap.dbtech.jdbc.translators.DBTechTranslator[]
     */
    public DBTechTranslator[] parseShortFields(boolean spaceoption,
            boolean isDBProcedure, DBProcParameterInfo[] procParameters, boolean isVardata)
            throws java.sql.SQLException {
        int columnCount;
        DBTechTranslator[] result;
        int pos;
        int mode;
        int ioType;
        int dataType;
        int frac;
        int len;
        int ioLen;
        int bufpos;
        boolean serial = false;
        boolean readOnly = false;
        
        columnCount = this.partArguments();
        result = new DBTechTranslator[columnCount];
        pos = this.getPartDataPos();
        //        byte [] info;
        for (int i = 0; i < columnCount; ++i) {
            //            info = this.getBytes(pos, 12);
            DBProcParameterInfo info = null;
            if (procParameters != null && procParameters.length > i) {
                info = procParameters[i];
            }
            mode = this.getInt1(pos + ParamInfo.Mode_O);
            ioType = this.getInt1(pos + ParamInfo.IoType_O);
            dataType = this.getInt1(pos + ParamInfo.DataType_O);
            frac = this.getInt1(pos + ParamInfo.Frac_O);
            len = this.getInt2(pos + ParamInfo.Length_O);
            ioLen = this.getInt2(pos + ParamInfo.InOutLen_O);
            if (isVardata && ioType == ParamInfo.Input_C){
               bufpos =  this.getInt2(pos + ParamInfo.Param_no_O); 
               readOnly = (this.getInt1(pos + ParamInfo.read_only_0 )!= 0);
               serial   = (this.getInt1(pos + ParamInfo.serial_0) != 0);
            }else{
                bufpos   =  this.getInt4(pos + ParamInfo.Bufpos_O);
            }  
            result[i] = this.getTranslator(mode, ioType, dataType, frac, len,
                    ioLen, bufpos, spaceoption, isDBProcedure, info,readOnly,serial);
            pos += ParamInfo.ParamInfo_END_O_C;
        }
        return result;
    }
    protected DBTechTranslator getTranslator
    (int mode,
        int ioType,
        int dataType,
        int frac,
        int len,
        int ioLen,
        int bufpos,
		boolean spaceoption,
		boolean isDBProcedure,
        DBProcParameterInfo procParamInfo,
        boolean readOnly, boolean autoIncre) throws java.sql.SQLException {
      return TranslatorFactory.create (mode, ioType, dataType,
                frac, len, ioLen, bufpos, spaceoption, isDBProcedure, procParamInfo, readOnly, autoIncre);
    }
    /**
     *
     * @return int
     */
    public int partArguments () {
        return this.mem.getInt2 (this.partOffs + Part.ArgCount_O);
    }
    /**
     *
     * @return com.sap.dbtech.jdbc.packet.PartEnumeration
     */
    public PartEnumeration partEnumeration () {
        this.partIdx = -1;
        this.partOffs = -1;
        return new PartEnumeration (this);
    }
    /**
     *
     * @return int
     */
    int partKind () {
        return this.mem.getInt1 (this.partOffs + Part.PartKind_O);
    }
    /**
     *
     * @return int
     */
    public int partLength () {
        return this.mem.getInt4 (this.partOffs + Part.BufLen_O);
    }


    /**
     *
     * @return int
     */
    int partPos () {
        return this.partOffs;
    }
    /**
     *
     * @return int
     * @param positionedAtPart boolean
     */
    public int resultCount (boolean positionedAtPart) throws java.sql.SQLException{
        if(this.cachedResultCount==Integer.MIN_VALUE) {
            if (!positionedAtPart) {
                try {
                    this.findPart (PartKind.Resultcount_C);
                }
                catch (PartNotFound exc) {
                    return this.cachedResultCount=-1;
                }
            }
            byte[] rawNumber = this.getDataBytes (this.partOffs + Part.Data_O, this.partLength ());
            return this.cachedResultCount = VDNNumber.number2int (rawNumber);
        } else {
            return this.cachedResultCount;
        }
    }

    /**
     *
     */
    public int
    getSessionID ()
    {
        int result;

        try {
            this.findPart (PartKind.SessionInfoReturned_C);
            result = this.mem.getInt4 (this.getPartDataPos () + 1);
        }
        catch (PartNotFound exc) {
            result = -1;
        }
        return result;
    }
    /**
    *
    */
   public DataPartVariable
   getVarDataPart ()
   {
       try {
           this.findPart (PartKind.Vardata_C);
           int argCount = 1;
           return new DataPartVariable( new StructuredBytes (
                                              this.mem.getBytes (this.getPartDataPos () , this.partLength())
                                              ), argCount);
       }
       catch (PartNotFound exc) {       }
       return null;
   }
        /**
     * Retrieves the major version number of the underlying database from order packet.
     * @return the underlying database's major version
     */
    public int
    getKernelMajorVersion ()
    {
        int result;

        try {
            this.findPart (PartKind.SessionInfoReturned_C);
            //offset 2200 taken from order interface manual
            result = Integer.parseInt (this.mem.getString (this.getPartDataPos () + 2200, 1));
        }
        catch (PartNotFound exc) {
            result = -1;
        }
        return result;
    }
    /**
     * Retrieves the minor version number of the underlying database from order packet.
     * @return the underlying database's major version
     */
    public int
    getKernelMinorVersion ()
    {
        int result;

        try {
            this.findPart (PartKind.SessionInfoReturned_C);
            //offset 2202 taken from order interface manual
            result = Integer.parseInt(this.mem.getString (this.getPartDataPos () + 2201, 2));
        }
        catch (PartNotFound exc) {
            result = -1;
        }
        return result;
    }
    /**
     * Retrieves the minor version number of the underlying database from order packet.
     * @return the underlying database's major version
     */
    public byte[]
    getFeatures ()
    {

        try {
            this.findPart (PartKind.Feature_C);
            return this.mem.getBytes (this.getPartDataPos () , this.partLength());
        }
        catch (PartNotFound exc) {
          return null;
        }
    }
    /**
     * Retrieves the correction level number of the underlying database from order packet.
     * @return the underlying database's major version
     */
    public int
    getKernelCorrectionLevel ()
    {
        int result;

        try {
            this.findPart (PartKind.SessionInfoReturned_C);
            //offset 2204 taken from order interface manual
            result = Integer.parseInt(this.mem.getString (this.getPartDataPos () + 2203, 2));
        }
        catch (PartNotFound exc) {
            result = -1;
        }
        return result;
    }
    /**
     *
     * @return int
     */
    public final int returnCode () {
        return this.mem.getInt2 (this.segmOffs + Segment.Returncode_O);
    }
    /**
     *
     * @return int
     */
    private int segmLength () {
        return this.mem.getInt4 (this.segmOffs + Segment.Len_O);
    }
    /**
     *
     * @return int
     */
    public String sqlState () {
        return this.mem.getString (this.segmOffs + Segment.SqlState_O, 5);
    }
    /**
     *
     * @return boolean
     */
    public boolean wasLastPart () {
        int partAttributes;
        boolean result;

        partAttributes = this.getInt1 (this.partOffs + Part.Attributes_O);
        result = (partAttributes & PartAttributes.LastPacket_E) != 0;
        return result;
    }
    /**
     *
     * @return int
     */
    public final int weakReturnCode () {
        int result = this.returnCode();
        if (result == 100) {
            switch (this.functionCode ()) {
                case FunctionCode.DBProcExecute_FC:
                case FunctionCode.DBProcWithResultSetExecute_FC:
                case FunctionCode.MFetchFirst_Fc:
                case FunctionCode.MFetchLast_Fc:
                case FunctionCode.MFetchNext_Fc:
                case FunctionCode.MFetchPrev_Fc:
                case FunctionCode.MFetchPos_Fc:
                case FunctionCode.MFetchSame_Fc:
                case FunctionCode.MFetchRelative_Fc:
                case FunctionCode.FetchFirst_Fc:
                case FunctionCode.FetchLast_Fc:
                case FunctionCode.FetchNext_Fc:
                case FunctionCode.FetchPrev_Fc:
                case FunctionCode.FetchPos_Fc:
                case FunctionCode.FetchSame_Fc:
                case FunctionCode.FetchRelative_Fc:
                /* keep result */
                    break;
                default:
                    result = 0;
                    break;
            }
        }
        return result;
    }

    private final void clearPartCache()
    {
        this.cachedResultCount=Integer.MIN_VALUE;
        this.cachedPartCount=Integer.MIN_VALUE;

        int pc= this.mem.getInt2 (this.segmOffs + Segment.NoOfParts_O);
        partIndices=new int[pc];
        int partofs=0;
        for(int i=0; i<pc; ++i) {
            if(i==0) {
                partofs=partIndices[i]=this.segmOffs + Segment.Part_O;
            } else {
                int partlen=this.mem.getInt4 (partofs + Part.BufLen_O);
                partofs = partIndices[i] = partofs +  this.aligned(partlen + Part.Data_O);
            }
        }
    }

    public StreamInfo[] parseStreamInfos()
    	throws SQLException
    {
        Vector v = new Vector();
        PartEnumeration e = partEnumeration();
        while(e.hasMoreElements()) {
            e.nextElement();
            if(e.partKind() == PartKind.AbapIStream_C) {
                int tabid = getInt4(getPartDataPos());
                short rowcount = (short) partArguments();
                v.addElement(new StreamInfo(true, tabid, rowcount));
            }
        }
        return (StreamInfo[]) v.toArray(new StreamInfo[v.size()]);
    }
    
    
        public int parseABAPTabIDForInput()
                throws SQLException
        {
                try {
                        findPart(PartKind.AbapIStream_C);
                } catch(PartNotFound pnf) {
                        throw new SQLException("Internal error: expected ABAP input stream part.");
                }
                return getInt4(getPartDataPos());
        }

        public int parseABAPTabIDForOutput()
                throws SQLException
        {
                try {
                        findPart(PartKind.AbapOStream_C);
                } catch(PartNotFound pnf) {
                        throw new SQLException("Internal error: expected ABAP output stream part.");
                }
                return getInt4(getPartDataPos());
        }

}
