/*------------------------------------------------------------------------------*
 * File Name:  TDMFile.cpp				 										*
 * Creation: AW 11/29/06														*
 * Purpose: OriginC Source C file												*
 * Copyright (c) ABCD Corp.	2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010		*
 * All Rights Reserved															*
 * 																				*
 * Modification Log:															*
 * Hong 12/01/06 FIX_DATE_TIME_DATA_IMPROT_ERRRO								*
 * Hong 12/06/06 MORE_INFO_FOR_ORGANIZER										*
 * Hong 12/06/06 ADD_DATA_SELECTION												*
 * Hong 12/07/06 FIX_MISSING_VALUE_SHOWN_ERROR									*
 *------------------------------------------------------------------------------*/
 
////////////////////////////////////////////////////////////////////////////////////
// Including the system header file Origin.h should be sufficient for most Origin
// applications and is recommended. Origin.h includes many of the most common system
// header files and is automatically pre-compiled when Origin runs the first time.
// Programs including Origin.h subsequently compile much more quickly as long as
// the size and number of other included header files is minimized. All NAG header
// files are now included in Origin.h and no longer need be separately included.
//
// Right-click on the line below and select 'Open "Origin.h"' to open the Origin.h
// system header file.
#include <Origin.h>
////////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////////
// Include your own header files here.
#include <ocm.h> // Hong 12/07/07 FIX_MISSING_VALUE_SHOWN_ERROR
#include "TDMFile.h"

////////////////////////////////////////////////////////////////////////////////////
// Start your functions here.

#define USI_INCLUDE					"usi:include"
#define BYTE_OFFSET					"byteOffset"

#define USI_DATA					"usi:data"
#define TDM_ROOT					"tdm_root"
#define TDM_CHANNELGROUP			"tdm_channelgroup"
#define TDM_CHANNEL					"tdm_channel"

#define TDM_LOCAL_COLUMNS				local_columns
#define TDM_NODE_NAME_CHANNEL_GROUPS	"groups"
#define TDM_NODE_NAME_GROUP_CHANNELS	"channels"
#define TDM_NODE_NAME_FILE				"file"
#define TDM_NODE_NAME_CHANNEL_INFO		"Info"
#define TDM_NODE_NAME_INSTANCE_ATTR		"instance_attributes"
#define TDM_ATTR_NAME_URL				"url"
#define TDM_ATTR_NAME_BYTEORDER			"byteOrder"
#define TDM_ATTR_NAME_BYTEOFFSET		"byteOffset"
#define TDM_ATTR_NAME_LENGTH			"length"
#define TDM_ATTR_NAME_EXTERNAL			"external"
#define TDM_ATTR_NAME_NAME				"name"
#define TDM_ATTR_NAME_WF_INCREMENT		"wf_increment"
#define TDM_ATTR_NAME_WF_START			"wf_start_offset"
#define TDM_ATTR_NAME_WF_XNAME			"wf_xname"
#define TDM_ATTR_NAME_WF_XUNIT			"wf_xunit_string"
/// Hong 12/01/06 FIX_DATE_TIME_DATA_IMPROT_ERRRO
#define TDM_TIME_DISPLAY_TYPE			"Time"
#define DAY_OF_A_YEAR					86400
#define DIFF_TIME_OFFSET_BETTEEN_TDM_OC 1721060
/// end FIX_DATE_TIME_DATA_IMPROT_ERRRO
#define TDM_NODE_NAME_GROUP				"TDMFile"
#define TDM_STR_LITTLEENDIAN			"littleEndian"

#define FILE_CLOSE_RETURN(_fid, _ret) {	\
		_fid.Close();					\
		return (_ret);					\
	}

static int _get_usi_array(LPSTR lpszInput, StringArray& vsUSI)
{
	BOOL bFirstQuoter = FALSE;
	char *pch;
	string strTemp;
	while( *lpszInput )
	{
		if ( '\"' == *lpszInput++ )
		{
			if ( bFirstQuoter )
			{
				*(lpszInput - 1) = '\0';
				strTemp = pch;
				vsUSI.Add(strTemp);
				bFirstQuoter = false;
			}
			else
			{
				pch = lpszInput;
				bFirstQuoter = true;
			}
		}
	}
	if ( bFirstQuoter )
		return TDM_ERROR_INVALID_USI_STRING;
	return 0;
}

