/* Main function to create two-way interactions.
   INPUT:
     data       - Data set containing all variables. This data gets updated with new variables.
     covars     - Space delimited list of covariates 	 
     catVarInfo - Data set created from calling %createDummyVariables containing info
	              on categorical variables.
	 sep        - The separator to combine variables names for the new interaction term names.
	              The default is X.
	              The interaction of var1 and var2 will be var1Xvar2 by default.
	OUTPUT:
	 retInfo    - Data set of interaction variable info. It will contain variables:
	              var1, var2, ndummy1, ndummy2, inter_vars
   
*/
%macro createInteractions(retInfo, data, covars, catVarInfo, sep=X, DEBUG=0);

	%local nvars catvarFlag i ind tmp var1 var2 nd1 nd2 nint len maxlen;
	%local var1vec var2vec nd1vec nd2vec dvars1 dvars2 intvec dvars1vec dvars2vec;

	%deleteData(&retInfo);
	%let i      = 0;
	%let covars = %normStr(&covars, upcase=1, removeMultBlanks=1);
	
	/* See if there are any interactions */
	%let nvars  = %listlen(&covars);
	%if &nvars %then %let i = %index(&covars, *); 
    %if %eval(&i = 0) %then %goto exit_crInt;

	%let catVarFlag = %sysfunc(exist(&catVarInfo)); /* Flag for categorical variables */
	
	/* Initialize */
    %let nint       = 0;
	%let var1vec    =;
	%let var2vec    =;
	%let nd1vec     =;
	%let nd2vec     =;
	%let intvec     =;
	%let dvars1vec  =;
	%let dvars2vec  =;
	%let maxlen     = 0;

	* Get the variables for each interaction, and see if any are categorical;
	%do i = 1 %to &nvars;
		%let tmp = %scan(&covars, &i, " ");
		%let ind = %index(&tmp, %str(*));
		%if &ind %then %do;
			%let var1   = %scan(&tmp, 1, %str(*));
			%let var2   = %scan(&tmp, 2, %str(*));
			* Only 2-way interactions are allowed;
			%let ind    = %normStr(%scan(&tmp, 3, %str(*)));
			%if %length(&ind) %then %do;
				%errorExit(9, ERROR with &tmp.: only 2-way interactions are allowed, exit=1);
			%end;
			
			* First assume var1 and var2 are continuous;
			%let nd1    = -1; /* -1 denotes a continuous variable */
			%let nd2    = -1;
			%let dvars1 = &var1;
			%let dvars2 = &var2;
			%if &catvarflag %then %do;
				* See if var1 or var2 is categorical;
				data _NULL_; set &catVarInfo;
					if variable = "&var1" then do;
						call symputx("nd1", ndummy);
						call symputx("dvars1", dummy_vars);
					end;
					if variable = "&var2" then do;
						call symputx("nd2", ndummy);
						call symputx("dvars2", dummy_vars);
					end;
				run;
			%end;
			%if &DEBUG %then %put var1=&var1 vars=&var2 dvars1=&dvars1 dvars2=&dvars2;
			
			* Save variable names and the new interaction names;
			%let var1vec   = &var1vec &var1;
			%let var2vec   = &var2vec &var2;
			%let nd1vec    = &nd1vec &nd1;
			%let nd2vec    = &nd2vec &nd2;
			%let nint      = %eval(&nint + 1);
			%let dvars1vec = &dvars1vec | &dvars1;
			%let dvars2vec = &dvars2vec | &dvars2;
			
			* New interaction variable names, save the maximum length;
			%let tmp       = %getInterVarNames(&dvars1, &dvars2, sep=&sep);
			%let intvec    = &intvec | &tmp;
			%let len       = %length(&tmp);
            %if %eval(&len > &maxlen) %then %let maxlen = &len;			
		%end;
	%end;

	%if %eval(&nint = 0) %then %goto exit_crInt;
	
	* Maximum length of variable in data step below;
	%let maxlen = %eval(&maxlen + 10);
	%let maxlen = %sysfunc(min(&maxlen, 32767));
	
	/* Create variable info data.
	   Return data set containing variables: var1, var2, ndummy1, ndummy2, inter_vars.
		Recall that a categorical variable with 0 dummy vars was left out of the analysis.
		So, only include interactions involving categorical variables with > 0 dummy variables. 
	*/
	data &retInfo; 
		length var1 $32;
		length var2 $32;
		length inter_vars $ &maxlen;
		
		%do i = 1 %to &nint;
			%let var1   = %scan(&var1vec, &i, ' ');
			%let var2   = %scan(&var2vec, &i, ' ');
			%let nd1    = %scan(&nd1vec,  &i, ' ');
			%let nd2    = %scan(&nd2vec,  &i, ' ');
			%let tmp    = %scan(&intvec,  &i, |);
			
			var1        = left(trim("&var1"));
			var2        = left(trim("&var2"));
			ndummy1     = &nd1;
			ndummy2     = &nd2;
			inter_vars  = left(trim("&tmp"));
	        output;
		%end;
	run;
	
	/* Add the new interaction variables to data. */
	%do i = 1 %to &nint;
		%let tmp = %scan(&intvec, &i, |);
		%let tmp = %left(%trim(&tmp));
		%if %length(&tmp) %then %do;
			%let dvars1 = %scan(&dvars1vec, &i, |);
			%let dvars2 = %scan(&dvars2vec, &i, |);
			%interByVar1(&data, &dvars1, &dvars2, newvars=&tmp, sep=&sep);
		%end;
	%end;
	
	%exit_crInt: ;

