/*------------------------------------------------------------------------------*
 * File Name: OMat.c															*
 * Creation: GJL 5/13/02														*
 * Purpose: Origin C file implementing matrix related dialogs					*
 * Copyright (c) OriginLab Corp.	2002-2007									*
 * All Rights Reserved															*
 * 																				*
 * Modification Log:															*
 *------------------------------------------------------------------------------*/

//////////////////////////////////////////////////////////////////////////////////
// Included header files
//////////////////////////////////////////////////////////////////////////////////
//
// System includes
#include <Origin.h>       // Origin C constants

// Include needed Origin C utility functions
#include "App_Utils.h"
#include "Wks_Utils.h"

// Include definitions of OMat constants, non-localized strings, and function prototypes
#include "OMat.h"

////////////////////////////////////////////////////////////////////////////////////

/**
		HLOC function to directly convert an Origin worksheet or Excel workbook range
		selection to a matrix.
	Example:
		See the OnClickConvert function in OMatDlgDMC.c for a sample call.
	Parameters:
		trDMC=Input DirectMatConvert tree containing all dialog settings
	Return:
		Returns DMC_NO_ERROR on success and a DMC_ERROR code on failure.
*/
int ConvertWksToMatrixDirect(Tree& trDMC)
{
	// *** Declare local variables ***
	string strMsg, strWinName;
	int iDataFormat, iReturn;
	MatrixPage mpOut; // Newly created output matrix page

	// *** Combine relevant checkbox items into bitwise variable *** 
	if( trDMC.xVariesAcrossCols.nVal == 1 )
		iDataFormat=1 + 2*trDMC.xInRow1.nVal + 4*trDMC.yInCol1.nVal;
	else
		iDataFormat=0 + 2*trDMC.xInCol1.nVal + 4*trDMC.yInRow1.nVal;

	// *** Create output matrix ***
	strWinName = CreateWindow("Origin.OTM", CREATE_HIDDEN);
	mpOut = Project.MatrixPages(strWinName);
	
	// *** Call main function to convert worksheet/workbook selection range to matrix ***
	iReturn = convert_wks_to_matrix_direct(trDMC.SelRange.strVal, iDataFormat, mpOut);

	// *** Process iReturn error codes ***
	switch( iReturn )
	{
		// Process Escape
		case DMC_ESCAPE:
			if( mpOut.IsValid() )
				mpOut.Destroy();
			return iReturn;
			break;

		// Process no error
		case DMC_NO_ERROR:
			mpOut.SetShow(); // Show matrix window
			return iReturn;
			break;

		// Process input range specification error
		case DMC_WKS_SEL_ERROR:
			if( mpOut.IsValid() )
				mpOut.Destroy();
			strMsg = _L(DMC_WKS_SEL_ERROR_MSG);
			strMsg.Write(WRITE_MESSAGE_BOX); // Else bad selection so give error message
			return iReturn;
			break;

		// Process input worksheet/workbook and pre-output matrix errors
		case DMC_INPUT_WKS_PAGE_ERROR:
		case DMC_INPUT_ORIGIN_WKSHT_ERROR:
		case DMC_INPUT_EXCEL_WKSHT_ERROR:
		case DMC_UPDATE_ORIGIN_ERROR:
			if( mpOut.IsValid() )
				mpOut.Destroy();
			strMsg = _L(DMC_INPUT_WKS_ERROR_MSG);
			strMsg.Write(WRITE_MESSAGE_BOX);
			return iReturn;
			break;

		// Process output matrix errors
		case DMC_CPY_WKS_TO_MAT_ERROR:
		case DMC_TRANSPOSE_MATRIX_ERROR:
		case DMC_ATTACH_OUTPUT_MAT_ERROR:
		case DMC_SUB_MAT_ERROR:
			if( mpOut.IsValid() )
				mpOut.Destroy();
			strMsg = _L(DMC_OUTPUT_MAT_ERROR_MSG);
			strMsg.Write(WRITE_MESSAGE_BOX);
			return iReturn;
			break;
	}

	// *** Process iReturn warning codes if any ***
	if( iReturn & DMC_XMAP_WARNING )
	{
		strMsg = _L(DMC_XMAP_WARNING_MSG);
		strMsg.Write(WRITE_MESSAGE_BOX);
	}
	if( iReturn & DMC_YMAP_WARNING )
	{
		strMsg = _L(DMC_YMAP_WARNING_MSG);
		strMsg.Write(WRITE_MESSAGE_BOX);
	}
	
	// *** Show matrix window ***
	mpOut.SetShow();

	return DMC_NO_ERROR; // Reset error flag after warnings are made
}

