/****************************************************************
 *								*
 * Copyright (c) 2001-2018 Fidelity National Information	*
 * Services, Inc. and/or its subsidiaries. All rights reserved.	*
 *								*
 *	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 "gtm_stdio.h"

#include "lv_val.h"
#include "gdsroot.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "zwrite.h"
#include "mlkdef.h"
#include "zshow.h"
#include "collseq.h"
#include "stringpool.h"
#include "op.h"
#include "outofband.h"
#include "do_xform.h"
#include "numcmp.h"
#include "patcode.h"
#include "mvalconv.h"
#include "follow.h"
#include "gtm_string.h"
#include "alias.h"
#include "promodemo.h"	/* for "demote" prototype used in LV_NODE_GET_KEY */
#include "jobsp.h"

#define eb_less(u, v)    (numcmp(u, v) < 0)

#define COMMON_STR_PROCESSING(NODE)										\
{														\
	mstr		key_mstr;										\
	mval 		tmp_sbs;										\
														\
	assert(MV_STR & mv.mvtype);										\
	if (TREF(local_collseq))										\
	{													\
		key_mstr = mv.str;										\
		mv.str.len = 0;	/* protect from "stp_gcol", if zwr_sub->subsc_list[n].actual points to mv */	\
		ALLOC_XFORM_BUFF(key_mstr.len);									\
		tmp_sbs.mvtype = MV_STR;									\
		tmp_sbs.str.len = TREF(max_lcl_coll_xform_bufsiz);						\
		assert(NULL != TREF(lcl_coll_xform_buff));							\
		tmp_sbs.str.addr = TREF(lcl_coll_xform_buff);							\
		do_xform(TREF(local_collseq), XBACK, &key_mstr, &tmp_sbs.str, &length);				\
		tmp_sbs.str.len = length;									\
		s2pool(&(tmp_sbs.str));										\
		mv.str = tmp_sbs.str;										\
	}													\
	do_lev = TRUE;												\
	if (n < lvzwrite_block->subsc_count)									\
	{													\
		if (zwr_sub->subsc_list[n].subsc_type == ZWRITE_PATTERN)					\
		{												\
			if (!do_pattern(&mv, zwr_sub->subsc_list[n].first))					\
				do_lev = FALSE;									\
		} else  if (zwr_sub->subsc_list[n].subsc_type != ZWRITE_ALL)					\
		{												\
			if (zwr_sub->subsc_list[n].first)							\
			{											\
				if (!MV_IS_CANONICAL(zwr_sub->subsc_list[n].first) &&				\
						(!follow(&mv, zwr_sub->subsc_list[n].first) &&			\
						(mv.str.len != zwr_sub->subsc_list[n].first->str.len ||		\
					  	memcmp(mv.str.addr, zwr_sub->subsc_list[n].first->str.addr,	\
						mv.str.len))))							\
					do_lev = FALSE;								\
			}											\
			if (do_lev && zwr_sub->subsc_list[n].second)						\
			{											\
				if (MV_IS_CANONICAL(zwr_sub->subsc_list[n].second) ||				\
						(!follow(zwr_sub->subsc_list[n].second, &mv) &&			\
						(mv.str.len != zwr_sub->subsc_list[n].second->str.len ||	\
					  	memcmp(mv.str.addr,						\
						zwr_sub->subsc_list[n].second->str.addr,			\
						mv.str.len))))							\
					do_lev = FALSE;								\
			}											\
		}												\
	}													\
	if (do_lev)												\
		lvzwr_var((lv_val *)NODE, n + 1);								\
}

#define COMMON_NUMERIC_PROCESSING(NODE)								\
{												\
	do_lev = TRUE;										\
	if (n < lvzwrite_block->subsc_count)							\
	{											\
		if (zwr_sub->subsc_list[n].subsc_type == ZWRITE_PATTERN)			\
		{										\
			if (!do_pattern(&mv, zwr_sub->subsc_list[n].first))			\
				do_lev = FALSE;							\
		} else  if (zwr_sub->subsc_list[n].subsc_type != ZWRITE_ALL)			\
		{										\
			if (zwr_sub->subsc_list[n].first)					\
			{									\
		 		if (!MV_IS_CANONICAL(zwr_sub->subsc_list[n].first)		\
						|| eb_less(&mv, zwr_sub->subsc_list[n].first))	\
					do_lev = FALSE;						\
			}									\
			if (do_lev && zwr_sub->subsc_list[n].second)				\
			{									\
				if (MV_IS_CANONICAL(zwr_sub->subsc_list[n].second)		\
						&& eb_less(zwr_sub->subsc_list[n].second, &mv))	\
					do_lev = FALSE;						\
			}									\
		}										\
	}											\
	if (do_lev)										\
		lvzwr_var((lv_val *)NODE, n + 1);						\
}

