#include <origin.h>
#include <GetNBox.h>
#include <xfutils.h>
 
///Nicole 07/05/2007 v8.0655 REMOVE_SPACE_FOR_LOCATION_STRING
#include <GetNBox.h>
#include <stats_utils.h>
#include <report_utils.h>
#include <event_utils.h>
#include <control.h>
#include <o8dlg.h>
///////////////////////////////////////////////////
// Modification log
///Jasmine 08/14/07 QA80-10207 REPORT_SHEET_CHECK_CONFLICT
/// Max 3/7/08 SHOULD_PASS_WHOLE_STRING_OF_LPCSZRDLABEL_FOR_LOCALIZATION
/// Max 3/19/08 SHOULD_NOT_RESET_VARIABLE_VALUE_IN_LT
/// Folger 12/26/08 QA80-12642-P3 v8.0991 CONSISTENT_SHOW_LN_WITH_DOUBLE_QUOTES_IN_REPORT_SHEET
/// Folger 02/20/09 OUTPUT_NODES_SHOULD_BE_DISABLED_IN_CAHNGE_PARAM
/// Kyle 03/02/2009 SHOULD_NOT_CHANGE_OUTPUT_ON_GUI_ON_CHANGE_PARAMETERS
/// Folger 08/26/2010 ORG-920-P2 COX_MODAL_BETTER_HANDLING_OF_OUTPUT_PLOT_AND_REPORT_DATA
///////////////////////////////////////////////////
//put your own support static functions here

#define IDST_COX_LE           0x00000010
#define IDST_COX_PE           0x00000012
#define IDST_COX_COV          0x00000016
#define IDST_COX_CORR         0x00000018
#define IDST_SA_SP            0x11110000


enum
{
	ERR_DIFF_SIZE,
	ERR_COX_MODEL
	
};

static void _get_fit_stats(SAFitStats stSAFitStats, vector &vStats)
{
	vStats.SetSize(4);
	vStats[0] = round(stSAFitStats.Criter, 4);
	vStats[1] = round(stSAFitStats.ChiSq, 4);
	vStats[2] = stSAFitStats.DOF;
	vStats[3] = round(stSAFitStats.Prob, 4);
}


static void _get_analysis_stats(SAParaEstim *psSAParaEstim, int ii, vector &vStats)
{
	vStats.SetSize(6);
	vStats[0] = psSAParaEstim[ii].DOF;
	vStats[1] = round(psSAParaEstim[ii].ParaEst, 4);
	vStats[2] = round(psSAParaEstim[ii].SE, 4);
	vStats[3] = round(psSAParaEstim[ii].ChiSq, 4);
	vStats[4] = round(psSAParaEstim[ii].Prob, 4);
	vStats[5] = round(psSAParaEstim[ii].Hazard, 4);
}

static void _get_cox_estimates(SAEstim stSurvivEstim, vector& vCoxEstim)
{
	vCoxEstim.SetSize(6);
	vCoxEstim[0] = stSurvivEstim.Time;
	vCoxEstim[1] = stSurvivEstim.Status;
	vCoxEstim[2] = stSurvivEstim.Surv;
	vCoxEstim[3] = stSurvivEstim.SD;
	vCoxEstim[4] = stSurvivEstim.Events;
	vCoxEstim[5] = stSurvivEstim.Remain;
	
}