/**
		LLOC function to directly convert an Origin worksheet or Excel workbook range
		selection to a matrix according to a bitwise data format description code.
	Example:
		See the ConvertWksToMatrixDirect function in OMat.c for a sample call.
	Parameters:
		strWksSelection=Input worksheet/workbook range selection string like "Worksheet_A[1]:B[7]" or
			"[Book1]Sheet1!$A$1:$B$7"
		iDataFormat=Bitwise data format description: Bit 0=1 X varies across columns or Bit 0=0 Y varies
			across columns, Bit 1=1 First row contains X/Y values, Bit 2=1 First column contains X/Y values
		mpOut=Output matrix page
	Return:
		Returns DMC_NO_ERROR and a valid matrix page on success or a DMC_ERROR code on failure.
*/
int convert_wks_to_matrix_direct(string strWksSelection, int iDataFormat, MatrixPage& mpOut)
{
	string strWksName, strSheetName;
	int iC1, iC2, iR1, iR2, ii, jj, iReturn;
	double dDelta;
	vector vXMap, vYMap;
	WorksheetPage wpIn;
	Worksheet wksIn;
	PageBase pbActiveWindow;
	matrix mTemp;
	Matrix matOut;
	MatrixLayer mlOut;
	
	waitCursor	wcCursor; // Put up a wait cursor and allow escape (on Esc key)
	
	iReturn = DMC_NO_ERROR;
	
	// *** Parse input worksheet/workbook range selection string ***
	if( !wuParseWksSelection(strWksSelection, strWksName, strSheetName, iC1, iC2, iR1, iR2) ) 
		return DMC_WKS_SEL_ERROR;

	// *** Attach to input worksheet/workbook page containing selected data range ***
	wpIn = Project.WorksheetPages(strWksName);
	if( !wpIn.IsValid() )
		return DMC_INPUT_WKS_PAGE_ERROR;
	
	// *** Attach to Origin worksheet layer or Excel workbook sheet containing selected data range ***
	if( strSheetName.IsEmpty() )
	{
		// If Origin worksheet...
		wksIn = (Worksheet) wpIn.Layers(); // Get Origin worksheet object (by default)
		if( !wksIn.IsValid() )
			return DMC_INPUT_ORIGIN_WKSHT_ERROR;
	}
	else
	{
		// Else Excel workbook...
		wksIn = (Worksheet) wpIn.Layers(strSheetName); // Get Excel workbook sheet object (by name)
		if( !wksIn.IsValid() )
			return DMC_INPUT_EXCEL_WKSHT_ERROR;
		// Update Origin from Excel
		if( !wksIn.UpdateOrigin() )
			return DMC_UPDATE_ORIGIN_ERROR;
	}
	
	if( wcCursor.CheckEsc() )
		return DMC_ESCAPE; // Quit if user hits Esc key
	
	// *** Copy worksheet/workbook range to a temporary Origin C matrix ***
	if( !mTemp.CopyFromWks(wksIn, iC1, iC2, iR1, iR2) )
		return DMC_CPY_WKS_TO_MAT_ERROR;
	
	if( wcCursor.CheckEsc() )
		return DMC_ESCAPE; // Quit if user hits Esc key

	// *** If Y varies across columns transpose matrix ***
	if( !(iDataFormat & 1) )
		if( !mTemp.Transpose() )
			return DMC_TRANSPOSE_MATRIX_ERROR;

	if( wcCursor.CheckEsc() )
		return DMC_ESCAPE; // Quit if user hits Esc key

	// *** Get X-column and Y-row maps into vectors ***
	ii = 0; // Assume first row of matrix does not contain X Map
	jj = 0; // Assume first column of matrix does not contain Y Map
	if( iDataFormat & 2 )
	{
		// If first row of matrix contains X map...
		ii = 1; // Set flag to remove row 0 with GetSubMatrix
		mTemp.GetRow(vXMap, 0); // Copy first row of matrix into vector
	}
	if( iDataFormat & 4 )
	{
		// If first column of matrix contains Y map...
		jj = 1; // Set flag to remove column 0 with GetSubMatrix
		mTemp.GetColumn(vYMap, 0); // Copy first column of matrix into vector
	}

	if( wcCursor.CheckEsc() )
		return DMC_ESCAPE; // Quit if user hits Esc key	

	// *** Get active internal Origin matrix layer, then attach to OriginC Matrix object ***
	if( !mpOut.IsValid() )
		return DMC_ATTACH_OUTPUT_MAT_ERROR;
	mlOut = (MatrixLayer) mpOut.Layers();
	if( !mlOut.IsValid() )
		return DMC_ATTACH_OUTPUT_MAT_ERROR;
	matOut.Attach(mlOut);
	if( !matOut.IsValid() )
		return DMC_ATTACH_OUTPUT_MAT_ERROR;
	
	// *** Copy temporary matrix to output matrix removing XY data if needed ***
	if( !mTemp.GetSubMatrix(matOut, jj, -1, ii, -1) )
		return DMC_SUB_MAT_ERROR;
	
	if( wcCursor.CheckEsc() )
		return DMC_ESCAPE; // Quit if user hits Esc key

	// *** Assign X-column map ***
	matOut.SetXMin(1); // Assume a 1 to NumCols X-column map
	matOut.SetXMax(matOut.GetNumCols());
	if(ii)
	{
		// If first row of matrix contained X map...
		if( wuVectorHasUniformSpacing(dDelta, vXMap, DMC_UNIFORM_SPACING_TOL, jj) )
		{
			// If X map has uniform spacing (within tolerance)...
			matOut.SetXMin(vXMap[jj]); // If first column was Y map use jj=1 else use jj=0 as min X
			matOut.SetXMax(vXMap[vXMap.GetSize() - 1]); // Always use last element as max X
		}
		else
			iReturn += DMC_XMAP_WARNING;
	}
	if( wcCursor.CheckEsc() )
		return DMC_ESCAPE; // Quit if user hits Esc key
	
	// *** Assign Y-row map ***
	matOut.SetYMin(1); // Assume 1 to NumRows Y-row map
	matOut.SetYMax(matOut.GetNumRows());
	if(jj)
	{
		// If first column of matrix contained Y map...
		if( wuVectorHasUniformSpacing(dDelta, vYMap, DMC_UNIFORM_SPACING_TOL, ii) )
		{
			// If Y map has uniform spacing (within tolerance)...
			matOut.SetYMin(vYMap[ii]); // If first row was X map use ii=1 else use ii=0 as min Y
			matOut.SetYMax(vYMap[vYMap.GetSize() - 1]); // Always use last element as max Y
		}
		else
			iReturn += DMC_YMAP_WARNING;
	}
	if( wcCursor.CheckEsc() )
		return DMC_ESCAPE; // Quit if user hits Esc key

	// *** Set page label and Show (activate) internal Origin matrix page ***
	strWksSelection.TrimLeft();
	strWksSelection.TrimRight();
	mpOut.Label = _L(DMC_MATRIX_PAGE_LABEL) + strWksSelection; // Set matrix page label
	LabTalk.Page.title=3; // Display both matrix name and label

	return iReturn;
}