%mend createInteractions;

%macro interByVar1(data, vars1, vars2, newvars=, sep=X);

	/* If newvars is specified, it must be of the correct order:
        vars1[1]Xvars2[1] vars1[1]Xvars2[2] ... */

	%local n1 n2 new i j k newflag v1 v2;
	
	%let n1      = %listlen(&vars1);
	%let n2      = %listlen(&vars2);
	%let newflag = %length(newvars);
	%if &newflag %then %do;
		%let i = %listlen(&newvars);
		%if (%eval(&i ^= &n1 * &n2)) %then %errorExit(9, INTERNAL CODING ERROR with newvars, exit=1);
	%end;

	%let k = 0;
	data &data; set &data;
		%do i = 1 %to &n1;
			%let v1 = %scan(&vars1, &i, " ");
			%do j = 1 %to &n2;
				%let v2 = %scan(&vars2, &j, " ");
				%let k  = %eval(&k + 1);
				* Get the new variable name;
				%if &newflag %then %let new = %scan(&newvars, &k, " ");
				%else %let new = &v1.&sep.&v2;	
				&new = &v1 * &v2;
			%end;
		%end;
	run;
	
%mend interByVar1;

/* Macro function to return the interaction variable names */
%macro getInterVarNames(vars1, vars2, sep=X);

	%local i j ret n1 n2 v1 v2 tmp;
	
	%let ret   =;
	%let vars1 = %left(%trim(&vars1));
	%let vars2 = %left(%trim(&vars2));
	%let n1    = %listlen(&vars1);
	%let n2    = %listlen(&vars2);
	
	%if &n1 and &n2 %then %do;
		%do i = 1 %to &n1;
			%let v1 = %scan(&vars1, &i, ' ');
			%do j = 1 %to &n2;
				%let v2  = %scan(&vars2, &j,  ' ');
				%let tmp = %left(%trim(&v1.&sep.&v2));
				%let ret = &ret &tmp;
			%end;
		%end;
	%end;

	&ret

%mend getInterVarNames;

