/*------------------------------------------------------------------------------*
 * File Name: Wks_Utils.c														*
 * Creation: GJL 5/22/2002														*
 * Purpose: Origin C file containing Worksheet Utilities						*
 * Copyright (c) OriginLab Corp.	2002-2007									*
 * All Rights Reserved															*
 * 																				*
 * Modification Log:															*
 * EJP 09-30-2003 v7.5710 QA70-4073.63 FIX_FIND_EMPTY_TEXT_COL					*
 *------------------------------------------------------------------------------*/

////////////////////////////////////////////////////////////////////////////////////
// Included header files
//////////////////////////////////////////////////////////////////////////////////
//
#include <origin.h>       // EXIST_ codes
#include "Wks_Utils.h"      // Function prototypes and non-localized constants
#include <tree_utils.h>
 
//
////////////////////////////////////////////////////////////////////////////////////

/** 
		Gets the current selection from an Origin worksheet or Excel workbook in native
		Origin or Excel format. The selected range may be either contiguous or not. A comma
		separated selection string with one entry for each selected or partially selected
		column is returned along with the number of entries.
	Example:
		string strSelectedRanges;
		int iRet, iNumRanges;
		iRet = GetNonContiguousWksSelection( strSelectedRanges, iNumRanges );
	Parameters:
		strSelectedRanges=Output selected ranges
		iNumRanges=Number of selected ranges (one for each selected or partially selected column)
		bSelectAll=Returns entire worksheet/workbook sheet as selection range, default is FALSE
	Return:
		If successful returns WKS_UTILS_NO_ERROR and a formatted string containing the
		current Origin worksheet or Excel workbook selection. The selection string is a comma
		separated string with one entry for each selected or partially selected column. The number
		of entries is also returned. If there is a problem getting the selection then
		WKS_UTILS_BAD_SEL_ERROR, an empty string, and 0 are returned. If there is no selection
		then WKS_UTILS_NO_SEL_WARNING, an empty string, and 0 are returned.
*/
int GetNonContiguousWksSelection( string& strSelectedRanges, int& iNumRanges, BOOL bSelectAll ) // bSelectAll = FALSE
{
	int ii, iPageType, iSelectionType, iC1, iC2, iR1, iR2;
	string strWindowName, strSheetName, strColumnName, strCurrentRange;
	PageBase pbActiveWindow;
	Worksheet wksActiveWorksheet;
	vector<int> vSelCols;
	Column colN;
	
	waitCursor	wcCursor; // Put up a wait cursor

	// Initialize to NULL and 0
	strSelectedRanges.Empty();
	strSheetName.Empty();
	iNumRanges = 0;

	// Get active window...if not valid...
	pbActiveWindow = Project.Pages();
	if( !pbActiveWindow.IsValid() )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	
	// Get window name and type
	strWindowName = pbActiveWindow.GetName();
	iPageType = pbActiveWindow.GetType();
	
	// If window is not a worksheet or workbook...
	if( iPageType != EXIST_WKS && iPageType != EXIST_EXTERN_WKS )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code
	
	// Get active layer in worksheet or workbook...if error...
	wksActiveWorksheet = (Worksheet) Project.ActiveLayer();
	if( !wksActiveWorksheet.IsValid() )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	
	// If window is an Excel workbook...
	if( iPageType == EXIST_EXTERN_WKS )
	{
		// Update Origin from Excel
		if( !wksActiveWorksheet.UpdateOrigin() )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	

		// Get Excel sheet name...if error...
		if( !wksActiveWorksheet.GetName( strSheetName ) )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	}

	// If user wants entire worksheet/workbook as selection range...
	if( bSelectAll )
	{
		// Simulate entire worksheet/workbook as selected...
		iSelectionType = WKS_SEL_ALL; // Select entire worksheet/workbook
		iC1 = 0; // Set number of first column
		iNumRanges = wksActiveWorksheet.GetNumCols(); // Get number of columns
		vSelCols.SetSize( iNumRanges ); // Set all columns as selected
		for( ii = 0; ii < iNumRanges; ii++ )
			vSelCols[ii] = ii;
		iC2 = iNumRanges - 1; // Set number of last column
		wksActiveWorksheet.GetBounds(iR1, 0, iR2, -1); // Set number of first row and last row
	}
	else // Get current worksheet/workbook selection
	{
		// Get selection type and range
		iSelectionType = wksActiveWorksheet.GetSelection( iC1, iC2, iR1, iR2 );

		// If editing a cell...
		if( iSelectionType & WKS_SEL_EDIT )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	

		// Get list of columns selected or partially selected in worksheet/workbook
		if( !wksActiveWorksheet.GetSelectedColumns( vSelCols ) )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code
		
		// Get number of selected or partially selected columns
		iNumRanges = vSelCols.GetSize();

		// If there is no selection...
		if( iSelectionType == WKS_SEL_NONE || iNumRanges == WKS_SEL_NONE )
			return WKS_UTILS_NO_SEL_WARNING; // Return a warning code
	}

	for( ii = 0; ii < iNumRanges; ii++ )
	{
		// Get current column name...if column is not valid...
		colN.Attach( wksActiveWorksheet, vSelCols[ii] );
		if( !colN.IsValid() )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
		strColumnName = colN.GetName();
		
		// If entire worksheet or entire columns are selected then override worksheet
		//   selection/bounds with upper and lower bounds of each column
		if( iSelectionType == WKS_SEL_ALL || iSelectionType & WKS_SEL_COLUMN )
		{
			iR1 = colN.GetLowerBound();
			iR2 = colN.GetUpperBound();
		}

		// Build range for current column
		if( iPageType == EXIST_EXTERN_WKS ) // If Excel workbook format selection range as such
			strCurrentRange.Format( WKS_UTILS_EXCEL_CONTIG_SEL, strWindowName, strSheetName, strColumnName, iR1 + 1, strColumnName, iR2 + 1 );
		else // Else format selection range as Origin worksheet
			strCurrentRange.Format( WKS_UTILS_WKS_CONTIG_SEL, strWindowName, strColumnName, iR1 + 1, strColumnName, iR2 + 1 );

		// If not first range append comma separator
		if( ii != 0 )
			strSelectedRanges += ", ";
		
		// Append current range
		strSelectedRanges += strCurrentRange;
	}
	
	return WKS_UTILS_NO_ERROR; // Return no error and good selected ranges string
}

