/****************************************************************
 *								*
 *	Copyright 2010, 2012 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"

#ifdef GTM_TRIGGER
#include "gtm_stdio.h"
#include "gtm_string.h"

#include "nametabtyp.h"
#include "namelook.h"
#include "op.h"
#include "util.h"
#include "gdsroot.h"
#include "gdsblk.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "error.h"
#include "filestruct.h"
#include "gdscc.h"
#include "gdskill.h"
#include "jnl.h"
#include "io.h"
#include "hashtab_str.h"
#include "gv_trigger.h"
#include "trigger.h"
#include "trigger_update_protos.h"
#include "trigger_select_protos.h"
#include "trigger_trgfile_protos.h"

GBLREF	sgmnt_data_ptr_t cs_data;
GBLREF  uint4		dollar_tlevel;
GBLREF	gd_addr		*gd_header;
GBLREF	gd_binding	*gd_map;
GBLREF	gd_region	*gv_cur_region;
GBLREF	gv_key		*gv_currkey;
GBLREF	gv_namehead	*gv_target;
GBLREF	boolean_t	dollar_ztrigger_invoked;
GBLREF	int4		gtm_trigger_depth;
GBLREF	mstr		*dollar_ztname;

LITREF	mval	literal_zero;
LITREF	mval	literal_one;

STATICDEF char		save_currkey[SIZEOF(gv_key) + SIZEOF(short) + DBKEYSIZE(MAX_KEY_SZ)];	/* SIZEOF(short) for alignment */
STATICDEF gd_addr	*save_gd_header;
STATICDEF gd_binding	*save_gd_map;
STATICDEF gv_key	*save_gv_currkey;
STATICDEF gv_namehead	*save_gv_target;
STATICDEF gd_region	*save_gv_cur_region;
STATICDEF boolean_t	save_gv_last_subsc_null, save_gv_some_subsc_null;
#ifdef DEBUG
STATICDEF boolean_t	in_op_fnztrigger;
#endif

error_def(ERR_FILENAMETOOLONG);
error_def(ERR_ZTRIGINVACT);
error_def(ERR_DZTRIGINTRIG);

enum ztrprms
{
	ZTRP_FILE,	/* $ZTRIGGER() FILE parameter - process given file of triggers */
	ZTRP_ITEM,	/* $ZTRIGGER() ITEM parameter - process single line of trigger file */
	ZTRP_SELECT	/* $ZTRIGGER() SELECT parameter - perform mupip trigger select with given parms */
};

LITDEF nametabent ztrprm_names[] =
{
	{1, "F"}, {4, "FILE"},
	{1, "I"}, {4, "ITEM"},
	{1, "S"}, {6, "SELECT"}
};
LITDEF unsigned char ztrprm_index[27] =
{
	 0,  0,  0,  0,  0,  0,  2,  2,  2,	/* a b c d e f g h i */
	 4,  4,  4,  4,  4,  4,  4,  4,  4,	/* j k l m n o p q r */
	 4,  6,  6,  6,  6,  6,  6,  6,  6	/* s t u v w x y z ~ */
};
LITDEF enum ztrprms ztrprm_data[] =
{
	ZTRP_FILE, ZTRP_FILE,
	ZTRP_ITEM, ZTRP_ITEM,
	ZTRP_SELECT, ZTRP_SELECT
};

/* reset global variables from the values noted down at op_fnztrigger entry */
#define RESTORE_ZTRIGGER_ENTRY_STATE												\
{																\
	unsigned short	top;													\
	DCL_THREADGBL_ACCESS;													\
																\
	SETUP_THREADGBL_ACCESS;													\
	/* Reset global variables noted down at function entry */								\
	gd_map = save_gd_map;													\
	gd_header = save_gd_header;												\
	if (NULL != save_gv_cur_region)												\
	{															\
		gv_cur_region = save_gv_cur_region;										\
		TP_CHANGE_REG(gv_cur_region);											\
	}															\
	gv_target = save_gv_target;												\
	if (NULL != save_gv_currkey)												\
	{	/* gv_currkey->top could have changed if we opened a database with bigger keysize				\
		 * inside the trigger* functions above. Therefore take care not to overwrite that				\
		 * part of the gv_currkey structure. Restore everything else.							\
		 */														\
		top = gv_currkey->top;												\
		memcpy(gv_currkey, save_gv_currkey, SIZEOF(gv_key) + save_gv_currkey->end);					\
		gv_currkey->top = top;												\
	} else if (NULL != gv_currkey)												\
	{															\
		gv_currkey->end = 0;												\
		gv_currkey->base[0] = KEY_DELIMITER;										\
		TREF(gv_last_subsc_null) = save_gv_last_subsc_null;								\
		TREF(gv_some_subsc_null) = save_gv_some_subsc_null;								\
	}															\
	DEBUG_ONLY(in_op_fnztrigger = FALSE);											\
}

/* In case of an rts_error deep inside the op_fnztrigger invocation, we need to restore certain global variables that could
 * have been modified.
 */
CONDITION_HANDLER(op_fnztrigger_ch)
{
	START_CH;

	RESTORE_ZTRIGGER_ENTRY_STATE;

	NEXTCH;

}