void cox_make_likelihood_analysis(ReportTree& outRT, SAFitStats& stSAFitStats, const bool bConverage)
{
    //////////////////////////////////////////////////////////////////
	// -2 Log Likelihood Estimates
	//////////////////////////////////////////////////////////////////
	ReportTable rtLE = outRT.CreateTable("Likelihood", _L("Analysis of -2 Log Likelihood Estimates"), IDST_COX_LE);
	
	vector<string> vFitStatsLabels(4);
	vFitStatsLabels[0] = _L("-2 Log Likelyhood");
	vFitStatsLabels[1] = _L("Chi-Square");
	vFitStatsLabels[2] = _L("DF");
	vFitStatsLabels[3] = _L("Pr > ChiSq");
	
	vector<string> vstrTagName;
	string strTagName = COX_LIKELIHOOD_ESTIM;
	strTagName.GetTokens(vstrTagName, '|');
	
	vector vFitStats;
	_get_fit_stats(stSAFitStats, vFitStats);
    /// Nicole 07/05/2007 v8.0655 REMOVE_SPACE_FOR_LOCATION_STRING
    //rtLE.AddRow(_L(""), vFitStats, _L(" "), vFitStatsLabels, NULL, make_one_set_ID(IDST_COX_LE, 0));
    rtLE.AddRow("LH", vFitStats, " ", vFitStatsLabels, vstrTagName, make_one_set_ID(IDST_COX_LE, 0));
    /// End REMOVE_SPACE_LOCATION_STRING    if (!bConverage)
	///Sophy 3/30/2009 QA80-13352 SUPPORT_SPECIFY_COLUMN_DESIGNATIONS_IN_REPORT_TABLES
	TreeNode trRow = rtLE.GetNode("LH");
	if ( trRow )
		tree_set_attribute_to_all_nodes(trRow, STR_COL_DESIGNATION_ATTRIB, (string)OKDATAOBJ_DESIGNATION_Y);
	///end SUPPORT_SPECIFY_COLUMN_DESIGNATIONS_IN_REPORT_TABLES

    {
    	string strFootNote = _L("Iteration limit reached without convergence.\nValidity of the model fit is questionable.");
	 	TreeNode trFooter = tree_check_get_node(rtLE, "Footer", IDE_FOOTNOTE_BEGIN, TREE_Footnote, "1"); // just need the TREE_Footnote attribute, value does not matter, so 1 is good
		trFooter.strVal = strFootNote;		
   }
}

void cox_make_parameter_analysis(ReportTree& outRT, SAParaEstim* psSAParaEstim, const vector<string>& vstrCovarLab)
{
	 //////////////////////////////////////////////////////////////////
	// Parameter estimates
	//////////////////////////////////////////////////////////////////
	ReportTable rtPE = outRT.CreateTable("ParamEstim", _L("Analysis of parameter estimates"), IDST_COX_PE);
	
	vector<string> vANAStatsLabels(6);
	vANAStatsLabels[0] = _L("DF");
	vANAStatsLabels[1] = _L("Estimate");
	vANAStatsLabels[2] = _L("Standard Error");
	vANAStatsLabels[3] = _L("Chi-Square");
	vANAStatsLabels[4] = _L("Pr > ChiSq");
	vANAStatsLabels[5] = _L("Hazard Ratio");
	
	vector<string> vstrTagName;
	string strTagName = COX_PARAM_ESTIM;
	strTagName.GetTokens(vstrTagName, '|');
	
	vector vANAStats;
	for(int ii = 0; ii < vstrCovarLab.GetSize(); ii++)
	{
	    _get_analysis_stats(psSAParaEstim, ii, vANAStats);
	    string strRow = "Param" + ftoa(ii+1);
	    rtPE.AddRow(strRow, vANAStats, vstrCovarLab[ii], vANAStatsLabels, vstrTagName, make_one_set_ID(IDST_COX_PE, ii));		
		///Sophy 3/30/2009 QA80-13352 SUPPORT_SPECIFY_COLUMN_DESIGNATIONS_IN_REPORT_TABLES
		TreeNode trRow = rtPE.GetNode(strRow);
		if ( trRow )
		{
			tree_set_attribute_to_all_nodes(trRow, STR_COL_DESIGNATION_ATTRIB, (string)OKDATAOBJ_DESIGNATION_Y);
			if ( trRow.SE )
				trRow.SE.SetAttribute(STR_COL_DESIGNATION_ATTRIB, OKDATAOBJ_DESIGNATION_ERROR);
		}
		///end SUPPORT_SPECIFY_COLUMN_DESIGNATIONS_IN_REPORT_TABLES
	}
}

void cox_get_survival_analysis_data(ReportData& outData, const bool bSF, const bool bHazard, const int& nSizeSAEstm, SAEstim* pSurvivEstim)
{
	vector vSurvivEstims;
	vector vX,vY;
	vX.SetSize(nSizeSAEstm+1);
	vY.SetSize(nSizeSAEstm+1);
	vX[0] = 0;
	vY[0] = 1;
	
	for (int ii = 0; ii < nSizeSAEstm; ii++)
	{
		_get_cox_estimates(pSurvivEstim[ii], vSurvivEstims);
		vX[ii+1] = vSurvivEstims[0];
		vY[ii+1] = vSurvivEstims[2];
	}
	
	vector<string> vstrGroup(1);
	vstrGroup[0] = " ";
	surv_update_report_data(outData, 0.95, vX, vY, NULL ,0, vstrGroup, bSF, bHazard);
}