/**
		Gets the current selection from an Origin worksheet or Excel workbook in native
		Origin or Excel format. The selected range must be contiguous.
	Example:
		string strSelectedRange;
		int iRet;
		iRet = GetContiguousWksSelection( strSelectedRange );
	Parameters:
		strSelectedRange=Output selected range
		bSelectAll=Returns entire worksheet/workbook sheet as selection range, default is FALSE
	Return:
		If successful returns WKS_UTILS_NO_ERROR and a formatted string containing the
		current Origin worksheet or Excel workbook selection. If bSelectAll is TRUE then
		the entire worksheet is returned as the selection string. If there is a problem
		getting the selection or if the selected range is not contiguous then
		WKS_UTILS_BAD_SEL_ERROR and an empty string are returned. If there is no 
		selection then WKS_UTILS_NO_SEL_WARNING and an empty string are returned.
*/
int GetContiguousWksSelection( string& strSelectedRange, BOOL bSelectAll ) // bSelectAll = FALSE
{
	int iPageType, iSelectionType, iC1, iC2, iR1, iR2;
	string strWindowName, strSheetName, strC1Name, strC2Name;
	PageBase pbActiveWindow;
	Worksheet wksActiveWorksheet;
	Column colN;
	
	waitCursor	wcCursor; // Put up a wait cursor
	
	// Initialize to NULL
	strSelectedRange.Empty();
	strSheetName.Empty();
	
	// Get active window...if not valid...
	pbActiveWindow = Project.Pages();
	if( !pbActiveWindow.IsValid() )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	
	// Get window name and type
	strWindowName = pbActiveWindow.GetName();
	iPageType = pbActiveWindow.GetType();
	
	// If window is not a worksheet or workbook...
	if( iPageType != EXIST_WKS && iPageType != EXIST_EXTERN_WKS )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code

	// Get active layer in workbook...if error...
	wksActiveWorksheet = (Worksheet) Project.ActiveLayer();
	if( !wksActiveWorksheet.IsValid() )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	

	// If window is an Excel workbook...
	if( iPageType == EXIST_EXTERN_WKS )
	{
		// Update Origin from Excel
		if( !wksActiveWorksheet.UpdateOrigin() )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	

		// Get Excel sheet name...if error...
		if( !wksActiveWorksheet.GetName( strSheetName ) )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	}

	// If user wants entire worksheet/workbook as selection range...
	if( bSelectAll )
	{
		// Simulate entire worksheet/workbook as selected...
		iSelectionType = WKS_SEL_ALL;
		iC1 = 0;
		iC2 = wksActiveWorksheet.GetNumCols() - 1;
		wksActiveWorksheet.GetBounds(iR1, 0, iR2, -1);
	}
	else // Get current worksheet/workbook selection
	{
		iSelectionType = wksActiveWorksheet.GetSelection( iC1, iC2, iR1, iR2 );

		// If there is no selection...
		if( iSelectionType == WKS_SEL_NONE )
			return WKS_UTILS_NO_SEL_WARNING; // Else Return a warning code

		// If editing a cell or if the selection is not contiguous...
		if( iSelectionType & WKS_SEL_EDIT || iSelectionType & WKS_SEL_DISCONTIGUOUS )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code
	}

	// Get first column name...if column is not valid...
	colN.Attach( wksActiveWorksheet, iC1 );
	if( !colN.IsValid() )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	strC1Name = colN.GetName();
	
	// Get second column name...if column is not valid...
	colN.Attach( wksActiveWorksheet, iC2 );
	if( !colN.IsValid() )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	strC2Name = colN.GetName();

	if( iPageType == EXIST_EXTERN_WKS ) // If Excel workbook format selection range as such
		strSelectedRange.Format( WKS_UTILS_EXCEL_CONTIG_SEL, strWindowName, strSheetName, strC1Name, iR1 + 1, strC2Name, iR2 + 1 );
	else                 // Else format selection range as Origin worksheet
		strSelectedRange.Format( WKS_UTILS_WKS_CONTIG_SEL, strWindowName, strC1Name, iR1 + 1, strC2Name, iR2 + 1 );
	
	return WKS_UTILS_NO_ERROR; // Return no error and good selected range string
}

