/*-------------------------------------------------------------------------
*
* Copyright (c) 2004-2005, PostgreSQL Global Development Group
* Copyright (c) 2004, Open Cloud Limited.
*
* IDENTIFICATION
*   $PostgreSQL: pgjdbc/org.postgresforest.core/v3/SimpleParameterList.java,v 1.10 2005/07/08 17:38:29 davec Exp $
*
*-------------------------------------------------------------------------
*/
package org.postgresforest.vm.core;

import java.io.InputStream;
import java.sql.SQLException;
import java.util.Arrays;

import org.postgresforest.core.Oid;
import org.postgresforest.core.ParameterList;
import org.postgresforest.util.GT;
import org.postgresforest.util.PSQLException;
import org.postgresforest.util.PSQLState;
import org.postgresforest.util.StreamWrapper;

/**
 * Parameter list for a single-statement V3 query.
 *
 * @author Oliver Jowett (oliver@opencloud.com)
 */
public class SimpleParameterList implements ParameterList {
    
    private final static int IN=1;
    private final static int OUT=2;
    private final static int INOUT=3;
    // this is here to avoid creating objects for copy
    // copy will simply discard them
    
    
    
    SimpleParameterList(int paramCount) {
    	if( paramCount < 0 )
    		paramCount = 0;
    	this.paramValues = new Object[paramCount];
        this.paramTypes = new int[paramCount];
        this.direction = new int[paramCount];
    }
        
    
    public void registerOutParameter( int index, int sqlType ) throws SQLException
    {
        if (index < 1 || index > paramValues.length)
            throw new PSQLException(GT.tr("The column index is out of range: {0}, number of columns: {1}.", new Object[]{new Integer(index), new Integer(paramValues.length)}), PSQLState.INVALID_PARAMETER_VALUE );

        direction[index-1] |= OUT;
    }

    private void bind(int index, Object value, int oid) throws SQLException {
        if (index < 1 || index > paramValues.length)
            throw new PSQLException(GT.tr("The column index is out of range: {0}, number of columns: {1}.", new Object[]{new Integer(index), new Integer(paramValues.length)}), PSQLState.INVALID_PARAMETER_VALUE );

        --index;

        paramValues[index] = value ;
        direction[index] |= IN;
        
        // If we are setting something to null, don't overwrite our existing type
        // for it.  We don't need the correct type info to send NULL and we
        // don't want to overwrite and require a reparse.
        if (oid == Oid.INVALID && paramTypes[index] != Oid.INVALID)
            return;

        paramTypes[index] = oid;
    }

    public int getParameterCount()
    {
        return paramValues.length;
    }

    public int getInParameterCount() 
    {
        int count=0;
        for( int i=0; i< paramTypes.length;i++)
        {
            if (direction[i] != OUT )
            {
                count++;
            }
        }
        return count;
    }

    public int getOutParameterCount()
    {
        int count=0;
        for( int i=paramTypes.length; --i >= 0;)
        {
            if ((direction[i] & OUT) == OUT )
            {
                count++;
            }
        }
        // Every function has at least one output.
        if (count == 0)
            count = 1;
        return count;

    }

    public void setIntParameter(int index, int value) throws SQLException {
        bind(index, new Integer(value), Oid.INT4);
    }

    public void setLiteralParameter(int index, String value, int oid) throws SQLException {
        bind(index, value, oid);
    }

    public void setStringParameter(int index, String value, int oid) throws SQLException {
        bind(index, value, oid);
    }

    public void setBytea(int index, byte[] data, int offset, int length) throws SQLException {
        bind(index, new StreamWrapper(data, offset, length), Oid.BYTEA);
    }

    public void setBytea(int index, InputStream stream, int length) throws SQLException {
        bind(index, new StreamWrapper(stream, length), Oid.BYTEA);
    }

    public void setNull(int index, int oid) throws SQLException {
        bind(index, NULL_OBJECT, oid);
    }

    public String toString(int index) {
        --index;
        if (paramValues[index] == null)
            return "?";
        else if (paramValues[index] == NULL_OBJECT)
            return "NULL";
        else
            return paramValues[index].toString();
    }



    public int[] getTypeOIDs() {
        return paramTypes;
    }

    //
    // Package-private V3 accessors
    //

    int getTypeOID(int index) {
        if (direction[index-1] == OUT)
        {
            paramTypes[index-1] = Oid.VOID;
            paramValues[index-1] = "null";
        }
        	
        return paramTypes[index-1];
    }

    boolean hasUnresolvedTypes() {
        for (int i=0; i< paramTypes.length; i++) {
            if (paramTypes[i] == Oid.INVALID)
                return true;
        }
        return false;
    }


    boolean isNull(int index) {
        return (paramValues[index-1] == NULL_OBJECT);
    }

    boolean isBinary(int index) {
        // Currently, only StreamWrapper uses the binary parameter form.
        return (paramValues[index-1] instanceof StreamWrapper);
    }

    
    
    public ParameterList copy() {
        SimpleParameterList newCopy = new SimpleParameterList(paramValues.length);
        System.arraycopy(paramValues, 0, newCopy.paramValues, 0, paramValues.length);
        System.arraycopy(paramTypes, 0, newCopy.paramTypes, 0, paramTypes.length);
        System.arraycopy(direction, 0, newCopy.direction, 0, direction.length);
        return newCopy;
    }

    
    public ParameterList copyTo(ParameterList distParamList) throws SQLException{

    	distParamList.clear();
    	
    	for (int i = 0; i < paramValues.length; i++) {
    		int oidType = paramTypes[i];
    		int paramIndex = i + 1;
    		
    		
			if(paramValues[i] == NULL_OBJECT){
			
				distParamList.setNull(paramIndex,paramTypes[i]);
			
			}else if( paramValues[i] instanceof StreamWrapper  ){
				
				StreamWrapper streamWrapper = (StreamWrapper)paramValues[i];
				byte[] bytes = streamWrapper.getBytes();
				if (bytes == null){
					distParamList.setBytea(paramIndex, streamWrapper.getStream(), streamWrapper.getLength());
				}else{
					distParamList.setBytea(paramIndex, bytes, streamWrapper.getOffset(), streamWrapper.getLength());
				}

			}else if(paramValues[i] instanceof Integer && oidType == Oid.INT4){
				
				Integer intValue = (Integer)paramValues[i];
				distParamList.setIntParameter(paramIndex,intValue.intValue());
				
			}else if(paramValues[i] instanceof String &&
					  (oidType == Oid.INT2 ||
					   oidType == Oid.INT4 ||
					   oidType == Oid.FLOAT8 ||
					   oidType == Oid.NUMERIC ||
					   oidType == Oid.INT8  )                ) {

				distParamList.setLiteralParameter(paramIndex, (String)paramValues[i], oidType);
			}else{
				
				distParamList.setStringParameter(paramIndex,(String)paramValues[i],oidType);
				
			}
			
			
		}
        return distParamList;
    }

    
    public void clear() {
        Arrays.fill(paramValues, null);
        Arrays.fill(paramTypes, 0);
        Arrays.fill(direction, 0);
    }


    private final Object[] paramValues;
    private final int[] paramTypes;
    private final int[] direction;
    
    /**
     * Marker object representing NULL; this distinguishes
     * "parameter never set" from "parameter set to null".
     */
    private final static Object NULL_OBJECT = new Object();

    
}