void cox_make_covars_corre_analysis(ReportTree& outRT, const int& cov, const int& corr, const matrix& mCovars, const matrix& mCorre, const vector<string>& vstrCovarLab)
{
	int ii;
	vector vTemp;
	string strRow;
	
	int nSize = vstrCovarLab.GetSize();
	vector<string> vstrTagName;
	vstrTagName.SetSize(nSize);
	for (ii = 0; ii < nSize; ii++)
	{
		vstrTagName[ii] = "Var"+ftoa(ii+1);
	}
	
	if(cov)
	{
		ReportTable rtCovariance = outRT.CreateTable("Covariance", _L("Estimated Covariance Matrix"), IDST_COX_COV);
		for(ii = 0; ii < nSize; ii++)
		{
			strRow = "Var" + ftoa(ii+1);
	    	mCovars.GetRow(vTemp, ii);
			rtCovariance.AddRow(strRow, vTemp, vstrCovarLab[ii], vstrCovarLab, vstrTagName, make_one_set_ID(IDST_COX_COV, ii));		
			///Sophy 3/30/2009 QA80-13352 SUPPORT_SPECIFY_COLUMN_DESIGNATIONS_IN_REPORT_TABLES
			TreeNode trRow = rtCovariance.GetNode(strRow);
			if ( trRow )
				tree_set_attribute_to_all_nodes(trRow, STR_COL_DESIGNATION_ATTRIB, (string)OKDATAOBJ_DESIGNATION_Y);
			///end SUPPORT_SPECIFY_COLUMN_DESIGNATIONS_IN_REPORT_TABLES
		}
	}
	
	if(corr)
	{
		ReportTable rtCorrelation = outRT.CreateTable("Correlation", _L("Estimated Correlation Matrix"), IDST_COX_CORR);
		for(ii = 0; ii < nSize; ii++)
		{
			strRow = "Var" + ftoa(ii+1);
	    	mCorre.GetRow(vTemp, ii);
			rtCorrelation.AddRow(strRow, vTemp, vstrCovarLab[ii], vstrCovarLab, vstrTagName, make_one_set_ID(IDST_COX_CORR, ii));		
			///Sophy 3/30/2009 QA80-13352 SUPPORT_SPECIFY_COLUMN_DESIGNATIONS_IN_REPORT_TABLES
			TreeNode trRow = rtCorrelation.GetNode(strRow);
			if ( trRow )
				tree_set_attribute_to_all_nodes(trRow, STR_COL_DESIGNATION_ATTRIB, (string)OKDATAOBJ_DESIGNATION_Y);
			///end SUPPORT_SPECIFY_COLUMN_DESIGNATIONS_IN_REPORT_TABLES
		}
	}
}	

//assume all subrange more than or equal to nIndex-th one are covariates
static int _get_covariate(const DataRange& dr, const int nIndex, vector<string> &vstrColNames, matrix &mCovars, int nSize)
{
	int r1, c1, r2, c2;
	Worksheet wksTemp;
	string strRange;

	int nRanges = dr.GetNumRanges() - nIndex;
	int nCovars = 0, nCol = 0;
	for (int kk = 0; kk < nRanges; kk++)
	{
	
		dr.GetRange(kk+nIndex, r1, c1, r2, c2, wksTemp, &strRange);
		int bBegin = nCovars;
		nCovars += c2-c1+1;
		mCovars.SetSize(nSize, nCovars, true);
		vstrColNames.SetSize(nCovars);
		for(int ii=c1, jj=0; ii<=c2; ii++, jj++)
		{
			nCol = bBegin + jj;
			Column col = wksTemp.Columns(ii);
			string strName = col.GetLongName();
			///------ Folger 12/26/08 QA80-12642-P3 v8.0991 CONSISTENT_SHOW_LN_WITH_DOUBLE_QUOTES_IN_REPORT_SHEET
			//strName = strName.IsEmpty()? col.GetName() : strName;
			strName = strName.IsEmpty()? col.GetName() : "\"" + strName + "\"";
			///------ End CONSISTENT_SHOW_LN_WITH_DOUBLE_QUOTES_IN_REPORT_SHEET
			vstrColNames[nCol] = strName;
			vector vCovar = col.GetDataObject();
			//if (vCovar.GetSize()!= nSize)
				//throw(CER_TIME_COVAR_DIFF_SIZE);
			mCovars.SetColumn(vCovar,nCol);
		}
	}
	
	return nCovars;
}