/**
		Parse a worksheet/workbook selection string and return the worksheet/workbook name, workbook sheet
		name (if Excel workbook), and column and row selection indices (0 based offsets, as integers).
	Example:
		See function omConvertWksToMatrixDirect in OMat.c for sample call.
	Parameters:
		strWksSelection=Input worksheet/workbook selection string
		strWksName=Output worksheet/workbook name
		strSheetName=Output workbook sheet name (if Excel workbook, else NULL)
		iC1=Output index of first selected column (0 based)
		iC2=Output index of last selected column (0 based)
		iR1=Output index of first selected row (0 based)
		iR2=Output index of last selected row (0 based)
	Return:
		Returns TRUE, worksheet/workbook name, workbook sheet name (if Excel), and column and row selection
		indices (0 based offsets, as integers) on success and FALSE on failure.
*/
BOOL ParseWksSelection( string strWksSelection, string& strWksName, string& strSheetName, int& iC1, int& iC2, int& iR1, int& iR2 )
{
	int ii;
	string str, strCol, strRow;
	
	strWksName.Empty();                  // Initialize
	strSheetName.Empty();
	iC1=iC2=iR1=iR2=-1;

	// Trim white space
	str = strWksSelection;
	str.TrimLeft();
	str.TrimRight();

	// Excel workbbook format:  str=="[Book1]Sheet1!$A$1:$B$7"
	// Origin worksheet format: str=="Worksheet_A[1]:B[7]"
	if( str.GetAt(0) == '[' )            // If first character is [ assume Excel workbook format
	{
		Worksheet wksBook;
		Worksheet wksSheet;
		string strCheckSheetName;

		// str=="[Book1]Sheet1!$A$1:$B$7"
		str = str.Mid(1);                // Strip off [ character
		
		// str=="Book1]Sheet1!$A$1:$B$7"
		ii = str.Find(']');              // Find ] character
		if( ii < 0 )                     // If no ] return error
			return FALSE;
		strWksName = str.Left(ii);       // Get Excel workbook name
		wksBook.Attach( strWksName );    // Attach to worksheet underlying workbook 
		if( !wksBook.IsValid() )         // If worksheet not valid return error
			return FALSE;
		str = str.Mid( ii + 1 );         // Strip off workbook name and ] character
		
		// str=="Sheet1!$A$1:$B$7"
		ii = str.Find('!');              // Find ! character
		if( ii < 0 )                     // If no ! return error
			return FALSE;
		strSheetName = str.Left(ii);     // Get Excel workbook sheet name from input string
		// Get Excel workbook sheet name from internal Origin workbook sheet
		wksSheet = (Worksheet) wksBook.GetPage().Layers( strSheetName ); // Get internal workbook sheet object by name
		if( !wksSheet.IsValid() )        // If worksheet not valid return error
			return FALSE;
		if( !wksSheet.GetName( strCheckSheetName ) ) // Get name of internal workbook sheet, if problem return error
			return FALSE;
		if( strSheetName.CompareNoCase( strCheckSheetName ) ) // If sheet names do not match return error 
			return FALSE;
 		str = str.Mid( ii + 1 );         // Strip off sheet name and ! character

		// str=="$A$1:$B$7"
		ii = str.Find('$');              // Find first $ character
		if( ii != 0 )                    // If $ is not next character return error
			return FALSE;		
		str = str.Mid( ii + 1 );         // Strip off first $ character
		
		// str=="A$1:$B$7"
		ii = str.Find('$');              // Find second $ character
		if( ii < 0 )                     // If no $ return error
			return FALSE;
		strCol = str.Left(ii);           // Get name of first column
		if( !wksSheet.Columns( strCol ).IsValid() ) // If first selected column is not valid return error  
			return FALSE;
		iC1 = wksSheet.Columns( strCol ).GetIndex(); // Get column number of first selected column
		if( iC1 < 0 )                    // If column not valid return error
			return FALSE;
		str = str.Mid( ii + 1 );         // Strip off name of first column and second $ character
		
		// str=="1:$B$7"
		ii = str.Find(':');              // Find : character
		if( ii < 0 )                     // If no : return error
			return FALSE;
		strRow = str.Left(ii);           // Get first row number (as string)
		iR1 = atoi( strRow ) - 1;        // Get first row number (as integer, subtract 1 for 0 based offset )
		strRow.Format( "%d", iR1 + 1 );  // Rebuild strRow (as string from integer) for check
		if( strRow.Compare( str.Left(ii) ) ) // If not same return error 
			return FALSE;
		str = str.Mid( ii + 1 );         // Strip off first row number and : character
		
		// str=="$B$7"
		ii = str.Find('$');              // Find third $ character		
		if( ii != 0 )                    // If $ is not next character return error
			return FALSE;
		str = str.Mid( ii + 1 );         // Strip off third $ character
		
		// str=="B$7"
		ii = str.Find('$');              // Find fourth $ character
		if( ii < 0 )                     // If no $ return error
			return FALSE;
		strCol = str.Left(ii);           // Get name of second column
		if( !wksSheet.Columns( strCol ).IsValid() ) // If second selected column is not valid return error
			return FALSE;
		iC2 = wksSheet.Columns( strCol ).GetIndex(); // Get column number of second selected column
		if( iC2 < 0 )                    // If column not valid return error
			return FALSE;
		str = str.Mid( ii + 1 );         // Strip off name of second column and fourth $ character
		
		// str=="7" - Remaining characters are second row number (as string)
		iR2 = atoi( str ) - 1;           // Get second row number (as integer, subtract 1 for 0 based offset)
		strRow.Format( "%d", iR2 + 1 );  // Rebuild strRow (as string from integer) for check
		if( strRow.Compare( str ) )      // If not same return error 
			return FALSE;
		
		// If column numbers are not in correct bounds return error
		if( iC1 < 0 || iC1 > iC2 || iC2 >= wksSheet.GetNumCols() )
			return FALSE;

		// If row numbers are not in correct bounds return error
		if( iR1	< 0 || iR1 > iR2 || iR2 >= wksSheet.GetNumRows() )
			return FALSE;
	}
	else                                 // Else first character not [ assume Origin worksheet format
	{
		Worksheet wksWorksheet;
		
		// str=="Worksheet_A[1]:B[7]"
		ii = str.Find('_');              // Find _ character
		if( ii < 0 )                     // If no _ return error
			return FALSE;		
		strWksName = str.Left(ii);       // Get Origin worksheet name
		wksWorksheet.Attach( strWksName );// Attach to worksheet 
		if( !wksWorksheet.IsValid() )    // If worksheet not valid return error
			return FALSE;		
		str = str.Mid( ii + 1 );         // Strip off worksheet name and _ character

		// str=="A[1]:B[7]"
		ii = str.Find('[');              // Find first [ character
		if( ii < 0 )                     // If no [ return error
			return FALSE;
		strCol = str.Left(ii);           // Get name of first column
		if( !wksWorksheet.Columns( strCol ).IsValid() ) // If first selected column is not valid return error  
			return FALSE;
		iC1 = wksWorksheet.Columns( strCol ).GetIndex(); // Get column number of first selected column
		if( iC1 < 0 )                    // If not valid return error
			return FALSE;
		str = str.Mid( ii + 1 );         // Strip off name of first column and first [ character
		
		// str=="1]:B[7]"		
		ii = str.Find(']');              // Find first ] character
		if( ii < 0 )                     // If no ] return error
			return FALSE;
		strRow = str.Left(ii);           // Get first row number (as string)
		iR1 = atoi( strRow ) - 1;        // Get first row number (as integer, subtract 1 for 0 based offset)
		strRow.Format( "%d", iR1 + 1 );  // Rebuild strRow (as string from integer) for check
		if( strRow.Compare( str.Left(ii) ) ) // If not same return error 
			return FALSE;
		str = str.Mid( ii + 1 );         // Strip off first row number and first ] character
		
		// str==":B[7]"		
		ii = str.Find(':');              // Find : character
		if( ii != 0 )                    // If : is not next character return error
			return FALSE;
		str = str.Mid( ii + 1 );         // Strip off : character

		// str=="B[7]"
		ii = str.Find('[');              // Find second [ character
		if( ii < 0 )                     // If no [ return error
			return FALSE;
		strCol = str.Left(ii);           // Get name of second column
		if( !wksWorksheet.Columns( strCol ).IsValid() ) // If second selected column is not valid return error
			return FALSE;
		iC2 = wksWorksheet.Columns( strCol ).GetIndex(); // Get column number of second selected column
		if( iC2 < 0 )                    // If not valid return error
			return FALSE;
		str = str.Mid( ii + 1 );         // Strip off name of second column and second [ character
		
		// str=="7]"
		ii = str.Find(']');              // Find second ] character
		if( ii < 0 )                     // If no ] return error
			return FALSE;
		strRow = str.Left(ii);           // Get second row number (as string)
		iR2 = atoi( strRow ) - 1;        // Get second row number (as integer, subtract 1 for 0 based offset)
		strRow.Format( "%d", iR2 + 1 );  // Rebuild strRow (as string from integer) for check
		if( strRow.Compare( str.Left(ii) ) )// If not same return error 
			return FALSE;
		str = str.Mid(ii);               // Strip off second row number
		
		// str=="]"
		if( str.Compare( "]" ) )         // If last character is not ] return error
			return FALSE;
		
		// If column numbers are not in correct bounds return error
		if( iC1 < 0 || iC1 > iC2 || iC2 >= wksWorksheet.GetNumCols() )
			return FALSE;
		
		// If row numbers are not in correct bounds return error	
		if( iR1	< 0 || iR1 > iR2 || iR2 >= wksWorksheet.GetNumRows() )
			return FALSE;
	}
	
	return TRUE;
}