%macro updateListWithInteractions(varlist, interVarsData);

	%local dsid rc nobs ret i j nvars var var1 var2 stop found m vnum1 vnum2 vnumi tmp;
	
	%let varlist = %normStr(&varlist, upcase=1, removeMultBlanks=1);
	%let nvars   = %listlen(&varlist);
	
	/* See if the data exists */
	%let nobs = %getNobs(&interVarsData, zeroIfMissing=1);
	%if %eval(&nobs < 1) %then %do;
		/* No interaction variables, so return varlist */
		%let ret = &varlist;
	%end;
	%else %if %eval(&nvars < 1) %then %do;
		/* No variables in list, so return varlist */
		%let ret = &varlist;
	%end;
	%else %do;
		/* Initialize return list */
		%let ret=;
	
		/* Store the interaction variables in macro variables */
		%let dsid  = %sysfunc(open(&interVarsData));
		%let vnum1 = %sysfunc(varnum(&dsid, var1));
		%let vnum2 = %sysfunc(varnum(&dsid, var2));
		%let vnumi = %sysfunc(varnum(&dsid, inter_vars));
		%if %eval(&vnum1 <= 0) or %eval(&vnum2 <= 0) or %eval(&vnumi <= 0) %then %do;
			%let rc = %sysfunc(close(&dsid));
			%errorExit(9, INTERNAL CODING ERROR with &interVarsData, exit=1);
		%end;		
		%do i = 1 %to &nobs;
			%local v1&i v2&i inter&i;
			%let rc      = %sysfunc(fetchobs(&dsid, &i));
			%let v1&i    = %normStr(%sysfunc(getvarc(&dsid, &vnum1)), upcase=1, removeMultBlanks=1);
			%let v2&i    = %normStr(%sysfunc(getvarc(&dsid, &vnum2)), upcase=1, removeMultBlanks=1);
			%let inter&i = %normStr(%sysfunc(getvarc(&dsid, &vnumi)), upcase=1, removeMultBlanks=1);
		%end;
		%let rc = %sysfunc(close(&dsid));
		
		/* Loop of each variable in varlist and replace an interaction variable with the new variables,
			preserving the order of varlist */
		%do i = 1 %to &nvars;
			%let var   = %scan(&varlist, &i, ' ');
			%let found = 0;
			%let j     = %index(&var, %str(*));
			%if &j %then %do;
				/* Interaction term */
				%let var1  = %scan(&var, 1, %str(*));
				%let var2  = %scan(&var, 2, %str(*));
				%let j     = 0;
				%let stop  = 0;
				%do %until (&stop);
					%let j = %eval(&j + 1);
					%if (("&var1" = "&&v1&j") and ("&var2" = "&&v2&j")) or
						(("&var2" = "&&v1&j") and ("&var1" = "&&v2&j")) %then %do;
						/* Variable found in list of interaction variables */
						%let found = &j;
						%let stop  = 1;
					%end;
					%if %eval(&j >= &nobs) %then %let stop = 1;
				%end;
			%end;
			/* If found, then check the interaction terms. If there are no terms, then 
				it will not be included in return list */
			%if &found %then %do;
				%let tmp = %normStr(&&inter&found);
				%if %length(&tmp) %then %let ret = &ret &tmp;
			%end;
			%else %do;
				/* Not an interaction, so keep original variable in return list */
				%let ret = &ret &var;
			%end;
		%end;
	%end;
	%if %length(&ret) %then %let ret = %normStr(&ret, upcase=1, removeMultBlanks=1);

	&ret

%mend updateListWithInteractions;

/* Macro to update a SAS data set of covariate lists with interaction variables. 
   This macro updates variable &var of the input data set &data. Also, the
   length of variable &var will be increased.
   INPUT:
   data          The data set to be updated containing variable var
   interVarsData The returned data set from %createInteractions
   var           The variable to be updated. The default is covariates
   OUTPUT:
                 The data set &data is updated 
*/
%macro updateDataWithInteractions(data, interVarsData, var="covariates");

	%local nobs len newlen i covlist newlist;

	%let nobs = %getNobs(&data, zeroIfMissing=1);
	%if &nobs %then %do;
		** We probably need to increase the LENGTH of the SAS variable;
		%let len    = %getVarLen(&data, &var);
		%let newlen = %sysfunc(min(&len + 1000, 32767));
		
		** Loop over each row of data;
		%do i = 1 %to &nobs;
			%let covlist =;
			** Get the ith covariate list;
			data _NULL_; set &data ;
				if (_N_ = &i) then call symputx("covlist", &var);
			run;
			%let newlist = %updateListWithInteractions(&covlist, &interVarsData);
			%if %length(&newlist) %then %do;
				data &data; 
					length &var $ &newlen;
					set &data;
					if (_N_ = &i) then &var = "&newlist"; 
				run;
			%end;
		%end;
	%end;

%mend updateDataWithInteractions;

%macro updateCovList(covlist);

  %local ret;
  %let ret = %sysfunc(tranwrd(&covlist, %str(*), %str( )));	
  &ret

%mend updateCovlist; 