static int cox_check_input(TreeNode& trGetN)
{
	int nErr = sa_check_data(trGetN.irng, SA_COVARIATE);
	if (nErr == CER_NO_ERROR)
		nErr = check_valid_censoring(trGetN);
	
	return nErr;
}


void phm_Cox(const Range& irng, const vector& censor, const int& summary, const int& cov, const int& corr, const int& sf, const int& hazard, ReportData& rd, ReportTree& rt)
{
	
	int nRet = sa_check_data(irng, SA_COVARIATE);
	if (CER_NO_ERROR != nRet)
		XF_THROW(nRet);
	
	int ii;
	vector vTime, vCensor;
	irng.GetData(vTime, 0);
	irng.GetData(vCensor, 1);
	vector<string> vstrCovarLab;	
	matrix mCovars;
	
	int nCovars = _get_covariate(irng, 2, vstrCovarLab, mCovars, vTime.GetSize());
	
	int nMissing;
	nRet = trim_nanum_sa(&nMissing, vTime, vCensor, NULL, mCovars);
	
	
	SASummary	stSASummary;
	SAFitStats	stSAFitStats;
	SAParaEstim* psSAParaEstim = (SAParaEstim*) malloc (nCovars * sizeof(SAParaEstim));	
	int nSizeSAEstm = vTime.GetSize();
	SAEstim* pSurvivEstim = (SAEstim*) malloc (nSizeSAEstm * sizeof(SAEstim));
	matrix mCov, mCorr;
	bool bConverage = true;
	nRet =  stats_surviv_cox_model(vTime,vCensor,censor,mCovars,stSASummary,stSAFitStats,
			psSAParaEstim,nCovars,pSurvivEstim,nSizeSAEstm,mCov,mCorr);
	if (nRet != STATS_NO_ERROR)
	{
		if ( STATS_ERROR_CONVERGENCE_FAIL == nRet )
			bConverage = false;
		else
			XF_THROW_EX(CER_UNKNOWN_ERROR_EX, nRet);
	}
	
	
	LPCSTR lpsczLabel = _L("CoxPHM");
	report_tree_create(rt, lpsczLabel, "irng");
	if(summary)
	{
	 		rt_add_sa_summary(rt, stSASummary, censor);
	}
		
	cox_make_likelihood_analysis(rt, stSAFitStats, bConverage);
	cox_make_parameter_analysis(rt, psSAParaEstim, vstrCovarLab);
	cox_make_covars_corre_analysis(rt, cov, corr, mCov, mCorr, vstrCovarLab);
	cox_get_survival_analysis_data(rd, sf, hazard, nSizeSAEstm, pSurvivEstim);
	
	///Kyle 03/02/2009 SHOULD_NOT_CHANGE_OUTPUT_ON_GUI_ON_CHANGE_PARAMETERS
	//if(0 != sf+hazard)
	if(0 != sf+hazard && rd)
	///End SHOULD_NOT_CHANGE_OUTPUT_ON_GUI_ON_CHANGE_PARAMETERS
	{
		rd.ID = IDST_RESULT_CURVES;	
		vector<string> vsLabel;
		vsLabel.SetSize(1);
	   	survival_rd_plotting(rd, vsLabel, rt, sf, hazard);
	}
	
	free(psSAParaEstim);
	free(pSurvivEstim);
	
	
	
}