/**
		Determine whether or not the specified elements of a vectorbase derived object are uniformly
		spaced.
	Example:
		See function omConvertWksToMatrixDirect in OMat.c for sample call.
	Parameters:
		dDelta=Output difference between elements if uniform spacing else 0.0
		vIn=Input vectorbase derived object
		dTol=Input relative tolerance between 0 and 1 (default is 0.05)
		i1=Input beginning 0 based index of vector to test (default is 0)
		i2=Input ending 0 based index of vector to test (default -1 tests to last element)
	Return:
		Returns TRUE and NANUM if only one element is tested, returns TRUE and the difference between the first
		two elements tested if the spacing is uniform, returns FALSE and the difference between the first two
		elements tested if the spacing is not uniform, or returns FALSE and NANUM if there is an error.
*/
BOOL VectorHasUniformSpacing( double& dDelta, vectorbase& vIn, double dTol, int i1, int i2 ) // dTol = 0.05, i1 = 0, i2 = -1
{
	int ii;
	double dDeltaLo, dDeltaHi, dDeltaCur;

	dDelta = NANUM;                             // Initialized value
	
	if( i2 == -1 )                              // If default i2 loop to end of vector
		i2 = vIn.GetSize() - 1;
	
	if( dTol < 0.0 || dTol > 1.0 )              // If relative tolerance is not between 0 and 1 return FALSE and NANUM
		return FALSE;
	
	if( i1 < 0 || i2 < i1  || i2 >= vIn.GetSize() ) // If vector indices are illogical return FALSE and NANUM
		return FALSE;

	if( i1 == i2 )                              // If only testing one element return TRUE and NANUM
		return TRUE;
	
	dDelta = vIn[i1] - vIn[ i1 + 1 ];           // Get delta between first two elements
	if( dDelta < 0 )                            // If dDelta is negative...
	{
		dDeltaLo = ( 1.0 + dTol ) * dDelta;     // Compute lower absolute dDelta from relative tolerance
		dDeltaHi = ( 1.0 - dTol ) * dDelta;     // Compute higher absolute dDelta from relative tolerance
	}
	else                                        // Else if dDelta is not negative...
	{
		dDeltaLo = ( 1.0 - dTol ) * dDelta;     // Compute lower absolute dDelta from relative tolerance
		dDeltaHi = ( 1.0 + dTol ) * dDelta;     // Compute higher absolute dDelta from relative tolerance
	}

	for( ii = i1 + 1; ii < i2; ii++ )           // For each element in specified range of vector...
	{
		dDeltaCur = vIn[ii] - vIn[ ii + 1 ];    // Get absolute delta between current and next element 
		if( dDeltaCur < dDeltaLo || dDeltaCur > dDeltaHi ) // If current absolute delta is not within tolerance... 
			return FALSE;                       // Elements of vector not uniformly spaced
	}
	
	return TRUE;                                // Elements of vector are uniformly spaced (within tolerance)
}

/**
	Rename the worksheet to the file name.
*/
BOOL RenameToFileName(Worksheet& wks, LPCSTR lpcszFileName)
{
	BOOL bRet = FALSE;
	string strNewName;
	
	//---- CPY 5/1/03
	//strNewName = GetFilename(lpcszFileName);
	//strNewName = GetFilenameWithoutExt(strNewName);
	strNewName = GetFileName(lpcszFileName, TRUE);
	//----
	if( strNewName.IsEmpty() )
		return bRet;
			
	Page page = wks.GetPage();
	if( page && page.IsValid() )
	{		
		page.Rename(strNewName);			// rename the worksheet	
		page.Label = lpcszFileName;			// set worksheet label		
		page.LT_execute("Page.title = 3");	// turn on both title and label
		bRet = TRUE;
	}
	
	return bRet;
}

/**
		Add the current worksheet/workbook sheet selection ranges to subnodes in the specified
		TreeNode.
	Example:
		See function call in StatisticsOn.c.
	Parameters:
		tr=Input TreeNode to which selection range subnodes will added
		bAppend=Input option to append new selection range subnodes (true) or to reset tree and
			add starting in first subnode (default false)
		bSelectAll=Input option to select entire worksheet/workbook sheet if there is no selection (true)
			or to return a no selection warning code if there is no selection (default false)
		bColWise=Input option to add row wise selections (false) or column wise selections (default true)
	Return:
		Returns WKS_UTILS_NO_ERROR on success and a WKS_UTILS warning or error code on failure.
*/
int AddWksSelectionsToTreeNode(TreeNode& tr, bool bAppend, bool bSelectAll, bool bColWise) // bAppend = false, bSelectAll = false, bColWise = true
{
	string strRange, strNodeName, strSelectedRanges;
	string strWksName, strSheetName;
	int ii, iRet, iBeginNodeNum, iNumRanges;
	int iC1, iC2, iR1, iR2; 
	TreeNode trSubNode;	// Must use TreeNode here since Tree is always a standalone copy

	if( tr )                                                                  // If TreeNode is valid...
	{
		if( bColWise )                                                         // If user wants column wise selection...
			iRet = GetNonContiguousWksSelection(strSelectedRanges, iNumRanges); // Get current selection range
		else                                                                   // Else user wants row wise selection...
			iRet = GetRowWiseWksSelection(strSelectedRanges, iNumRanges);      // Get current selection range
		
		if( iRet )                                                             // If no selection or bad selection...
		{
			if( bSelectAll && ( iRet == WKS_UTILS_NO_SEL_WARNING ) )           // If bSelectAll is true and no selection...
			{
				if( bColWise )                                                 // If user wants column wise selection... 
					iRet = GetNonContiguousWksSelection(strSelectedRanges, iNumRanges, TRUE); // Get entire worksheet/workbook as selection
				else                                                           // Else user wants row wise selection...
					iRet = GetRowWiseWksSelection(strSelectedRanges, iNumRanges, TRUE); // Get entire worksheet/workbook as selection
			}
	
			if( iRet )                                                         // Now if no selection or bad selection...
			{
				if( !bAppend )                                                 // If user is not appending...
				{
					tr.Reset();                                                // Reset node
					tr.Range1.Display.strVal = "";                             // Indicate no selection
					tr.Range1.C1.nVal = -1;
					tr.Wks.strVal = "";
				}
				return iRet;                                                   // Return warning or error code
			}
		}
	
		if( bAppend )                                                          // If user wants to append...
			iBeginNodeNum = tr.GetNodeCount() - 1;                             // Set begining node number - 1 for Wks node 
		else                                                                   // Else not appending...
		{
			tr.Reset();                                                        // Reset TreeNode
			iBeginNodeNum = 0;                                                 // Set begining node number to 0
		}
	
		for( ii = 0; ii < iNumRanges; ii++ )                                   // For each selected range...
		{
			strRange = strSelectedRanges.GetToken( ii, ',' );                  // Get range
			ParseWksSelection(strRange, strWksName, strSheetName, iC1, iC2, iR1, iR2); // Parse range into column and row indices
			strNodeName.Format("Range%d", ii + iBeginNodeNum + 1);             // Get subnode name
			//strNodeName.Format("%s%d", TREE_Range, ii + iBeginNodeNum);                 // Get subnode name
			trSubNode = tr.AddNode(strNodeName);                               // Add subnode to tree
			trSubNode.Display.strVal = strRange;                               // Assign display and index nodes
			trSubNode.C1.nVal = iC1;
			trSubNode.C2.nVal = iC2;
			trSubNode.R1.nVal = iR1;
			trSubNode.R2.nVal = iR2;
		}
		
		tr.Wks.strVal = strWksName;
	}
	else
		return WKS_UTILS_INVALID_TREENODE_ERROR;                               // Else TreeNode is invalid return error
	
	return WKS_UTILS_NO_ERROR;
}