static int _get_data_type(string& strDataType)
{
	int nDataType; 

	if( strDataType.CompareNoCase("DT_Float") == 0 )
		nDataType = FSI_REAL;
	else if( strDataType.CompareNoCase("DT_Double") == 0 )
		nDataType = FSI_DOUBLE;
	else if( strDataType.CompareNoCase("DT_UInt8") == 0 )
		nDataType = FSI_BYTE;
	else if( strDataType.CompareNoCase("DT_Int32") == 0 )
		nDataType = FSI_LONG;
	else if( strDataType.CompareNoCase("DT_Int16") == 0 )
		nDataType = FSI_SHORT;
	else if( strDataType.CompareNoCase("DT_String") == 0 )
		nDataType = FSI_TEXT;
	else 
	{
		ASSERT(FALSE);
		out_str("please check unsuuport data type, and contact andrew");
		nDataType = FSI_MIXED;
	}
	return nDataType;
}

static int SetColumn(Worksheet& wks, Column& cc, TreeNode& trChannel)
{
	string strDesc = trChannel.description.strVal ;
	string strTag, strValue, strXUnit, strXName;
	double dInc = 0, dStart = 0;
	string strTemp;
	//cc.SetName(trChannel.name.strVal);	// Hong 12/26/06 NO_NEED_DUPLICATE
	cc.SetLongName(trChannel.name.strVal);
	cc.SetInternalData(_get_data_type(trChannel.datatype.strVal));
	
	///////
	TreeNode trChannelInfo = trChannel.AddNode(TDM_NODE_NAME_CHANNEL_INFO);
	TreeNode trChannelAttr = trChannel.GetNode(TDM_NODE_NAME_INSTANCE_ATTR);
	string strAttrName;
	StringArray	vsNames, vsValues;
	foreach (TreeNode trSub in trChannelAttr.Children )
	{
		if ( trSub.GetAttribute(TDM_ATTR_NAME_NAME, strAttrName) )
		{
			strTag = trSub.tagName;
			if ( strTag.Find("string") >= 0)
				strValue = trSub.s.strVal;
			else
				strValue = 	trSub.strVal;
			/// Hong 12/26/06 NO_NEED_ADD_TO_USER_PARAMETER
			if( 0 == strAttrName.CompareNoCase("NoValueKey") || 0 == strAttrName.CompareNoCase("DisplayType"))
				continue;
			/// end NO_NEED_ADD_TO_USER_PARAMETER
			if ( strAttrName.CompareNoCase(TDM_ATTR_NAME_WF_INCREMENT) == 0 )
				dInc = atof(strValue);
			else if ( strAttrName.CompareNoCase(TDM_ATTR_NAME_WF_START) == 0 )
				dStart = atof(strValue);
			else if ( strAttrName.CompareNoCase(TDM_ATTR_NAME_WF_XNAME) == 0 )
				strXName = strValue;
			else if ( strAttrName.CompareNoCase(TDM_ATTR_NAME_WF_XUNIT) == 0 )
				strXUnit = strValue;
			else
			{
				strTemp.Format("\n%s: %s", strAttrName, strValue);
				strDesc += strTemp;
				/////
				vsNames.Add(strAttrName);
				vsValues.Add(strValue);
				////
			}
			
			trChannelInfo.AddTextNode(strValue, strAttrName);
		}
	}
	//////
	
	//cc.SetLabel(strDesc);
	//cc.SetComments(strDesc);// Hong 12/05/06 FIX_SET_COMMENTS
	set_user_parameters(cc, vsNames, vsValues);
	cc.SetUnits(trChannel.unit_string.strVal);
	if ( 0 != dInc )
		wks.SetEvenSampling(dStart, dInc, cc, strXUnit, strXName);

	return 0;
}

static int getStringColumnData(Column& col,TreeNode& trValues)
{
	StringArray vs;
	foreach ( TreeNode trNode in trValues.Children )
	{
		vs.Add(trNode.strVal);
	}
	col.PutStringArray(vs);
	return 0;
}

//////////////////////////////////////////////////////
////	class OCTDMFile	
////
OCTDMFile::OCTDMFile(int nType)
:Tree(nType)
{
	m_bValid = false;
}

/*
OCTDMFile::OCTDMFile(LPCSTR lpcszFile, int nType)
:Tree(lpcszFile, nType)
{
	m_bValid = false;
	m_strTDMFileName = lpcszFile;
	init();
}
*/

int OCTDMFile::Load(WorksheetPage wksPage, TreeNode& trFileNode, TreeNode& trInfo, LPCSTR lpcszFile, int nType)
{
	int nRet;
	m_bValid = false;
	if ( Load(lpcszFile, nType) )
	{
		m_strTDMFileName = lpcszFile;
		if ( !wksPage )
			return TDM_ERROR_INVALID_WKS_PAGE;
		m_wksPage = wksPage;
		if ( nRet = init() )
			return nRet;
		return handleRootNode(trFileNode, trInfo);
	}
	 
	return TDM_ERROR_INVALID_TDM_FILE;
}

