/****************************************************************
 *								*
 *	Copyright 2001, 2009 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include <stdarg.h>
#include "gtm_string.h"

#include "gdsroot.h"
#include "gdsblk.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "stringpool.h"
#include <rtnhdr.h>
#include "mv_stent.h"
#include "fnname.h"
#include "op.h"
#include "gvsub2str.h"
#include "mvalconv.h"

GBLREF gv_key		*gv_currkey;
GBLREF spdesc		stringpool;
GBLREF mv_stent		*mv_chain;
GBLREF unsigned char	*msp, *stackwarn, *stacktop;

#ifdef  TEST_DOLLAR_NAME_GCOL
#define TEST_FAKE_STRINGPOOL_FULL	stringpool.free = stringpool.top /* force gcol at various stages of $NAME processing */
#else
#define TEST_FAKE_STRINGPOOL_FULL
#endif

#define COPY_ARG_TO_STP								\
{										\
	boolean_t	has_str_repsn;						\
										\
	has_str_repsn = MV_IS_STRING(arg) || !MV_DEFINED(arg);			\
	TEST_FAKE_STRINGPOOL_FULL;						\
	mval_lex(arg, &format_out);						\
	if (MV_IS_CANONICAL(arg))						\
	{ /*  mval_lex doesn't create string representation for canonical arg	\
	   * that already has a string representation. */			\
		assert(arg->str.len  == format_out.len );			\
		assert(arg->str.addr == format_out.addr);			\
		TEST_FAKE_STRINGPOOL_FULL;					\
		ENSURE_STP_FREE_SPACE(arg->str.len);				\
		memcpy(stringpool.free, arg->str.addr, arg->str.len);		\
			/* use arg 'coz gcol doesn't preserve format_out  */	\
	}									\
	if (has_str_repsn)							\
	{	/* mval_lex copies arg at stringpool.free and USUALLY leaves	\
		 * stringpool.free unchanged. Caller (us) has to update		\
		 * stringpool.free to keep dst protected. EXCEPT: MV_FORCE_STR	\
		 * in mval_lex creates string representation for canonical	\
		 * numbers that did not have string representation and updates	\
		 * stringpool.free as that representation "belongs" to the mval	\
		 * in question - no need to update stringpool.free for such	\
		 * cases. Lucky for us, it sits in the perfect place in the	\
		 * middle of our work has_str_repsn includes not defined check	\
		 * in case of noundef - if undef it comes back as empty string.	\
		 */								\
		stringpool.free += format_out.len;				\
	}									\
}										\

#define COPY_SUBSCRIPTS 								\
	for ( ; ; ) 									\
	{										\
		arg = va_arg(var, mval *);						\
		COPY_ARG_TO_STP;							\
		dst->str.len += format_out.len;						\
		depth_count--;								\
		if (0 == depth_count)							\
			break;								\
		*stringpool.free++ = ',';						\
		dst->str.len++;								\
	}