/**
		Get the current worksheet/workbook sheet selection range and set the specified TreeNode
		with it. The selection range must be contained within a single worksheet/workbook column
		and the TreeNode must be passed by reference. Row ranges can optionally be shown or not
		be shown.
	Example:
		See function call in StatisticsOn.c.
	Parameters:
		tr=Input reference to TreeNode whose strVal is set
		bShowRows=Input option to show or not show row ranges, default is true (show row ranges)
	Return:
		Returns WKS_UTILS_NO_ERROR on success and a WKS_UTILS warning or error code on failure.
*/
int SetTreeNodeWithSingleColumnWksSelection(TreeNode& tr, bool bShowRows)
{
	string strSelectedRange;
	string strWksName, strSheetName;
	int iRet, iNumRanges;
	int iC1, iC2, iR1, iR2; 

	if( tr )                                                               // If TreeNode is valid...
	{
		tr.Reset();
		iRet = GetNonContiguousWksSelection(strSelectedRange, iNumRanges); // Get current selection range
		if( !iRet )                                                        // If selection exists...
		{
			if( iNumRanges == 1 )                                          // If selection range is in single column...
			{
				ParseWksSelection(strSelectedRange, strWksName, strSheetName, iC1, iC2, iR1, iR2); // Parse range into column and row indices
				if( bShowRows )
				{
					tr.Display.strVal = strSelectedRange;                  // Assign display range node
					tr.C1.nVal = iC1;                                      // Assign index nodes
					tr.C2.nVal = iC2;
					tr.R1.nVal = iR1;
					tr.R2.nVal = iR2;
				}
				else
				{
					tr.Display.strVal = GetSingleColumnRangeWithoutRows(strSelectedRange); // Assign display range node without rows
					tr.C1.nVal = iC1;                                      // Assign index nodes
					tr.C2.nVal = iC1;
					tr.R1.nVal = -1;
					tr.R2.nVal = -1;
				}
				tr.Parent().Wks.strVal = strWksName;
				return WKS_UTILS_NO_ERROR;
			}
			else                                                           // Else multiple columns return error
				iRet = WKS_UTILS_NO_SINGLE_COL_ERROR;
		}
		tr.Display.strVal = "";                                            // Indicate no selection
		tr.C1.nVal = -1;
	}
	else                                                                   // Else no TreeNode return error...
		iRet = WKS_UTILS_INVALID_TREENODE_ERROR;

	return iRet;
}

/** 
		Gets the current row wise selection from an Origin worksheet or Excel workbook in
		native Origin or Excel format. The selected range must be contiguous. A comma
		separated selection string with one entry for each selected or partially selected
		row is returned along with the number of entries.
	Example:
		string strSelectedRanges;
		int iRet, iNumRowRanges;
		iRet = GetRowWiseWksSelection(strSelectedRanges, iNumRowRanges);
	Parameters:
		strSelectedRanges=Output selected row ranges
		iNumRowRanges=Number of selected row ranges (one for each selected or partially selected row)
		bSelectAll=Returns entire worksheet/workbook sheet as selection range, default is FALSE
	Return:
		If successful returns WKS_UTILS_NO_ERROR and a formatted string containing the
		current Origin worksheet or Excel workbook selection. The selection string is a comma
		separated string with one entry for each selected or partially selected row. The number
		of entries is also returned. If there is a problem getting the selection then
		WKS_UTILS_BAD_SEL_ERROR, an empty string, and 0 are returned. If there is no selection
		then WKS_UTILS_NO_SEL_WARNING, an empty string, and 0 are returned.
*/
int GetRowWiseWksSelection( string& strSelectedRanges, int& iNumRowRanges, BOOL bSelectAll ) // bSelectAll = FALSE
{
	int ii, iPageType, iSelectionType, iC1, iC2, iR1, iR2;
	string strWindowName, strSheetName, strCol1Name, strCol2Name, strCurrentRange;
	PageBase pbActiveWindow;
	Worksheet wksActiveWorksheet;
	Column col1, col2;

	// Initialize to NULL and 0
	strSelectedRanges.Empty();
	strSheetName.Empty();
	iNumRowRanges = 0;

	// Get active window...if not valid...
	pbActiveWindow = Project.Pages();
	if( !pbActiveWindow.IsValid() )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	
	// Get window name and type
	strWindowName = pbActiveWindow.GetName();
	iPageType = pbActiveWindow.GetType();
	
	// If window is not a worksheet or workbook...
	if( iPageType != EXIST_WKS && iPageType != EXIST_EXTERN_WKS )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code
	
	// Get active layer in worksheet or workbook...if error...
	wksActiveWorksheet = (Worksheet) Project.ActiveLayer();
	if( !wksActiveWorksheet.IsValid() )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	
	// If window is an Excel workbook...
	if( iPageType == EXIST_EXTERN_WKS )
	{
		// Update Origin from Excel
		if( !wksActiveWorksheet.UpdateOrigin() )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	

		// Get Excel sheet name...if error...
		if( !wksActiveWorksheet.GetName( strSheetName ) )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	}

	// If user wants entire worksheet/workbook as selection range...
	if( bSelectAll )
	{
		// Simulate entire worksheet/workbook as selected...
		iSelectionType = WKS_SEL_ALL; // Select entire worksheet/workbook
		iC1 = 0; // Set number of first column
		iC2 = wksActiveWorksheet.GetNumCols() - 1; // Set number of last column
		wksActiveWorksheet.GetBounds(iR1, 0, iR2, -1); // Set number of first row and last row
	}
	else // Get current worksheet/workbook selection
	{
		// Get selection type and range
		iSelectionType = wksActiveWorksheet.GetSelection( iC1, iC2, iR1, iR2 );

		// If editing a cell...
		if( iSelectionType & WKS_SEL_EDIT )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	

		// If the selection is discontiguous...
		if( iSelectionType & WKS_SEL_DISCONTIGUOUS )
			return WKS_UTILS_BAD_SEL_ERROR; // Return an error code

		// If there is no selection...
		if( iSelectionType == WKS_SEL_NONE )
			return WKS_UTILS_NO_SEL_WARNING; // Return a warning code
	}

	iNumRowRanges = iR2 - iR1 + 1;

	// Get first column name...if column is not valid...
	col1.Attach( wksActiveWorksheet, iC1 );
	if( !col1.IsValid() )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	strCol1Name = col1.GetName();

	// Get second column name...if column is not valid...
	col2.Attach( wksActiveWorksheet, iC2 );
	if( !col2.IsValid() )
		return WKS_UTILS_BAD_SEL_ERROR; // Return an error code	
	strCol2Name = col2.GetName();
	
	for( ii = iR1; ii < iR2 + 1; ii++ )
	{
		// Build range for current column
		if( iPageType == EXIST_EXTERN_WKS ) // If Excel workbook format selection range as such
			strCurrentRange.Format( WKS_UTILS_EXCEL_CONTIG_SEL, strWindowName, strSheetName, strCol1Name, ii + 1, strCol2Name, ii + 1 );
		else // Else format selection range as Origin worksheet
			strCurrentRange.Format( WKS_UTILS_WKS_CONTIG_SEL, strWindowName, strCol1Name, ii + 1, strCol2Name, ii + 1 );

		// If not first range append comma separator
		if( ii != iR1 )
			strSelectedRanges += ", ";
		
		// Append current range
		strSelectedRanges += strCurrentRange;
	}
	
	return WKS_UTILS_NO_ERROR; // Return no error and good selected ranges string
}