int OCTDMFile::init()
{
	if ( Tree::IsValid() )
	{
		m_trData = GetNode(USI_DATA);
		TreeNode trInclude = GetNode(USI_INCLUDE);
		m_trFile = trInclude.GetNode(TDM_NODE_NAME_FILE);
		
		if ( m_trData )
		{
			if ( m_trFile )
			{
				string strByteOrder;
				if ( m_trFile.GetAttribute(TDM_ATTR_NAME_BYTEORDER, strByteOrder) && m_trFile.GetAttribute(TDM_ATTR_NAME_URL, m_strTDXFileName))
				{
					m_bLittleEndian =  ( strByteOrder.CompareNoCase(TDM_STR_LITTLEENDIAN ) == 0 )? true : false;
					m_strTDXFileName = GetFilePath(m_strTDMFileName) + m_strTDXFileName;
				}
			}
			m_bValid = true;
			return 0;
		}
	}
	return TDM_ERROR_INVALID_TDM_FILE;
}

int OCTDMFile::handleRootNode(TreeNode& trFileNode, TreeNode& trInfo)
{
	string strGroup;
	StringArray	vsUSI;
	int nRet = 0;
	
	if ( !m_bValid)
		return TDM_ERROR_INVALID_TDM_FILE;
	TreeNode trRoot = m_trData.GetNode(TDM_ROOT);
	strGroup = trRoot.channelgroups.strVal;
	char *pbuff = strGroup.GetBuffer(strGroup.GetLength() + 1);
	_get_usi_array(pbuff, vsUSI);
	strGroup.ReleaseBuffer();
	if ( nRet = handleGroupsNode(vsUSI, trRoot, trFileNode) )
		return nRet;
	trInfo.AddNode(trRoot);
	/// Hong 12/06/06 MORE_INFO_FOR_ORGANIZER
	TreeNode trRange = trInfo.AddNode("FileDataRange");
	Tree trTemp;
	m_drRange.GetTree(trTemp);
	trRange.Replace(trTemp.Clone(), true, true);
	/// end MORE_INFO_FOR_ORGANIZER
	return 0;
}

int OCTDMFile::handleGroupsNode(StringArray& vsUSI, TreeNode& trRoot, TreeNode& trFileNode)
{
	string strGroup;
	int nRet;
	Worksheet wks;
	/// Hong 12/06/06 ADD_DATA_SELECTION
	//int nGroupNum = vsUSI.GetSize();
	int nGroupNum = 0;
	foreach(TreeNode tn in trFileNode.Children)
	{
		if(tn.Use)
			nGroupNum++;
	}
	/// end ADD_DATA_SELECTION
	if ( nGroupNum <= 0 )
		return TDM_ERROR_INVALID_TDM_FILE;
	TreeNode trGroups = trRoot.AddNode(TDM_NODE_NAME_CHANNEL_GROUPS);
	TreeNode trGroup;
	for ( int ii = m_wksPage.Layers.Count(); ii < nGroupNum; ii++ )
		wks = m_wksPage.Layers(m_wksPage.AddLayer());

	/// Hong 12/06/06 ADD_DATA_SELECTION
	//for (ii = 0; ii < nGroupNum; ii++ )
	ii = 0;
	int nIndex = 0;
	foreach(TreeNode trGroupSetting in trFileNode.Children)
	{
		if(trGroupSetting.Use)
		{
			wks = m_wksPage.Layers(nIndex);
			while( wks.DeleteCol(0) ); // Remove all columns in temp worksheet
	
			string strRangeName;
			strRangeName.Format("wks%d", nIndex);
			m_drRange.Add(wks, 0, strRangeName);
			
			trGroup = m_trData.FindNodeByAttribute("id", vsUSI[ii], false, false);
			if ( !trGroup)
				return TDM_ERROR_INVALID_CHANNEL_GROUP;
	
			StringArray	vsUSIlocal;
			strGroup = trGroup.channels.strVal;
			char *pbuff = strGroup.GetBuffer(strGroup.GetLength() + 1);
			_get_usi_array(pbuff, vsUSIlocal);
			strGroup.ReleaseBuffer();
			
			if ( nRet = handleChannelsNode(wks, vsUSIlocal, trGroup, trGroupSetting) )
				return nRet;
			wks.SetName(trGroup.name.strVal);
			trGroups.AddNode(trGroup);
			
			m_drRange.SetRange(ii, wks, 0, 0 , -1, wks.GetNumCols() - 1);
			nIndex++;
		}
		ii++;
	}
	/// end ADD_DATA_SELECTION
	return 0;
}