/**
		Create and initialize a DirectMatConvert tree.
	Return:
		Returns a newly created DirectMatConvert tree.
*/
Tree CreateDirectMatConvertTree()
{
	// *** Create tree and nodes ***
	Tree trDMC;
	trDMC.Reset();
	trDMC.AddNode("SelRange");
	trDMC.AddNode("xVariesAcrossCols");
	trDMC.AddNode("xInRow1");
	trDMC.AddNode("yInCol1");
	trDMC.AddNode("xInCol1");
	trDMC.AddNode("yInRow1");

	// *** Initialize node values ***
	InitDirectMatConvertTree(trDMC);

	return trDMC;
}

/**
		Initialize a DirectMatConvert tree.
	Parameters:
		trDMC=DirectMatConvert tree to initialize
	Return:
		Returns an initialized DirectMatConvert tree by reference.
*/
BOOL InitDirectMatConvertTree(Tree& trDMC)
{
	// *** Set tree nodes ***
	trDMC.SelRange.strVal = "";
	trDMC.xVariesAcrossCols.nVal = 1;
	trDMC.xInRow1.nVal = 0;
	trDMC.yInCol1.nVal = 0;
	trDMC.xInCol1.nVal = 0;
	trDMC.yInRow1.nVal = 0;

	return TRUE;
}