int FindEmptyColumn(Worksheet &wks, int nStartCol)
{
	for( int nCol = nStartCol; nCol < wks.GetNumCols(); nCol++ )
	{
		/// EJP 09-30-2003 v7.5710 QA70-4073.63 FIX_FIND_EMPTY_TEXT_COL
		///Dataset ds(wks.Columns(nCol));
		///if( -1 == ds.GetUpperIndex() )
		///	return nCol;
		int i1, i2;
		Column col;
		col = wks.Columns(nCol);
		if( col && col.GetRange(i1, i2) )
		{
			if( -1 == i2 )
				return nCol;
		}
		/// end FIX_FIND_EMPTY_TEXT_COL
	}
	return -1; // no empty columns
}

/**
		Parse a worksheet/workbook selection string and return the data contained in the
		worksheet selection range.
	Parameters:
		strWksSelection=Input worksheet/workbook selection string having form "[Book1]Sheet1!$A$1:$B$7"
			or "Worksheet_A[1]:B[7]" 
		mData=Output data
	Return:
		Returns WKS_UTILS_NO_ERROR and a matrix containing the data contained in the worksheet
		selection range on success and returns WKS_UTILS_BAD_SEL_ERROR on failure.
*/
int GetDataInWksSelectionRange(string strWksSelection, matrix& mData)
{
	string strWksName, strSheetName;
	int iC1, iC2, iR1, iR2, iPageType;
	WorksheetPage wpWksPage;
	Worksheet wksWorksheet;
                                                               // Parse worksheet selection range...if valid...
	if( ParseWksSelection(strWksSelection, strWksName, strSheetName, iC1, iC2, iR1, iR2) )
	{
		wpWksPage = Project.WorksheetPages(strWksName);		   // Get WorksheetPage
		if( wpWksPage )                                        // If WorksheetPage is valid...
		{
			iPageType = wpWksPage.GetType();			       // Get window type
			if( EXIST_WKS == iPageType )			           // If window is a worksheet...
				wksWorksheet = (Worksheet) wpWksPage.Layers(); // Get active layer in worksheet
			else if( EXIST_EXTERN_WKS == iPageType )           // Else if window is an Excel workbook...
			{
				wksWorksheet = (Worksheet) wpWksPage.Layers(strSheetName); // Get specified layer in workbook
				wksWorksheet.UpdateOrigin();                   // Update Origin from Excel
			}
			if( wksWorksheet )                                 // If worksheet is valid...
				if( mData.CopyFromWks(wksWorksheet, iC1, iC2, iR1, iR2) ) // Copy data from worksheet
					return WKS_UTILS_NO_ERROR;                 // If copy is successful...return with no error
		}
	}

	return WKS_UTILS_BAD_SEL_ERROR; // "Else"...return an error code
}

/**
		Get a single column worksheet/workbook range without rows.
	Parameters:
		strRange=Input worksheet/workbook range with row ranges
	Return:
		Returns a single column worksheet/workbook range without rows.
*/
string GetSingleColumnRangeWithoutRows(const string& strRange)
{
	string str;
	
	if( strRange[0] == '[' )                 // If first character is [ then Excel range...
	{
		str = strRange.GetToken(0, '$');     // Get book and sheet
		str += "$";                          // Add token separator back 
		str += strRange.GetToken(1, '$');    // Add first column name without rows
	}
	else                                     // Else must be Origin worksheet
		str = strRange.GetToken(0, '[');     // Get worksheet and first column names without rows 

	return str;
}

/**
		Parse a column selection string and return the worksheet/workbook name, workbook sheet
		name (if Excel workbook), and column index (0 based offset, as an integer).
	Parameters:
		strColSelection=Input column selection string
		strWksName=Output worksheet/workbook name
		strSheetName=Output workbook sheet name (if Excel workbook, else NULL)
		iC1=Output index of selected column (0 based)
	Return:
		Returns TRUE, worksheet/workbook name, workbook sheet name (if Excel), and column index
		(0 based offset, as an integer) on success and FALSE on failure.
*/
BOOL ParseColSelection( string strColSelection, string& strWksName, string& strSheetName, int& iC1 )
{
	int ii;
	string str;

	strWksName.Empty();                  // Initialize
	strSheetName.Empty();
	iC1=-1;

	// Trim white space
	str = strColSelection;
	str.TrimLeft();
	str.TrimRight();

	// Excel workbbook format:  str=="[Book1]Sheet1!$A"
	// Origin worksheet format: str=="Worksheet_A"
	if( str.GetAt(0) == '[' )            // If first character is [ assume Excel column format
	{
		Worksheet wksBook;
		Worksheet wksSheet;
		string strCheckSheetName;

		// str=="[Book1]Sheet1!$A"
		str = str.Mid(1);                // Strip off [ character
		
		// str=="Book1]Sheet1!$A"
		ii = str.Find(']');              // Find ] character
		if( ii < 0 )                     // If no ] return error
			return FALSE;
		strWksName = str.Left(ii);       // Get Excel workbook name
		wksBook.Attach( strWksName );    // Attach to worksheet underlying workbook 
		if( !wksBook.IsValid() )         // If worksheet not valid return error
			return FALSE;
		str = str.Mid( ii + 1 );         // Strip off workbook name and ] character
		
		// str=="Sheet1!$A"
		ii = str.Find('!');              // Find ! character
		if( ii < 0 )                     // If no ! return error
			return FALSE;
		strSheetName = str.Left(ii);     // Get Excel workbook sheet name from input string
		// Get Excel workbook sheet name from internal Origin workbook sheet
		wksSheet = (Worksheet) wksBook.GetPage().Layers( strSheetName ); // Get internal workbook sheet object by name
		if( !wksSheet.IsValid() )        // If worksheet not valid return error
			return FALSE;
		if( !wksSheet.GetName( strCheckSheetName ) ) // Get name of internal workbook sheet, if problem return error
			return FALSE;
		if( strSheetName.CompareNoCase( strCheckSheetName ) ) // If sheet names do not match return error 
			return FALSE;
 		str = str.Mid( ii + 1 );         // Strip off sheet name and ! character

		// str=="$A"
		ii = str.Find('$');              // Find first $ character
		if( ii != 0 )                    // If $ is not next character return error
			return FALSE;		
		str = str.Mid( ii + 1 );         // Strip off $ character
		
		// str=="A"
		if( !wksSheet.Columns( str ).IsValid() ) // If selected column is not valid return error
			return FALSE;
		iC1 = wksSheet.Columns( str ).GetIndex(); // Get column number of selected column
		if( iC1 < 0 )                   // If column not valid return error
			return FALSE;
	}
	else                                 // Else first character not [ assume Origin column format
	{
		Worksheet wksWorksheet;
		
		// str=="Worksheet_A"
		ii = str.Find('_');              // Find _ character
		if( ii < 0 )                     // If no _ return error
			return FALSE;		
		strWksName = str.Left(ii);       // Get Origin worksheet name
		wksWorksheet.Attach( strWksName );// Attach to worksheet 
		if( !wksWorksheet.IsValid() )    // If worksheet not valid return error
			return FALSE;		
		str = str.Mid( ii + 1 );         // Strip off worksheet name and _ character

		// str=="A"
		if( !wksWorksheet.Columns( str ).IsValid() ) // If selected column is not valid return error  
			return FALSE;
		iC1 = wksWorksheet.Columns( str ).GetIndex(); // Get column number of selected column
		if ( iC1 < 0 )                   // If not valid return error
			return FALSE;
	}

	return TRUE; // Success!
}

