/*

   Derby - Class org.apache.derby.impl.sql.GenericParameter

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to you under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package org.apache.derby.impl.sql;

import org.apache.derby.iapi.reference.SQLState;

import org.apache.derby.iapi.error.StandardException;

import org.apache.derby.iapi.types.DataTypeDescriptor;
import org.apache.derby.iapi.types.DataValueDescriptor;
import org.apache.derby.iapi.types.TypeId;

import java.sql.ParameterMetaData;
import java.sql.Types;

/**
 * A parameter.  Originally lifted from ParameterValueSet.
 *
 */
final class GenericParameter
{

	// These defaults match the Network Server/ JCC max precision and
	// The JCC "guessed" scale. They are used as the defaults for 
	// Decimal out params.
	private static int DECIMAL_PARAMETER_DEFAULT_PRECISION = 31;
	private static int DECIMAL_PARAMETER_DEFAULT_SCALE = 15;


	/*
	** The parameter set we are part of
	*/
	private final GenericParameterValueSet		pvs;

	/**
	** Our value
	*/
	private DataValueDescriptor				value;

	/**
		Compile time JDBC type identifier.
	*/
	int								jdbcTypeId;

	/**
		Compile time Java class name.
	*/
	String							declaredClassName;

	/**
		Mode of the parameter, from ParameterMetaData
	*/
	short							parameterMode;

	/*
	** If we are set
	*/
	boolean							isSet;

	/*
	** Output parameter values
 	*/
	private final boolean					isReturnOutputParameter;

	/**
		Type that has been registered.
	*/
	int	registerOutType = Types.NULL;
	/**
		Scale that has been registered.
	*/
	int registerOutScale = -1;

	/**
	 * When a decimal output parameter is registered we give it a 
	 * precision
	 */

	int registerOutPrecision = -1;

	/**
	 * Constructor for a Parameter
	 *
	 * @param pvs the parameter set that this is part of
	 * @param isReturnOutputParameter true if this is a return output parameter
	 */
	GenericParameter
	(
		GenericParameterValueSet	pvs,
		boolean						isReturnOutputParameter
	)
	{
		this.pvs = pvs;
		parameterMode = (this.isReturnOutputParameter = isReturnOutputParameter)
            ? (short) (ParameterMetaData.parameterModeOut) : (short) (ParameterMetaData.parameterModeIn);
	}

	/**
	 * Clone myself.  It is a shallow copy for everything but
	 * the underlying data wrapper and its value -- e.g. for
	 * everything but the underlying SQLInt and its int.
	 *
	 * @param pvs the parameter value set
	 *
	 * @return a new generic parameter.
	 */
	public GenericParameter getClone(GenericParameterValueSet pvs)
	{
		GenericParameter gpClone = new GenericParameter(pvs, isReturnOutputParameter);
        gpClone.initialize(this.getValue().cloneValue(false),
                           jdbcTypeId, declaredClassName);
		gpClone.isSet = true;

		return gpClone;
	}

	/**
	 * Set the DataValueDescriptor and type information for this parameter
	 *
	 */
	void initialize(DataValueDescriptor value, int jdbcTypeId, String className)
	{
		this.value = value;
		this.jdbcTypeId = jdbcTypeId;
		this.declaredClassName = className;
	}


	/**
	 * Clear the parameter, unless it is a return
	 * output parameter
	 */
	void clear()
	{
		isSet = false;
	}


	/**
	 * Get the parameter value.  Doesn't check to
	 * see if it has been initialized or not.
	 *
	 * @return the parameter value, may return null
	 */
	DataValueDescriptor getValue()
	{
		return value;
	}


	//////////////////////////////////////////////////////////////////
	//
	// CALLABLE STATEMENT
	//
	//////////////////////////////////////////////////////////////////