/**
		HLOC function to perfrom conditional replace on an internal Origin matrix.
	Example:
		See the OnClickReplace function in OMatDlgMR.c for a sample call.
	Parameters:
		trMR=Input MatrixReplace tree containing all dialog settings
	Return:
		Returns MR_NO_ERROR or a MR_ERROR code on failure.
*/
int MatrixReplace(Tree& trMR)
{
	PageBase pbActiveWindow;
	MatrixLayer ml;
	Matrix mat;
	WorksheetPage wpUndo;
	Worksheet wksUndo;
	Column colUndo;
	Dataset dsUndo;
	int iUndoColNum;
	uint wBitwiseOption;
	BOOL bUndo;
	string strMatrixName;
	
	waitCursor wcCursor;                            // Put up an hour glass
	bUndo = FALSE;                                  // Init to FALSE

	// *** Get bitwise option value ***
	wBitwiseOption = trMR.ConditionSign.nVal + 1;              // nVal (adjusted for 0 based offeset) is same as wBitwiseOption
	if(wBitwiseOption>6) wBitwiseOption+=2;                    // Except must add 2 if Choice > 6 to set Bit 3 for absolute value
	wBitwiseOption += 16 * trMR.ReplaceSign.nVal;              // And must add 16 to keep original sign when replacing 
	wBitwiseOption += 32 * trMR.ReplaceMissingValue.nVal;      // And must add 32 to set to missing value when not replacing

	// *** If using abs value function with negative or missing condition value in Condition tell user and return *** 
	if( ( wBitwiseOption & 8 ) && ( trMR.ConditionValue.dVal < 0 ) )
	{
		MessageBox(NULL, _L(MR_ABS_COND_ERROR_MSG), _L(MR_TITLE), MB_OK | MB_ICONEXCLAMATION);
		return MR_ABS_COND_ERROR;
	}		

	// *** Get active matrix ***
	pbActiveWindow = Project.Pages();               // Get active page (window)
	if( !pbActiveWindow.IsValid() )                 // If not valid...
	{
		MessageBox(NULL, _L(MR_ACT_MAT_ERROR_MSG), _L(MR_TITLE), MB_OK | MB_ICONSTOP);
		return MR_ACT_MATRIX_ERROR;                 // Output error message and return error code
	}
	
	if( pbActiveWindow.GetType() != EXIST_MATRIX )  // If window is not a matrix...
	{
		MessageBox(NULL, _L(MR_ACT_MAT_ERROR_MSG), _L(MR_TITLE), MB_OK | MB_ICONSTOP);
		return MR_ACT_MATRIX_ERROR;                 // Output error message and return error code
	}
	
	strMatrixName = pbActiveWindow.GetName();       // Get name of active matrix

	ml = (MatrixLayer) Project.ActiveLayer();       // Get active matrix layer
	if( !ml.IsValid() )                             // If not valid...
	{
		MessageBox(NULL, _L(MR_ACT_MAT_ERROR_MSG), _L(MR_TITLE), MB_OK | MB_ICONSTOP);
		return MR_ACT_MATRIX_ERROR;                 // Output error message and return error code
	}

	mat.Attach(ml);                                 // Attach to matrix
	if( !mat.IsValid() )                            // If not valid...
	{
		MessageBox(NULL, _L(MR_ACT_MAT_ERROR_MSG), _L(MR_TITLE), MB_OK | MB_ICONSTOP);
		return MR_ACT_MATRIX_ERROR;                 // Output error message and return error code
	}
	
	// *** Save Original matrix values in Matrix Replace Undo worksheet column ***
	wpUndo = Project.WorksheetPages(MR_UNDO_WKS_NAME);      // Attach to worksheet page
	if( !wpUndo.IsValid() )                         // If not valid (page does not exist) then create
	{
		wpUndo.Create("0", CREATE_HIDDEN);          // Create hidden worksheet from no template
		wpUndo.Rename(MR_UNDO_WKS_NAME);                    // Rename worksheet page MRUndo
	}

	wksUndo = (Worksheet) wpUndo.Layers();          // Attach to worksheet (layer)
	if( !wksUndo.IsValid() )                        // If not valid...
	{
		if( MessageBox(NULL, _L(MR_UNDO_WARNING_MSG), _L(MR_TITLE), MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDNO )
			return MR_NO_UNDO_QUIT_ERROR;           // Ask user to continue and return if answer is No
	}
	else
	{
		colUndo = wksUndo.Columns(strMatrixName);   // Attach to column for active matrix
		if( !colUndo.IsValid() )                    // If not valid (column does not exist)...
			iUndoColNum = wksUndo.AddCol(strMatrixName);// Add column and get column number
		else
			iUndoColNum = colUndo.GetIndex();       // Else just get column number
		
		dsUndo.Attach(wksUndo, iUndoColNum);        // Attach Dataset to Undo column
		if( !dsUndo.IsValid() )                     // If not valid...
		{
			if( MessageBox(NULL, _L(MR_UNDO_WARNING_MSG), _L(MR_TITLE), MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDNO )
				return MR_NO_UNDO_QUIT_ERROR;       // Ask user to continue and return if answer is No
		}
		else
		{
			if( !mat.GetAsVector(dsUndo) )          // Copy matrix to Undo Dataset...if not valid...
			{
				if( MessageBox(NULL, _L(MR_UNDO_WARNING_MSG), _L(MR_TITLE), MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDNO )
					return MR_NO_UNDO_QUIT_ERROR;           // Ask user to continue and return if answer is No
			}
			else
				bUndo = TRUE;                       // Enable restorative Undo
		}
	}

	// *** Replace matrix values ***
	if( !mat.Replace(trMR.ConditionValue.dVal, trMR.ReplaceValue.dVal, wBitwiseOption) ) // If matrix replace fails...
	{
		if( bUndo ) // If Undo is enabled...
		{
			if( !mat.SetByVector(dsUndo) )		// If Undo Matrix Replace is successful...
			{
				MessageBox(NULL, _L(MR_FAILED_UNDO_ERROR_MSG), _L(MR_TITLE), MB_OK | MB_ICONSTOP); // Output an error message
				return MR_FAILED_ERROR;             // And return error code
			}
		}

		// Else, if there is no Undo or if Undo is not successful...
		MessageBox(NULL, _L(MR_FAILED_ERROR_MSG), _L(MR_TITLE), MB_OK | MB_ICONSTOP); // Output an error message
		return MR_FAILED_ERROR;                 	// And return error code
	}
	
	return MR_NO_ERROR;                             // Everything went well
}

/**
		HLOC to undo matrix replace.
	Example:
		iRet = UndoMatrixReplace();
	Return:
		Returns MR_NO_ERROR on success or MR_UNDO_FAILED_ERROR on failure.
*/
int UndoMatrixReplace()
{
	PageBase pbActiveWindow;
	MatrixLayer ml;
	Matrix mat;
	WorksheetPage wpUndo;
	Worksheet wksUndo;
	Column colUndo;
	Dataset dsUndo;
	string strMatrixName;
	int iColNum;

	waitCursor wcCursor; // Put up an hour glass

	pbActiveWindow = Project.Pages(); // Get active page
	if( pbActiveWindow.IsValid() )
	{
		// If page is valid...
		if( pbActiveWindow.GetType() == EXIST_MATRIX )
		{
			// If page type is matrix...
			strMatrixName = pbActiveWindow.GetName(); // Get matrix name

			ml = (MatrixLayer) Project.ActiveLayer(); // Get active matrix layer
			if( ml.IsValid() )
			{
				// If matrix layer is valid...
				mat.Attach(ml); // Attach to matrix
				if( mat.IsValid() )
				{
					// If matrix is valid...
					wpUndo = Project.WorksheetPages(MR_UNDO_WKS_NAME); // Get Undo worksheet page
					if( wpUndo.IsValid() )
					{
						// If worksheet page is valid...
						wksUndo = (Worksheet) wpUndo.Layers(); // Get Undo worksheet (layer)
						if( wksUndo.IsValid() )
						{
							// If worksheet is valid...
							colUndo = wksUndo.Columns(strMatrixName); // Get Undo column
							if( colUndo.IsValid() )
							{
								// If column is valid...
								dsUndo.Attach(colUndo); // Get Undo dataset
								if( dsUndo.IsValid() )
								{
									// If dataset is valid...
									if( dsUndo.GetSize() == mat.GetNumRows() * mat.GetNumCols() )
									{
										// If dataset has same number of cells as matrix...
										if( !mat.SetByVector(dsUndo) )
										{
											// Refresh page before message box
											ml.GetPage().Refresh(TRUE);
											
											// If Undo Matrix Replace is successful...
											iColNum = colUndo.GetIndex(); // Get index of undo column
											if( iColNum > 0 )
												wksUndo.DeleteCol(iColNum); // Delete column if found
											
											MessageBox(NULL, _L(MR_UNDO_SUCCESS_MSG), _L(MR_TITLE), MB_OK | MB_ICONASTERISK); 
											return MR_NO_ERROR; // Success!
										}	// End Undo Matrix Replace is successful
									}	// End same number of cells
								}	// End dataset is valid
							}	// End column is valid
						}	// End worksheet is valid
					}	// End worksheet page is valid
				}	// End matrix is valid
			}	// End matrix layer is valid
		}	// End page type is matrix
	}	// End page is valid
	
	// Else (for all ifs)...	
	MessageBox(NULL, _L(MR_UNDO_FAILED_ERROR_MSG), _L(MR_TITLE), MB_OK | MB_ICONSTOP);
	return MR_UNDO_FAILED_ERROR; // Failure
}	// End Undo Matrix Replace

/**
		Create and initialize a MatrixReplace tree.
	Return:
		Returns a newly created MatrixReplace tree.
*/
Tree CreateMatrixReplaceTree()
{
	// *** Create tree and nodes ***
	Tree trMR;
	trMR.Reset();
	trMR.AddNode("ConditionSign");
	trMR.AddNode("ConditionValue");
	trMR.AddNode("ReplaceValue");
	trMR.AddNode("ReplaceSign");
	trMR.AddNode("ReplaceMissingValue");

	// *** Initialize node values ***
	InitMatrixReplaceTree(trMR);

	return trMR;
}

/**
		Initialize a MatrixReplace tree.
	Parameters:
		trMR=MatrixReplace tree to initialize
	Return:
		Returns an initialized MatrixReplace tree by reference.
*/
BOOL InitMatrixReplaceTree(Tree& trMR)
{
	// *** Set tree nodes ***
	trMR.ConditionSign.nVal = 1;
	trMR.ConditionValue.dVal = NANUM;
	trMR.ReplaceValue.dVal = 0.0;
	trMR.ReplaceSign.nVal = 0;
	trMR.ReplaceMissingValue.nVal = 0;

	return TRUE;
}