/**
		Add the current selected columns to subnodes in the specified TreeNode.
	Example:
		See function call in StatisticsOn.c.
	Parameters:
		tr=Input TreeNode to which selected columns will added
		bAppend=Input option to append new columns (true) or to reset tree and
			add starting in first subnode (default false)
		bSelectAll=Input option to select all columns in worksheet/workbook sheet if there are
			none selected (true) or to return a no selection warning code if there is no selection
			(default false)
	Return:
		Returns WKS_UTILS_NO_ERROR on success and a WKS_UTILS warning or error code on failure.
*/
int AddColSelectionsToTreeNode(TreeNode& tr, bool bAppend, bool bSelectAll) // bAppend = false, bSelectAll = false
{
	string strRange, strNodeName, strSelectedRanges;
	string strWksName, strSheetName;
	int ii, iRet, iBeginNodeNum, iNumRanges;
	int iC1, iC2, iR1, iR2; 
	TreeNode trSubNode;	// Must use TreeNode here since Tree is always a standalone copy

	if( tr )                                                                   // If TreeNode is valid...
	{
		iRet = GetNonContiguousWksSelection(strSelectedRanges, iNumRanges);    // Get current selection range

		if( iRet )                                                             // If no selection or bad selection...
		{
			if( bSelectAll && ( iRet == WKS_UTILS_NO_SEL_WARNING ) )           // If bSelectAll is true and no selection...
				iRet = GetNonContiguousWksSelection(strSelectedRanges, iNumRanges, TRUE); // Get entire worksheet/workbook as selection

			if( iRet )                                                         // Now if no selection or bad selection...
			{
				if( !bAppend )                                                 // If user is not appending...
				{
					tr.Reset();                                                // Reset node
					tr.Range1.Display.strVal = "";                             // Indicate no selection
					tr.Range1.C1.nVal = -1;
					tr.Wks.strVal = "";
				}
				return iRet;                                                   // Return warning or error code
			}
		}
	
		if( bAppend )                                                          // If user wants to append...
			iBeginNodeNum = tr.GetNodeCount() - 1;                             // Set begining node number - 1 for Wks node
		else                                                                   // Else not appending...
		{
			tr.Reset();                                                        // Reset TreeNode
			iBeginNodeNum = 0;                                                 // Set begining node number to 0
		}
	
		for( ii = 0; ii < iNumRanges; ii++ )                                   // For each selected range...
		{
			strRange = strSelectedRanges.GetToken( ii, ',' );                  // Get range
			ParseWksSelection(strRange, strWksName, strSheetName, iC1, iC2, iR1, iR2); // Parse range into column and row indices
			strRange = GetSingleColumnRangeWithoutRows(strRange);              // Only add single column range without rows                       
			strNodeName.Format("Range%d", ii + iBeginNodeNum + 1);             // Get subnode name
			trSubNode = tr.AddNode(strNodeName);                               // Add subnode to tree
			trSubNode.Display.strVal = strRange;                               // Assign display and index nodes
			trSubNode.C1.nVal = iC1;
			trSubNode.C2.nVal = iC1;
			trSubNode.R1.nVal = -1;
			trSubNode.R2.nVal = -1
		}
		
		tr.Wks.strVal = strWksName;
	}
	else                                                                       // Else TreeNode is invalid return error...
		return WKS_UTILS_INVALID_TREENODE_ERROR;

	return WKS_UTILS_NO_ERROR;                                                 // Success!
}




typedef	bool	(*PFN_tree_reporting_prepare_targets)(TreeNode &trRep, DWORD &dwTarget, string &strNoteWnd, BOOL *pbAppendToNote);




/**
		It outputs a report from a worksheet to a Note window and/or to the Ouput log, based on the
		parameters supplied by the trRep tree argument.
	Example:
		// Make sure that there is a worksheet with the name "Data1" with at least five columns and
		// 35 rows before running this example.
		void	test_wks_report()
		{
			Worksheet		wks("Data1");
			
			Tree			trWhole;
			
			// How?
			trWhole.Reporting.Settings.Outputlog.Mode.nVal = 0;
			
			trWhole.Reporting.Settings.Note.Name.strVal = "Note1";
			trWhole.Reporting.Settings.Note.Mode.nVal = 1;
			//
			
			// What?
			trWhole.Reporting.Contents.Wks.Range1.r1.nVal = 17;
			trWhole.Reporting.Contents.Wks.Range1.r2.nVal = 22;
			trWhole.Reporting.Contents.Wks.Range1.c1.nVal = 1;
			trWhole.Reporting.Contents.Wks.Range1.c2.nVal = 3;
			
			trWhole.Reporting.Contents.Wks.Range1.r1.nVal = 25;
			trWhole.Reporting.Contents.Wks.Range1.r2.nVal = 33;
			trWhole.Reporting.Contents.Wks.Range1.c1.nVal = 2;
			trWhole.Reporting.Contents.Wks.Range1.c2.nVal = 4;
			//
		
			/////
			if (trWhole.Reporting)
				wks_report(wks, trWhole.Reporting, TRUE);
			/////
		}
	Parameters:
		wksOutput=the worksheet to be output.
	Return:
		Returns TRUE if OK, otherwise FALSE.
*/
BOOL  	wks_report(Worksheet& wksOutput, TreeNode &trRep, BOOL bBatchProcessing)
{
	if (!trRep || !wksOutput)
	{
		ASSERT(FALSE);
		return FALSE;
	}
	
	
	TreeNode		trSett = trRep.Settings;
	if (!trSett)
	{
		out_str("No Settings branch, don't know how to report.");
		return TRUE;
	}
	
	// Printing:
	if( bBatchProcessing )
	{
		TreeNode	trWks = trRep.Settings.Wks;
		if( trWks )
		{
			WorksheetPage WksPage;
			wksOutput.GetParent(WksPage);

			if( WksPage )
				WksPage.Print(trWks);
		}
	}

	
	TreeNode		trCntsWks = trRep.Settings.Wks;
	if (!trCntsWks)
	{
		out_str("No Contents.Wks branch, nothing to report.");
		return TRUE;
	}
	

	/*
	// Output log:
	BOOL			bOutputLogReplace = FALSE;
	DWORD			dwTarget = 0;
	if (trSett.Outputlog)
	{
		dwTarget |= TYPETARGET_OUTPUTLOG;
		if (trSett.Outputlog.Mode)
			bOutputLogReplace = trSett.Outputlog.Mode.nVal;
		
		// Here need to empty OutputLog if bOutputLogReplace.
	}

	// Notes window:
	string			strNoteWnd;
	BOOL			bNoteTextReplace = FALSE;
	if (trSett.Note)
	{
		if (trSett.Note.Mode)
			bNoteTextReplace = trSett.Note.Mode.nVal;
		
		if (trSett.Note.WINDOWNAME)
			strNoteWnd = trSett.Note.WINDOWNAME.strVal;
		
		if ( !strNoteWnd.IsEmpty() && get_create_Note(strNoteWnd) )
		{
			dwTarget |= TYPETARGET_NAMED_WINDOW;
			
			Note	nt(strNoteWnd);
			if ( bNoteTextReplace )
				nt.Text = "";	// empty
		}
	}
	*/
	DWORD		dwTarget = 0;	// where should the report go
	string		strNoteWnd;
	
	
	PFN_tree_reporting_prepare_targets	pfn = Project.FindFunction("tree_reporting_prepare_targets", "Originlab\\tree_Utils.c");
	if (pfn)
	{
		if (!pfn(trRep, dwTarget, strNoteWnd, NULL))
			return FALSE;
	}
	else
	{
		ASSERT(FALSE);
		return FALSE;
	}
	
	if (!dwTarget)
		return TRUE;	// nothing to do
	
	// The header table:
	//TreeNode		trTable = trRep.Contents.Header.Table;
	//if (trTable)
	//	dump_wks_header_table(trTable, dwTarget, strNoteWnd);
	
	// The worksheet data:
	BOOL			bOK = wksOutput.Type(dwTarget, strNoteWnd, 5, 0, 0, trCntsWks);
	
	
	return TRUE;
}