GBLREF lvzwrite_datablk	*lvzwrite_block;
GBLREF int4		outofband;
GBLREF zshow_out	*zwr_output;
GBLREF int		merge_args;
GBLREF bool		undef_inhibit;
GBLREF zwr_hash_table	*zwrhtab;			/* How we track aliases during zwrites */

LITREF	mval		literal_null;

error_def(ERR_UNDEF);

/* lv subscript usage notes:
 *  1. The sub field in lvzwrite_datablk is an array allocated at MAX_LVSUBSCRIPTS.
 *  2. The subscripts that appear at any given time are those for the current node being processed.
 *  3. Nodes are setup by lvzwr_arg().
 *
 * Example - take the following nodes:
 *   A(1,1)=10
 *   A(1,2)=20
 *
 * The simplified processing that occurs is as follows:
 *  1. lvzwr_fini() sets curr_name which is the base var name (A)
 *  2. First level lvzwr_var is called with level (aka n) == 0
 *  3. Since A has no value, nothing is printed. Notices that there are children so lvzwr_arg()
 *     is called recursively with level 1.
 *  4. Sets up the level 1 subscript (key = 1).
 *  5. Since A(1) has no value, nothing is printed. Notices that there are children so lvzwr_arg()
 *     is called recursively withe level 2.
 *  6. Sets up the level 2 subscript (key = 1).
 *  7. A(1,1) does have a value so lvzwr_out() is called to print the current key (from these
 *     subscripts) and its value.
 *  8. No more subscripts at this level so pops back to level 1.
 *  9. There is another child at this level so calls lvzwr_arg() recursively with level 2.
 * 10. Replaces the level 2 subscript with the new key value (key = 2).
 * 11. A(1,2) does have a value so lvzwr_out() is called to print the current key.
 * 12. no more children at any level so everything pops back.
 */