//nEventID < 0 on event, like GETNE_ON_INIT, GETNE_ON_OK etc, >0 on change from control
//return 0 to do nothing, return PEVENT_GETN_RET_TO_CLOSE to close dialog
//dwEnables = bOKEnable, bBTN1Enable, bBTN2Enable, bBTN3Enable, , can set to false to disable OK/Apply buttons
//lpcszNodeName = the trGetN TreeNode tag name that generates the event, or is  if GETNE_ON_OK etc
//strAux = optional string for XF Wizard Bar, like DLG_NEED_CHANGE_GRID etc, it will be invalid if from internal node value change event
//strErrMsg = allowing messages to be shown
int phm_Cox_event1(TreeNode& trGetN, int nRow, int nEventID, DWORD& dwEnables, LPCSTR lpcszNodeName, WndContainer& DynaCntrlContainer, string& strAux, string& strErrMsg)
{
	if( xf_is_extended_event(nEventID) )
		return phm_Cox_event1_ex(trGetN, nRow, nEventID, dwEnables, lpcszNodeName, DynaCntrlContainer, strAux, strErrMsg);
	DECLARE_BUTTON_ENABLES
	
	TreeNode trRange = trGetN.irng;
	
	TreeNode trCensor = trGetN.censor;
	if (trRange && trCensor)
		set_censor_list(trRange, trCensor);
	
	///------ Folger 08/26/2010 ORG-920-P2 COX_MODAL_BETTER_HANDLING_OF_OUTPUT_PLOT_AND_REPORT_DATA
	/*
	///------ Folger 02/20/09 OUTPUT_NODES_SHOULD_BE_DISABLED_IN_CAHNGE_PARAM
	//bool bEnabled = (0 != get_plot_number(trGetN));
	int		nEnabled = ENABLE;
	bool	bIsChangeParam = trGetN.AutoUpdate.GetAttribute(STR_ENABLE_ATTRIB, nEnabled) && nEnabled == DISABLE;
	bool	bEnabled = !bIsChangeParam && (0 != get_plot_number(trGetN));
	///------ End OUTPUT_NODES_SHOULD_BE_DISABLED_IN_CAHNGE_PARAM
	
	/// Max 3/7/08 SHOULD_PASS_WHOLE_STRING_OF_LPCSZRDLABEL_FOR_LOCALIZATION
	//rd_set_optional(nEventID, bOKEnable, lpcszNodeName, trGetN.rd, bEnabled, _L("SurvFunc1"));
	/// Max 4/8/08 move to event_utils.h
	//string strRDvalue = "[<input>]<new>";
	string strRDvalue = STR_DEFAULT_TEXT_FOR_REPORT_DATA_NODE;
	//------
	okutil_convert_localized_PDS(&strRDvalue);
	///Kyle 03/02/2009 SHOULD_NOT_CHANGE_OUTPUT_ON_GUI_ON_CHANGE_PARAMETERS
	//tr_set_optional(nEventID, lpcszNodeName, trGetN.rd, bEnabled, strRDvalue);
	if(!bIsChangeParam)
		tr_set_optional(nEventID, lpcszNodeName, trGetN.rd, bEnabled, strRDvalue);
	///End SHOULD_NOT_CHANGE_OUTPUT_ON_GUI_ON_CHANGE_PARAMETERS
	/// END SHOULD_PASS_WHOLE_STRING_OF_LPCSZRDLABEL_FOR_LOCALIZATION
	*/
	///------ End COX_MODAL_BETTER_HANDLING_OF_OUTPUT_PLOT_AND_REPORT_DATA
	tr_set_optional_ex(nEventID, lpcszNodeName, trGetN.rd, xf_event_is_change_param(trGetN), 0 != get_plot_number(trGetN));
	trGetN.irng.SetAttribute(STR_ATTRIB_BRANCH, GETNBRANCH_OPEN);	
	
	int nErr = cox_check_input(trGetN);
	///Jasmine 08/14/07 QA80-10207 REPORT_SHEET_CHECK_CONFLICT
	if (nErr == CER_NO_ERROR && is_same_sheet(trGetN.rd, trGetN.rt))
		nErr = XFERR_REPORT_SHEET_CONFLICT;
	///End REPORT_SHEET_CHECK_CONFLICT
	if (CER_NO_ERROR != nErr)
	{
		bOKEnable = false;
		strErrMsg = nErr;
		if (CER_DATA_TOO_FEW == nErr)
			strErrMsg += ":" + 2;
		
	}
	
	
	return true;
	

}