static void sort_vectors(const vector<int>& vs1, vector<int>& vs2)
{
	Worksheet wks;
	wks.Create(NULL, CREATE_HIDDEN);
	Dataset da(wks, 0);
	Dataset db(wks, 1);
	da = vs1;
	db = vs2;
	Curve crv( wks, 0, 1 );
	crv.Sort();
	vector vt;
	da = db;
	db = vs2;
	crv.Sort();
	vs2 = db;
	wks.Destroy();
}
	

int wks_report_parameters(Worksheet& wks, int nRow, const TreeNode& trParams, const TreeNode& trCols)
{
	// now we will report the parameters, we will only use P1, P2 etc and ignore
	// other possible values in this branch
	// first, using P1, we determine the titles
	TreeNode trP1 = trParams.P1;
	vector<int> vsIDs;
	vector<int> vsCols;
	vector<string> vsLabels;
	vector<string> vsNames;
	int nCol = 0;
	if(trP1)
	{		
		int nTitleRow = nRow++;
		string strTemp;
		foreach(TreeNode trItem in trP1.Children)
		{
			TreeNode trOputputName = trCols.GetNode(trItem.tagName);
			// has to exist
			if(trOputputName)
				strTemp = trOputputName.strVal;
			else
				strTemp = "";// should be error
			
			vsIDs.Add(trOputputName.ID); // we need this later to determin how to show the value
			vsCols.Add(nCol++);
			vsLabels.Add(strTemp);
			vsNames.Add(trItem.tagName);
			//wks.SetCell(nTitleRow, nCol++, strTemp);
		}
		// we need to order the parameter according to their IDs, so smaller ID will be shown more to the left
		sort_vectors(vsIDs, vsCols);
		
		for(int nc = 0; nc < nCol; nc++)
		{
			wks.SetCell(nRow, nc, STR_SEPARATOR_CELL); //separator
			wks.SetCell(nTitleRow, vsCols[nc], vsLabels[nc]);
		}
		
		nRow++;
	}
	
	TreeNodeCollection tcParameters(trParams, "P");
	int ii = nRow;
	foreach(TreeNode trParam in tcParameters)
	{
		for(int jj = 0; jj < vsNames.GetSize(); jj++)
		{
			TreeNode trItem = trParam.GetNode(vsNames[jj]);
			if(!trItem)
			{
				ASSERT(FALSE);
				printf("Err: %s has missing entry at %s\n", trParam.tagName, vsNames[jj]);
				continue;
			}
			nCol = vsCols[jj];
			if(FWR_PARA_NAME == vsIDs[jj])
				wks.SetCell(ii, nCol, trItem.strVal);
			else if(FWR_PARA_VARY == vsIDs[jj])
				wks.SetCell(ii, nCol, trItem.nVal > 0? "":"Fixed");
			else
				wks.SetCell(ii, nCol, trItem.dVal);
		}
		ii++;
	}
	return ii;
}

/**
	find column by name and return index
	Return:
		column index (0 offset) or -1 if given column name does not exist	
*/
int wks_get_col_index(Worksheet& wks, LPCSTR lpcszColName)
{
	Column cc = wks.Columns(lpcszColName);
	if(cc)
		return cc.GetIndex();
	
	return -1;
}

void wks_fill_separator_line(Worksheet& wks, int nRow, int nCol1, int nCol2)
{
	for(int ii = nCol1; ii <= nCol2; ii++)
		wks.SetCell(nRow, ii, "---");
}

int wks_report_table(
	Worksheet& wks, int nRow, int nCol, 
	LPCSTR lpcszTitle, vector<string>& vsColTitles, vector<string>& vsRowTitles, matrix<double> matValues)
{
	int nNumCols = vsColTitles.GetSize();
	int nNumRows = vsRowTitles.GetSize();
	if(nNumCols != matValues.GetNumCols() || nNumRows != matValues.GetNumRows())
	{
		printf("Err: wks_report_table found inconsistent matValues passed in\n");
		return nRow;
	}
	
	wks.SetCell(nRow++, nCol, "");// make a blank like
	wks.SetCell(nRow++, nCol, lpcszTitle);
	wks_fill_separator_line(wks, nRow++, nCol, nCol + nNumCols + 1); // need to add Row heading
	int nc, nr, ii, jj;
	for(nc = nCol + 1, ii = 0; ii < nNumCols; ii++, nc++)
		wks.SetCell(nRow, nc, vsColTitles[ii]);
	nRow++;
	wks_fill_separator_line(wks, nRow++, nCol, nCol + nNumCols + 1);// need to add Row heading
	// row heading
	for(nr = nRow, ii = 0; ii < nNumRows; ii++, nr++)
		wks.SetCell(nr, nCol, vsRowTitles[ii]);
	
	// mat data
	for(nr = nRow, ii = 0; ii < nNumRows; ii++, nr++)
	{
		for(nc = nCol+1, jj = 0; jj < nNumCols; jj++, nc++)
		{
			wks.SetCell(nr, nc, matValues[ii][jj]);
		}
	}
	
	return nRow + nNumRows;
}