	/**
	 * Mark the parameter as an output parameter.
	 *
	 * @param sqlType	A type from java.sql.Types
	 * @param scale		scale, -1 if no scale arg
	 *
	 * @exception StandardException on error
	 */
	void setOutParameter(int sqlType, int scale)
		throws StandardException
	{
		// fast case duplicate registrations.
		if (registerOutType == sqlType) {
			if (scale == registerOutScale)
				return;
		}

		switch (parameterMode) {
        case (ParameterMetaData.parameterModeIn):
        case (ParameterMetaData.parameterModeUnknown):
		default:
			throw StandardException.newException(SQLState.LANG_NOT_OUT_PARAM, getJDBCParameterNumberStr());

        case (ParameterMetaData.parameterModeInOut):
        case (ParameterMetaData.parameterModeOut):
			// Declared/Java procedure parameter.
			if (!DataTypeDescriptor.isJDBCTypeEquivalent(jdbcTypeId, sqlType))
				throw throwInvalidOutParamMap(sqlType);
			break;

		}

		registerOutType = sqlType;
		
	}

	private StandardException throwInvalidOutParamMap(int sqlType) {

		//TypeId typeId = TypeId.getBuiltInTypeId(sqlType);
		// String sqlTypeName = typeId == null ? "OTHER" : typeId.getSQLTypeName();


		String jdbcTypesName = org.apache.derby.impl.jdbc.Util.typeName(sqlType);

		TypeId typeId = TypeId.getBuiltInTypeId(jdbcTypeId);
		String thisTypeName = typeId == null ? declaredClassName : typeId.getSQLTypeName();
				
		StandardException e = StandardException.newException(SQLState.LANG_INVALID_OUT_PARAM_MAP,
					getJDBCParameterNumberStr(),
					jdbcTypesName, thisTypeName);

		return e;
	}



	/**
	 * Validate the parameters.  This is done for situations where
	 * we cannot validate everything in the setXXX() calls.  In
	 * particular, before we do an execute() on a CallableStatement,
	 * we need to go through the parameters and make sure that
	 * all parameters are set up properly.  The motivator for this
	 * is that setXXX() can be called either before or after
	 * registerOutputParamter(), we cannot be sure we have the types
	 * correct until we get to execute().
	 *
	 * @exception StandardException if the parameters aren't valid
	 */
	void validate() throws StandardException
	{
		switch (parameterMode) {
        case (ParameterMetaData.parameterModeUnknown):
			break;
        case (ParameterMetaData.parameterModeIn):
			break;
        case (ParameterMetaData.parameterModeInOut):
        case (ParameterMetaData.parameterModeOut):
			if (registerOutType == Types.NULL) {
				throw StandardException.newException(SQLState.NEED_TO_REGISTER_PARAM,
					getJDBCParameterNumberStr(),
					 org.apache.derby.catalog.types.RoutineAliasInfo.parameterMode(parameterMode));
			}
			break;
		}
	}

	/**
	 * Return the scale of the parameter.
	 *
	 * @return scale
	 */
	int getScale()
	{
		//when the user doesn't pass any scale, the registerOutScale gets set to -1
		return (registerOutScale == -1 ? 0 : registerOutScale);
	}


	int getPrecision()
	{
		return registerOutPrecision;
		
	}

	////////////////////////////////////////////////////
	//
	// CLASS IMPLEMENTATION
	//
	////////////////////////////////////////////////////

	/**
	 * get string for param number
	 */
	String getJDBCParameterNumberStr()
	{
		return Integer.toString(pvs.getParameterNumber(this));
	}

	public String toString()
	{
		/* This method is used for debugging.
		 * It is called when derby.language.logStatementText=true,
		 * so there is no check of SanityManager.DEBUG.
		 * Anyway, we need to call value.getString() instead of
		 * value.toString() because the user may have done a
		 * a setStream() on the parameter.  (toString() could get
		 * an assertion failure in that case as it would be in an
		 * unexpected state since this is a very weird codepath.)
		 * getString() can throw an exception which we eat and
		 * and reflect in the returned string.
		 */
		if (value == null)
		{
			return "null";
		}
		else
		{
			try
			{
				return value.getTraceString();
			}
			catch (StandardException se)
			{
				return "unexpected exception from getTraceString() - " + se;
			}
		}
	}
}