int phm_Cox_make_tree(TreeNode& tr, LPCSTR lpcszVarName)
{
	
	return false;
	
}
//nGetNDialog = 0,1,2, corresponding to the XF GetN dropdown, 0 = dialog is not used
//you can change nRet to be one of XFEVT_PROCEED (defualt), XFEVT_ABORT, XFEVT_PROCEED_NO_DLG or XFEVT_PROCEED_WITH_DLG(when nGetNDialog = 0)
//nGetNDialog = -1 when Ok on GetN dialog to allow handler to preocess <auto> etc variables
//dwCntrl is combination of bits
//LTXF_FROM_GUI_MENU, LTXF_FROM_GUI_PROMPT, LTXF_FROM_AUTO_UPDATE, LTXF_FROM_FUNCTION, LTXF_THEME_USED, LTXF_CHANGE_PARAM
void phm_Cox_before_execute(TreeNode& trGetN, int nGetNDialog, int& nRet, int dwCntrl)
{
	
	//------ fisher 11/16/07 ADD_MAP_ID_TO_CHM
	if(is_use_origin_chm())
		trGetN.SetAttribute(STR_HELPID_ATTRIB, IDD_XF_phm_Cox);
	
	/// Max 3/19/08 SHOULD_NOT_RESET_VARIABLE_VALUE_IN_LT
	// It is the responsibility of users to specify each variable value (<auto> ones are exception) when run from LT
	/*
	///handle case that user select plot but not specify rd, when run from command
	if (nGetNDialog <= 0)
	{
		/// Max 3/7/08 LOCALIZE_PREDEFINED_STRING
		//if (!lstrcmp(trGetN.rd.strVal, "<optional>") && get_plot_number(trGetN))
		//{
			//trGetN.rd.strVal = "[<input>]<new name:=SurvFunc1>";
		//}
		if (compare_string_localization(trGetN.rd.strVal, STR_OPTIONAL) && get_plot_number(trGetN))
		{
			string strRDvalue = "[<input>]<new>";
			okutil_convert_localized_PDS(&strRDvalue);
			trGetN.rd.strVal = strRDvalue;
		}
		/// END LOCALIZE_PREDEFINED_STRING
	}
	*/
	/// END SHOULD_NOT_RESET_VARIABLE_VALUE_IN_LT
	
	///Jasmine 08/14/07 QA80-10207 REPORT_SHEET_CHECK_CONFLICT
	//if ( (dwCntrl&LTXF_THEME_USED) && nGetNDialog <= 0 )
	if (nGetNDialog <= 0 )
	{
		//if ( CER_NO_ERROR != cox_check_input(trGetN) )
		int nErr = cox_check_input(trGetN);
		if(CER_NO_ERROR == nErr && is_same_sheet(trGetN.rd, trGetN.rt))
			nErr = XFERR_REPORT_SHEET_CONFLICT;
		if(CER_NO_ERROR != nErr)
		{
			if((dwCntrl&LTXF_THEME_USED))
				nRet = XFEVT_PROCEED_WITH_DLG;
			else
			{
				warning_msg_box(nErr, false, 'E');
				nRet = XFEVT_ABORT;
			}
		}
	}
	///End REPORT_SHEET_CHECK_CONFLICT
	
	///Kyle 03/02/2009 SHOULD_NOT_CHANGE_OUTPUT_ON_GUI_ON_CHANGE_PARAMETERS
	if( nGetNDialog == 0 && (dwCntrl & LTXF_EXECUTE) && 0 == get_plot_number(trGetN) )
		trGetN.rd.strVal = STR_OPTIONAL;
	///End SHOULD_NOT_CHANGE_OUTPUT_ON_GUI_ON_CHANGE_PARAMETERS
}
//nEventID < 0 on event, like GETNE_ON_INIT, GETNE_ON_OK etc, >0 on change from control
//return 0 to do nothing, return PEVENT_GETN_RET_TO_CLOSE to close dialog
//dwEnables = bOKEnable, bBTN1Enable, bBTN2Enable, bBTN3Enable, , can set to false to disable OK/Apply buttons
//lpcszNodeName = the trGetN TreeNode tag name that generates the event, or is  if GETNE_ON_OK etc
//strAux = optional string for XF Wizard Bar, like DLG_NEED_CHANGE_GRID etc, it will be invalid if from internal node value change event
//strErrMsg = allowing messages to be shown
int phm_Cox_event1_ex(TreeNode& trGetN, int nRow, int nEventID, DWORD& dwEnables, LPCSTR lpcszNodeName, WndContainer& DynaCntrlContainer, string& strAux, string& strErrMsg)
{
	return false;
}