void lvzwr_var(lv_val *lv, int4 n)
{
	mval		mv;
	int             length;
	lv_val		*var;
	char		*top;
	int4		i;
	boolean_t	do_lev, verify_hash_add, htent_added, value_printed_pending;
	zwr_sub_lst	*zwr_sub;
	ht_ent_addr	*tabent_addr;
	zwr_alias_var	*zav, *newzav;
	lvTree		*lvt;
	lvTreeNode	*node, *nullsubsnode, *parent;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	assert(lvzwrite_block);
	if (lv == zwr_output->out_var.lv.child)
		return;
	if (outofband)
	{
		assert(TREF(in_zwrite));	/* in_zwrite indicates we are properly set up for a zwrite and should be cleared */
		TREF(in_zwrite) = FALSE;	/* 	here along with the below 2 because outofband_action may not return */
		lvzwrite_block->curr_subsc = lvzwrite_block->subsc_count = 0;
		outofband_action(FALSE);
		TREF(in_zwrite) = TRUE;		/* in case we come back because the outofband_action turns out to be info */
	}
	lvzwrite_block->curr_subsc = n;
	zwr_sub = (zwr_sub_lst *)lvzwrite_block->sub;
	zwr_sub->subsc_list[n].actual = (mval *)NULL;
	/* Before we process this var, there are some special cases to check for first when
	 * this is a base var (0 == lvzwrite_block->subsc_count) and the var is an alias.
	 *
	 * 1. Check if we have seen it before (the lvval is in the zwr_alias_var hash table), then we
	 *    need to process this var with lvzwr_out NOW and we will only be processing the base
	 *    var, not any of the subscripts. This is because all those subscripts (and the value
	 *    of the base var itself) have been dealt with previously when we first saw this
	 *    lvval. So in that case, call lvzwr_out() to output the association after which we are
	 *    done with this var.
	 * 2. If we haven't seen it before, set a flag so we verify if the base var gets processed by
	 *    lvzwr_out or not (i.e. whether it has a value and the "subscript" or lack there of is
	 *    either wildcarded or whatever so that it actually gets dumped by lvzwr_out (see conditions
	 *    below). If not, then *we* need to add the lvval to the hash table to signify we have seen
	 *    it before so the proper associations to this alias var can be printed at a later time
	 *    when/if they are encountered.
	 */
	verify_hash_add = FALSE;	/* By default we don't need to verify add */
	value_printed_pending = FALSE;	/* Force the "value_printed" flag on if TRUE */
	zav = NULL;
	if (!merge_args && LV_IS_BASE_VAR(lv) && IS_ALIASLV(lv))
	{
		assert(0 == n);	/* Verify base var lv_val */
		if (tabent_addr = (ht_ent_addr *)lookup_hashtab_addr(&zwrhtab->h_zwrtab, (char **)&lv))
		{	/* We've seen it before but check if it was actually printed at that point */
			zav = (zwr_alias_var *)tabent_addr->value;
			assert(zav);
			if (zav->value_printed)
			{
				lvzwr_out(lv);
				ZWRITE_OUTPUT_HOOK();
				lvzwrite_block->curr_subsc = lvzwrite_block->subsc_count = 0;
				return;
			} else
				value_printed_pending = TRUE;	/* We will set value_printed flag true later */
		} else
			verify_hash_add = TRUE;
	}
	if ((0 == lvzwrite_block->subsc_count) && (0 == n))
		zwr_sub->subsc_list[n].subsc_type = ZWRITE_ASTERISK;
	if (LV_IS_VAL_DEFINED(lv)
	    && (!lvzwrite_block->subsc_count || ((0 == n) && ZWRITE_ASTERISK == zwr_sub->subsc_list[n].subsc_type)
		|| ((0 != n) && !(lvzwrite_block->mask >> n))))
	{	/* Print value for *this* node  */
		lvzwr_out(lv);
		ZWRITE_OUTPUT_HOOK();
	}
	if (verify_hash_add && !lvzwrite_block->zav_added)
	{	/* lvzwr_out processing didn't add a zav for this var. Take care of that now so we
		 * recognize it as a "dealt with" alias when/if it is encountered later.
		 */
		newzav = als_getzavslot();
		newzav->zwr_var = *lvzwrite_block->curr_name;
		newzav->value_printed = TRUE;
		htent_added = add_hashtab_addr(&zwrhtab->h_zwrtab, (char **)&lv, newzav, &tabent_addr);
		assert(htent_added);
	}
	/* If we processed a base var above to print an alias association but it hadn't been printed yet,
	 * we had to wait until after lvzwr_out() was called before we could set the flag that indicated
	 * the printing had occurred. Do that now. Note that it is only when this flag is set we are
	 * certain to have a good value in zav.
	 */
	if (value_printed_pending)
	{
		assert(zav);
		zav->value_printed = TRUE;
	}
	if (lvzwrite_block->subsc_count && (n >= lvzwrite_block->subsc_count)
		&& (ZWRITE_ASTERISK != zwr_sub->subsc_list[lvzwrite_block->subsc_count - 1].subsc_type))
		return;

	if (n < lvzwrite_block->subsc_count && ZWRITE_VAL == zwr_sub->subsc_list[n].subsc_type)
	{
		var = op_srchindx(VARLSTCNT(2) lv, zwr_sub->subsc_list[n].first);
		zwr_sub->subsc_list[n].actual = zwr_sub->subsc_list[n].first;
		if (var && (LV_IS_VAL_DEFINED(var) || n < lvzwrite_block->subsc_count -1))
		{
			lvzwr_var(var, n + 1);
			zwr_sub->subsc_list[n].actual = (mval *)NULL;
			lvzwrite_block->curr_subsc = n;
		} else
		{
			if (lvzwrite_block->fixed)
			{
				unsigned char buff[512], *end;

				lvzwrite_block->curr_subsc++;
				end = lvzwr_key(buff, SIZEOF(buff));
				zwr_sub->subsc_list[n].actual = (mval *)NULL;
				lvzwrite_block->curr_subsc = lvzwrite_block->subsc_count = 0;
				if (!undef_inhibit)
					rts_error_csa(CSA_ARG(NULL) VARLSTCNT(4) ERR_UNDEF, 2, end - buff, buff);
			}
		}
	} else  if (lvt = LV_GET_CHILD(lv))
	{	/* If node has children, process them now */
		zwr_sub->subsc_list[n].actual = &mv;
		/* In case of standard null collation, first process null subscript if it exists */
		if (TREF(local_collseq_stdnull))
		{
			nullsubsnode = lvAvlTreeLookupStr(lvt, (treeKeySubscr *)&literal_null, &parent);
			if (NULL != nullsubsnode)
			{
				assert(MVTYPE_IS_STRING(nullsubsnode->key_mvtype) && !nullsubsnode->key_len);
				/* Process null subscript first */
				LV_STR_NODE_GET_KEY(nullsubsnode, &mv); /* Get node key into "mv" */
				COMMON_STR_PROCESSING(nullsubsnode);
			}
		} else
			nullsubsnode = NULL;
		for (node = lvAvlTreeFirst(lvt); NULL != node; node = lvAvlTreeNext(node))
		{
			if (node == nullsubsnode)
			{
				assert(TREF(local_collseq_stdnull));
				continue;	/* skip null subscript as it has already been processed */
			}
			LV_NODE_GET_KEY(node, &mv); /* Get node key into "mv" depending on the structure type of "node" */
			if (!MVTYPE_IS_STRING(mv.mvtype))
			{	/* "node" is of type "lvTreeNodeNum *" */
				COMMON_NUMERIC_PROCESSING(node);
			} else
			{	/* "node" is of type "lvTreeNode *" */
				COMMON_STR_PROCESSING(node);
			}
		}
		zwr_sub->subsc_list[n].actual = (mval *)NULL;
		lvzwrite_block->curr_subsc = n;
	}
}