int OCTDMFile::GetGroupChannelNum(LPCSTR lpcszFile, vector<int>& vnChannenNum)
{
	int nRet;
	if( !m_bValid )
	{
		if ( Load(lpcszFile, 0) )
		{
			m_strTDMFileName = lpcszFile;
			if ( nRet = init() )
				return nRet;
		}
	}
	string strGroup;
	StringArray	vsUSI;

	TreeNode trRoot = m_trData.GetNode(TDM_ROOT);
	strGroup = trRoot.channelgroups.strVal;
	char *pbuff = strGroup.GetBuffer(strGroup.GetLength() + 1);
	_get_usi_array(pbuff, vsUSI);
	strGroup.ReleaseBuffer();
	
	for (int ii = 0; ii < vsUSI.GetSize(); ii++ )
	{
		TreeNode trGroup = m_trData.FindNodeByAttribute("id", vsUSI[ii], false, false);
		if ( !trGroup)
			return TDM_ERROR_INVALID_CHANNEL_GROUP;

		StringArray	vsUSIlocal;
		strGroup = trGroup.channels.strVal;
		char *pbuff = strGroup.GetBuffer(strGroup.GetLength() + 1);
		_get_usi_array(pbuff, vsUSIlocal);
		strGroup.ReleaseBuffer();
		
		vnChannenNum.Add( vsUSIlocal.GetSize() );
	}
	
	return 1;
}

int OCTDMFile::handleChannelsNode(Worksheet& wks, StringArray& vsUSI, TreeNode& trGroup, TreeNode& trFileNode)
{
	string strChannels;
	int nRet, nCol;
	
	int nChannelNum = vsUSI.GetSize();
	if ( nChannelNum <= 0 )
		return TDM_ERROR_INVALID_TDM_FILE;
		
	TreeNode trChannels = trGroup.AddNode(TDM_NODE_NAME_GROUP_CHANNELS);
	TreeNode trChannel;
	/// Hong 12/06/06 ADD_DATA_SELECTION
	//for (int ii = 0; ii < nChannelNum; ii++ )
	int ii = 0;
	foreach(TreeNode tn in trFileNode.Children)
	{
		if(tn.nVal)
		{
			nCol = wks.AddCol();
			Column col(wks, nCol);
	
			trChannel = m_trData.FindNodeByAttribute("id", vsUSI[ii], false, false);
			if ( !trChannel )
				return TDM_ERROR_INVALID_CHANNEL;
			
			/// get channels
			StringArray	vsUSIlocal;
			strChannels = trChannel.TDM_LOCAL_COLUMNS.strVal;
			char *pbuff = strChannels.GetBuffer(strChannels.GetLength() + 1);
			_get_usi_array(pbuff, vsUSIlocal);
			strChannels.ReleaseBuffer();
			
			SetColumn(wks, col, trChannel);
			
			if ( nRet = handleColumnNode(col, vsUSIlocal, trChannel) )
				return nRet;
			
			// now looks no need hadnle submatrix nodes
			/// get submatrix
			//strChannels = trGroup.submatrices.strVal;
			//*pbuff = strChannels.GetBuffer(strChannels.GetLength() + 1);
			//_get_usi_array(pbuff, vsUSI);
			//strChannels.ReleaseBuffer();
			
			trChannels.AddNode(trChannel);
		}
		ii++;
	}
	/// end ADD_DATA_SELECTION
	return 0;
}