void op_fnname(UNIX_ONLY_COMMA(int sub_count) mval *finaldst, ...)
{
	int		space_needed;
	int 		depth_count, fnname_type;
	mval		*dst, *arg, *depth;
	VMS_ONLY(int	sub_count;)
	mstr		format_out;
	va_list		var;
	unsigned char	*sptr, *key_ptr, *key_top;

	error_def(ERR_MAXSTRLEN);
	error_def(ERR_GVNAKED);
	error_def(ERR_FNNAMENEG);
	error_def(ERR_STACKOFLOW);
	error_def(ERR_STACKCRIT);

	/* Implementation note: $NAME does not edit check the result, such as if the key size exceeds the maximum for a global.
	 * So, the result if used in other operations (such as SET, KILL) may generate run time errors (GVSUBOFLOW, etc) */

	VAR_START(var, finaldst);
	VMS_ONLY(va_count(sub_count);)
	assert(3 <= sub_count);
	fnname_type = va_arg(var, int);
	depth = va_arg(var, mval *); /* if second arg to $NAME not specified, compiler sets depth_count to MAXPOSINT4 */
	depth_count = MV_FORCE_INT(depth);
	sub_count -=3;

	if (depth_count < 0)
	{
		va_end(var);
		rts_error(VARLSTCNT(1) ERR_FNNAMENEG);
	}

	/* Note about garbage collection : *dst and all possible *arg's in this function are anchored in the stringpool chains
	 * and preserved during garbage collection (run time argument mvals provided by compiler). So, if we maintain dst->str.len
	 * as we copy stuff to the stringpool, we are protected from mval_lex->stp_gcol shuffling strings around. Since the
	 * temporary allocation algorithm used by the compiler may re-use temporaries, it is possible that one of the subscript
	 * arguments is the destination for the result of $NAME. We have to assign the result mval as the last step, only after
	 * all arguments have been processed. We create a temporary place-holder for the result on the M stack so that the result
	 * is also on the stringpool chain and preserved during garbage collection.
	 * e.g. S X(1)=$NAME($J("hello",100))
	 * The result of $J is a temporary that is also an argument to $NAME. This temporary also happens to be the temporary to
	 * hold the result of $NAME.
	 */
	PUSH_MV_STENT(MVST_MVAL); /* create a temporary on M stack */
	dst = &mv_chain->mv_st_cont.mvs_mval;
	dst->mvtype = MV_STR;
	dst->str.len = 0;
	if (fnname_type == FNNAKGBL)
	{
		if (!gv_currkey || gv_currkey->prev == 0)
		{
			va_end(var);
			rts_error(VARLSTCNT(1) ERR_GVNAKED);
		}
		/* Reserve enough space for naked reference. Include space for ^() and a maximum of sub_count ',' separators for
		 * subscripts specified as arguments to $NAME() in addition to those in the naked reference */
		space_needed = (int)((STR_LIT_LEN("^()") + ZWR_EXP_RATIO(MAX_KEY_SZ) + sub_count));
		TEST_FAKE_STRINGPOOL_FULL;
		ENSURE_STP_FREE_SPACE(space_needed);
		sptr = stringpool.free;
		*sptr++ = '^';
		key_ptr = (unsigned char *)&gv_currkey->base[0];
		key_top = (unsigned char *)&gv_currkey->base[gv_currkey->prev];
		for ( ; (*sptr = *key_ptr++); sptr++)
			;
		if (0 != depth_count)
		{
			*sptr++ = '(';
			if (key_ptr < key_top)
			{
				for ( ; ; )
				{
					sptr = gvsub2str(key_ptr, sptr, TRUE); /* gvsub2str assumes enough buffer available */
					while (*key_ptr++)
						;
					if (depth_count != MAXPOSINT4) /* although this may not make a difference in reality, */
						depth_count--;	       /* do not disturb depth_count (used later) if default  */
					*sptr++ = ',';
					if (0 == depth_count       /* fewer subscripts requested than in naked reference */
					    || key_ptr >= key_top) /* more subscripts requested than in naked reference */
						break;
				}
			}
			/* Naked reference copied, now copy remaining subscripts. From this point on, maintain dst to protect
			 * against potential string shuffling by mval_lex->stp_gcol
			 */
			dst->str.len = INTCAST(sptr - stringpool.free);
			dst->str.addr = (char *)stringpool.free;
			assert(dst->str.len < space_needed);
			stringpool.free = sptr;
			depth_count = ((sub_count < depth_count) ? sub_count : depth_count);
			if (0 != depth_count)
			{
				COPY_SUBSCRIPTS;
			} else
			{ /* take off extra , if depth doesn't go into new subs */
				stringpool.free--;
				dst->str.len--;
			}
			*stringpool.free++ = ')';
			dst->str.len++;
		} else
		{ /* naked reference, zero depth => result is just the global name */
			dst->str.len = INTCAST(sptr - stringpool.free);
			dst->str.addr = (char *)stringpool.free;
			assert(dst->str.len < space_needed);
			stringpool.free = sptr;
		}
	} else
	{
		space_needed = (int)(STR_LIT_LEN("^[,]()") + MAX_MIDENT_LEN + sub_count - 1); 	/* ^[,]GLVN(max of sub_count-1 *
											 * subscript separator commas) */
		TEST_FAKE_STRINGPOOL_FULL;
		/* We don't account for subscripts here as they are processed by mval_lex which reserves space if necessary */
		ENSURE_STP_FREE_SPACE(space_needed);
		dst->str.addr = (char *)stringpool.free;
		if (fnname_type & FNGBL)
		{
			*stringpool.free++ = '^';
			dst->str.len++;
		}
		if (fnname_type & (FNEXTGBL1 | FNEXTGBL2))
		{
			*stringpool.free++ = ((fnname_type & FNVBAR) ? '|' : '[');
			dst->str.len++;
			arg = va_arg(var, mval *);
			COPY_ARG_TO_STP;
			dst->str.len += format_out.len;
			sub_count--;
			if (fnname_type & FNEXTGBL2)
			{
				*stringpool.free++ = ',';
				dst->str.len++;
				arg = va_arg(var, mval *);
				COPY_ARG_TO_STP;
				dst->str.len += format_out.len;
				sub_count--;
			}
			*stringpool.free++ = ((fnname_type & FNVBAR) ? '|' : ']');
			dst->str.len++;
		}
		arg = va_arg(var, mval *);
		assert(MV_IS_STRING(arg) && (arg->str.len <= MAX_MIDENT_LEN));
		memcpy(stringpool.free, arg->str.addr, arg->str.len);
		stringpool.free += arg->str.len;
		dst->str.len += arg->str.len;
		sub_count--;
		depth_count = ((sub_count < depth_count) ? sub_count : depth_count);
		if (0 != depth_count)
		{
			*stringpool.free++ = '(';
			dst->str.len++;
			COPY_SUBSCRIPTS;
			*stringpool.free++ = ')';
			dst->str.len++;
		}
	}
	va_end(var);
	if (MAX_STRLEN < dst->str.len)
		rts_error(VARLSTCNT(1) ERR_MAXSTRLEN);
	*finaldst = *dst;
	POP_MV_STENT(); /* don't need no temporary no more */
	return;
}