void op_fnztrigger(mval *func, mval *arg1, mval *arg2, mval *dst)
{
	int				inparm_len, index;
	uint4				dummy_stats[NUM_STATS], filename_len;
	boolean_t			failed;
	char				filename[MAX_FN_LEN + 1];
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	assert(FALSE == in_op_fnztrigger); /* $ZTRIGGER() should not nest */
	DEBUG_ONLY(in_op_fnztrigger = TRUE);
	/* $ZTRIGGER() is not allowed while already inside the trigger frame */
	if (0 < gtm_trigger_depth)
	{
		DEBUG_ONLY(in_op_fnztrigger = FALSE);
		rts_error(VARLSTCNT(4) ERR_DZTRIGINTRIG, 2, dollar_ztname->len, dollar_ztname->addr);
	}
	MV_FORCE_STR(func);
	MV_FORCE_STR(arg1);
	/* MV_FORCE_STR(arg2); optional arg2 not currently used - added so parm easily implemented when added */
	inparm_len = func->str.len;
	if ((0 >= inparm_len) || (NAME_ENTRY_SZ < inparm_len) || !ISALPHA_ASCII(func->str.addr[0]))
	{
		DEBUG_ONLY(in_op_fnztrigger = FALSE;)
		/* We have a wrong-sized keyword */
		rts_error(VARLSTCNT(3) ERR_ZTRIGINVACT, 1, 1);
	}
	if (0 > (index = namelook(ztrprm_index, ztrprm_names, func->str.addr, inparm_len)))	/* Note assignment */
	{
		DEBUG_ONLY(in_op_fnztrigger = FALSE);
		/* Specified parm was not found */
		rts_error(VARLSTCNT(3) ERR_ZTRIGINVACT, 1, 1);
	}
	if ((0 < arg1->str.len) && (0 == arg1->str.addr[0]))
	{
		DEBUG_ONLY(in_op_fnztrigger = FALSE);
		/* 2nd parameter is invalid */
		rts_error(VARLSTCNT(3) ERR_ZTRIGINVACT, 1, 2);
	}
	save_gd_header = gd_header;
	save_gv_target = gv_target;
	save_gd_map = gd_map;
	save_gv_cur_region = gv_cur_region;
	if (NULL != gv_currkey)
	{	/* Align save_gv_currkey on a "short" boundary, but first we check that field "gv_currkey->end" is truly short */
		assert(SIZEOF(short) == SIZEOF(save_gv_currkey->end));
		save_gv_currkey = (gv_key *)ROUND_UP2((INTPTR_T)&save_currkey[0], SIZEOF(gv_currkey->end));
		memcpy(save_gv_currkey, gv_currkey, SIZEOF(gv_key) + gv_currkey->end);
		save_gv_last_subsc_null = TREF(gv_last_subsc_null);
		save_gv_some_subsc_null = TREF(gv_some_subsc_null);
	} else
		save_gv_currkey = NULL;
	util_out_print(NULL, RESET);	/* Make sure the util_out_print_gtmio() messages start at the beginning of the buffer
					 * in case there are any stale messages already in the buffer - for example rts_error
					 * output left over from a trapped error and hence not printed
					 */
	/* Any time gv_currkey is manipulated, the global variables - gv_last_subsc_null and gv_some_subsc_null should also be
	 * set appropriately. Although the BUILD_HASHT_*_CURRKEY_T macros maintain this property, the
	 * INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED macro does not maintain the global variables. Since the macro is used in various
	 * other places (including update process and GT.M runtime), the implications of setting the global variables is not
	 * clear. So, reset gv_currkey and the global variables at the $ztrigger() entry as they will be restored before exiting
	 * from this function.
	 */
	if (gv_currkey)
	{
		gv_currkey->end = 0;
		gv_currkey->base[0] = KEY_DELIMITER;
	}
	TREF(gv_last_subsc_null) = TREF(gv_some_subsc_null) = FALSE;
	ESTABLISH(op_fnztrigger_ch);
	dollar_ztrigger_invoked = TRUE; /* reset after use when the transaction commits, restarts or rollbacks */
	switch(ztrprm_data[index])
	{
		case ZTRP_FILE:
			if (0 == arg1->str.len)
				/* 2nd parameter is missing */
				rts_error(VARLSTCNT(3) ERR_ZTRIGINVACT, 1, 2);
			if (MAX_FN_LEN < arg1->str.len)
				rts_error(VARLSTCNT(1) ERR_FILENAMETOOLONG);
			/* The file name is in string pool so make a local copy in case GC happens */
			strncpy(filename, arg1->str.addr, arg1->str.len);
			filename_len = arg1->str.len;
			filename[filename_len] = '\0';
			failed = trigger_trgfile_tpwrap(filename, filename_len, TRUE);
			break;
		case ZTRP_ITEM:
			if (0 == arg1->str.len)
				/* 2nd parameter is missing */
				rts_error(VARLSTCNT(3) ERR_ZTRIGINVACT, 1, 2);
			failed = trigger_update(arg1->str.addr, arg1->str.len);
			break;
		case ZTRP_SELECT:
			failed = (TRIG_FAILURE == trigger_select(arg1->str.addr, arg1->str.len, NULL, 0));
			break;
		default:
			GTMASSERT;	/* Should never happen with checks above */
	}
	REVERT;
	RESTORE_ZTRIGGER_ENTRY_STATE;
	memcpy(dst, (failed ? &literal_zero : &literal_one), SIZEOF(mval));
	return;
}
#else /* !GTM_TRIGGER */
void op_fnztrigger(mval *func, mval *arg1, mval *arg2, mval *dst)
{
	error_def(ERR_UNIMPLOP);

	rts_error(VARLSTCNT(1) ERR_UNIMPLOP);
}
#endif