int OCTDMFile::handleColumnNode(Column& col, StringArray& vsUSI, TreeNode& trChannel)
{
	string 			strcolumns;
	TreeNode 		trColumn, trSequence, trValues,trBlock;
	int				nOffset;    	////!!!!!!!!! this should be 64bit int
	int				nLength;		////!!!!!!!!! this should be 64bit int
	int 			nRet;
	string			strIncName;
	
	int nColumnlNum = vsUSI.GetSize();
	if ( nColumnlNum <= 0 )
		return TDM_ERROR_INVALID_TDM_FILE;
		
	for (int ii = 0; ii < nColumnlNum; ii++ )
	{
		// no need add local column info for the time being
		//trChannel = trDataNode.FindNodeByAttribute("id", vsUSI[ii], false, false);
		//trChannels.AddNode(trChannel);
		
		trColumn = m_trData.FindNodeByAttribute("id", vsUSI[ii], false, false);
		if ( !trColumn )
			return TDM_ERROR_INVALID_COLUMN;
		
		/// get channels
		StringArray		vsUSIlocal;
		strcolumns = trColumn.values.strVal;
		char *pbuff = strcolumns.GetBuffer(strcolumns.GetLength() + 1);
		_get_usi_array(pbuff, vsUSIlocal);
		strcolumns.ReleaseBuffer();
		
		if ( vsUSIlocal.GetSize() != 1) 
		{
			ASSERT(FALSE);	
		}
		if ( trSequence = m_trData.FindNodeByAttribute("id", vsUSIlocal[0], false, false) )
		{
			trValues = trSequence.GetNode("values");
			if ( trValues && trValues.GetAttribute(TDM_ATTR_NAME_EXTERNAL, strIncName) )
			{
				if ( m_trFile && (trBlock = m_trFile.FindNodeByAttribute("id", strIncName, false, false)) )
				{
					if ( trBlock.GetAttribute(TDM_ATTR_NAME_BYTEOFFSET, nOffset) && trBlock.GetAttribute(TDM_ATTR_NAME_LENGTH, nLength) )
					{
						/// Hong 12/01/06 FIX_DATE_TIME_DATA_IMPROT_ERRRO
						//if ( nRet = getColumnData(col,nOffset, nLength) )
						bool bTime = false;
						//TreeNode trDisplayType = tree_get_node_by_tagname(trChannel, "displaytype", true);
						TreeNode trDisplayType = trChannel.FindNodeByAttribute(TDM_ATTR_NAME_NAME, "displaytype"); // Hong 01/09/06 FIX_FAIL_GET_DISPLAU_TYPE_TREE_NODE
						if( trDisplayType.IsValid() )
						{
							string strDisType = trDisplayType.s.strVal;
							if( strDisType.Compare(TDM_TIME_DISPLAY_TYPE) == 0 )
								bTime = true;
						}
						if ( nRet = getColumnData(col, nOffset, nLength, bTime) )
						/// end FIX_DATE_TIME_DATA_IMPROT_ERRRO
							return nRet;
					}
				}
				else
					return TDM_ERROR_INVALID_BALOCK;
			}
			else
			{
				if ( nRet = getStringColumnData(col,trValues) )
								return nRet;		
			}
		}
	}
	return 0;
}
/// Hong 12/01/06 FIX_DATE_TIME_DATA_IMPROT_ERRRO
//int OCTDMFile::getColumnData(Column& cc, int nOffset, int nLength)
int OCTDMFile::getColumnData(Column& cc, int nOffset, int nLength, bool bTime)
/// end FIX_DATE_TIME_DATA_IMPROT_ERRRO
{
	int nElementSize;				
	UINT nNumElements;				
	cc.SetUpperBound(nLength - 1);   
	LPVOID lpData = cc.GetInternalDataBuffer(&nElementSize, &nNumElements);
	
	UINT	nSizeToRead = nElementSize * nNumElements;
	
	file fid;
	
	//if the file failed to open
	if( !fid.Open(m_strTDXFileName, file::modeRead|file::typeBinary) )
		FILE_CLOSE_RETURN(fid, TDM_ERROR_INVALID_TDX_FILE)

	fid.Seek(nOffset, file::begin);
	if( nSizeToRead != fid.Read(lpData, nSizeToRead) )
		return TDM_ERROR_READ_TDX;
	/// Hong 12/07/06 FIX_MISSING_VALUE_SHOWN_ERROR
	if( FSI_DOUBLE == cc.GetInternalData() )
		ocm_check_infinite_value((double*)lpData, nSizeToRead/8);
	/// end FIX_MISSING_VALUE_SHOWN_ERROR
	/// Hong 12/01/06 FIX_DATE_TIME_DATA_IMPROT_ERRRO
	if( bTime )
	{
		Dataset ds(cc);
		ds = ds/DAY_OF_A_YEAR + DIFF_TIME_OFFSET_BETTEEN_TDM_OC;
		cc.SetFormat(OKCOLTYPE_DATE);
	}
	/// end FIX_DATE_TIME_DATA_IMPROT_ERRRO
	return 0;	
}




///////////////////////////////////////////////////////////////////////////////////////////
//// testing codes
int testtdm(int nFileDlg = 1)
{
	int ddcError = 0;
	string strFileName;
	
	if (nFileDlg)
		strFileName = GetOpenBox("[*.tdm] *.tdm");
	else
		strFileName = "d:\\text.TDM";
	
	
	Tree trInfo;
	
	Worksheet wks = Project.ActiveLayer();
	WorksheetPage wksPage;
	wks.GetParent(wksPage);
	
	OCTDMFile tdmFile;
	//return tdmFile.Load(wksPage, trInfo, strFileName); 
	vector<int> vn;
	return tdmFile.GetGroupChannelNum(strFileName, vn);
}







