/*------------------------------------------------------------------------------*
 * File Name: exportTDM.h													*
 * Creation: Hong 01/24/07														*
 * Purpose: OriginC Source C file to import DIADem(*.tdm)files					*
 * Copyright (c) ABCD Corp.	2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010		*
 * All Rights Reserved															*
 * Modification Log:															*
 *	Hong 02/24/07 CONVERT_SAMPLING_INTERVAL_TO_WAVEFORM_IN_TDM					*
 * 	Hong 03/14/07 UPDATE_TO_NEW_TDM_DLL											*
 *	Hong 03/14/07 QA80-9441 FIX_REDUNDANT_ROW									*
 *	Hong 03/14/07 QA80-9301 DIRECTLY_EXPORT_TIME_AS_DOUBLE						*
 *	Hong 09/24/07 QA80-10343 v8.0707 UPDATE_TO_NEW_DLL_SUPPORT_TDMS				*
 *	Hong 10/10/07 v8.0721 FIX_MISS_MISSING_VALUE_FLAG_WHEN_EXPORT				*
 *	Hong 01/08/08 ADD_WARNING_WHEN_DUPLICATE_CHANNEL_NAME						*
 *------------------------------------------------------------------------------*/

#ifndef	EXPORTTDM_H
#define	EXPORTTDM_H

#include <ocm.h>
#include <.\..\originlab\nilibddc\nilibddc.h>

typedef enum	// for temp 
{
	FAIL_CREATE_TDM_FILE = -20,
	FAIL_CREATE_CHANNEL_GROUPS,
	FAIL_CREATE_CHANNEL,
	FAIL_WRITE_CHANNEL_DATA,
	FAIL_ALLOCATE_MEMORY,
	FAIL_GET_CHANNEL,
	FAIL_READ_DATA,
	FAIL_GET_DATA_NUM,
	FAIL_GET_DATA_TYPE,
	UNKNOWN_DATA_TYPE,
	FAIL_GET_DATA,	
	IMPORTED_SUCCEED = 0,
	
}TDM_EXPORT_ERROR;

#define TDM_PROPERTY_DATE_STRING	"datestring"
#define TDM_PROPERTY_TIME_STRING	"timestring"

#define TDM_DISPLAY_TYPE		"displaytype"
#define TDM_TIME_DISPLAY_TYPE			"Time"
#define DAY_OF_A_YEAR					86400
#define DIFF_TIME_OFFSET_BETTEEN_TDM_OC 1721060
/// Hong 02/24/07 CONVERT_SAMPLING_INTERVAL_TO_WAVEFORM_IN_TDM
#define DDC_CHANNEL_WAVEFORM_NAME		"wf_xname"       // WaveForm X-name	
#define DDC_CHANNEL_WAVEFORM_UNIT		"wf_xunit_string"       // WaveForm X-unit
#define DDC_CHANNEL_WAVEFORM_OFFSET		"wf_start_offset"       // WaveForm X-offset
#define DDC_CHANNEL_WAVEFORM_INCREMENT	"wf_increment"      	// WaveForm X-increment
/// end CONVERT_SAMPLING_INTERVAL_TO_WAVEFORM_IN_TDM

/// Hong 10/10/07 v8.0721 FIX_MISS_MISSING_VALUE_FLAG_WHEN_EXPORT
// all these are copy from import, need move to a shared header later, maybe nilibddc.h
#define DDC_CHANNEL_MISSING_VALUE_FLAG	"novaluekey"       	// Missing Value Flag
#define MISSING_VALUE_FLAG				"Missing Value Flag"
/// end FIX_MISS_MISSING_VALUE_FLAG_WHEN_EXPORT

/// Hong 01/31/07 WRAP_VA_LIST
/*
//// AW 01/30/07 MORE_WORK_IN_EXPORT_TDM
static int ocDDC_SetFilePropertyV(DDCFileHandle pfile, LPCSTR lpcszPropertyName,
									LPCSTR lpcszPropertyValue)
{
	DWORD	dwName = (DWORD)(const void*)lpcszPropertyValue;
	return DDC_SetFilePropertyV(pfile, lpcszPropertyName, (va_list)&dwName);
}
//// END MORE_WORK_IN_EXPORT_TDM
*/
static int ocDDC_CreateFilePropertyV(DDCFileHandle pfile, LPCSTR lpcszPropertyName, DDCDataType dataType,
									void* pPropertyValue) 									
{
	DWORD	dwName = (DWORD)(const void*)pPropertyValue;
	return DDC_CreateFilePropertyV(pfile, lpcszPropertyName, dataType, (va_list)&dwName);
}

static int ocDDC_SetFilePropertyV(DDCFileHandle pfile, LPCSTR lpcszPropertyName,
									void* pPropertyValue) 
{
	DWORD	dwName = (DWORD)(const void*)pPropertyValue;
	return DDC_SetFilePropertyV(pfile, lpcszPropertyName, (va_list)&dwName);
}

static int ocDDC_CreateChannelGroupPropertyV(DDCChannelGroupHandle channelGroup, LPCSTR lpcszPropertyName, DDCDataType dataType,
									void* pPropertyValue) 
{
	DWORD	dwName = (DWORD)(const void*)pPropertyValue;
	return DDC_CreateChannelGroupPropertyV(channelGroup, lpcszPropertyName, dataType, (va_list)&dwName);
}

static int ocDDC_SetChannelGroupPropertyV(DDCChannelGroupHandle channelGroup, LPCSTR lpcszPropertyName,
									void* pPropertyValue)
{
	DWORD	dwName = (DWORD)(const void*)pPropertyValue;
	return DDC_SetChannelGroupPropertyV(channelGroup, lpcszPropertyName, (va_list)&dwName);
}

static int ocDDC_CreateChannelPropertyV(DDCChannelHandle channel, LPCSTR lpcszPropertyName, DDCDataType dataType,
									void* pPropertyValue)
{
	DWORD	dwName = (DWORD)(const void*)pPropertyValue;
	return DDC_CreateChannelPropertyV(channel, lpcszPropertyName, dataType, (va_list)&dwName);
}

static int ocDDC_SetChannelPropertyV(DDCChannelHandle channel, LPCSTR lpcszPropertyName,
									void* pPropertyValue)
{
	DWORD	dwName = (DWORD)(const void*)pPropertyValue;
	return DDC_SetChannelPropertyV(channel, lpcszPropertyName, (va_list)&dwName);
}
/// end WRAP_VA_LIST

class exportTDM
{
public:
	exportTDM(LPCSTR lpcszFileName)
	{
		m_File = 0;
		m_strFileName = lpcszFileName;
		m_bValidTDM = FALSE;
		
		if( Create() )
		{
			m_bValidTDM = TRUE;
		}
	}
	
	~exportTDM()
	{
		Close();
	}
	
public:
	int WriteFile(WorksheetPage& wp, int nMixTextPercent)
	{
		if( !m_bValidTDM )
			return FAIL_CREATE_TDM_FILE;
		
		// add code here to set file property
		string strFileName, strDescript, strTitle, strAuthor; 
		strFileName = GetFileName(m_strFileName, TRUE);

		ocDDC_SetFilePropertyV(m_File, DDC_FILE_NAME, &strFileName); 	
		//// AW 01/30/07 MORE_WORK_IN_EXPORT_TDM
		LPSTR lpcstr = strAuthor.GetBuffer(255);
		GetLicenseInfo(lpcstr);
		strAuthor.ReleaseBuffer();
		int nRet = ocDDC_SetFilePropertyV(m_File, DDC_FILE_AUTHOR, &strAuthor); // need change to origin user name
		if ( nRet ) 
			return nRet;
		//// END MORE_WORK_IN_EXPORT_TDM
		strTitle = wp.GetLongName();
		ocDDC_SetFilePropertyV(m_File, DDC_FILE_NAME, &strTitle);
		
		SYSTEMTIME st;
		get_current_time(st, true);
		string strTemp;
		LPSTR lpstr = strTemp.GetBuffer(255);
		if(lpstr)
		{
			systemtime_to_date_str(&st, lpstr, LDF_SHORT_AND_HHMMSS_SEPARCOLON);
			strTemp.ReleaseBuffer();
			vector<string> vsDateTime;
			strTemp.GetTokens(vsDateTime, ' ');
			ocDDC_CreateFilePropertyV( m_File, TDM_PROPERTY_DATE_STRING, DDC_String, &vsDateTime[0]);
			ocDDC_CreateFilePropertyV( m_File, TDM_PROPERTY_TIME_STRING, DDC_String, &vsDateTime[1]);
		}
		
		return WriteGroups(wp, nMixTextPercent);
	}
protected:
	int WriteGroups(WorksheetPage& wp, int nMixTextPercent)
	{
		int nRet = IMPORTED_SUCCEED;
		
		foreach(Layer ly in wp.Layers)
		{
			Worksheet wks(ly);
			if(wks.GetSystemParam(0) & WP_SHEET_HIERARCHY)
				continue;	// not export report wks, may need dump a warning message here
			
			if( is_sheet_empty(wks) ) // skip empty worksheet
				continue;
			
			string strName, strDescript;
			strName = wks.GetName();
			DDCChannelGroupHandle cgHandle;
			if( DDC_AddChannelGroup(m_File, strName, "", &cgHandle) )
				return FAIL_CREATE_CHANNEL_GROUPS;
			
			if( nRet = ReadChannels(wks, cgHandle, nMixTextPercent) )
				return nRet;
		}
		
		return IMPORTED_SUCCEED;
	}
	
	int ReadChannels(Worksheet& wks, DDCChannelGroupHandle cgHandle, int nMixTextPercent)
	{
		int nRet = IMPORTED_SUCCEED;
		
		foreach(Column col in wks.Columns)
		{
			int nDataType;
			bool bNeedDataConvert = false;
			if( !checkConvertDataType(col, nDataType, bNeedDataConvert, nMixTextPercent) )
				continue; // not export tdm not support data type, may need dump a warning message here
			
			DDCChannelHandle chHandle;
			string strColName, strName, strComment, strUint;
			strColName = col.GetLongName(); // channel name should not be empty
			if(strColName.IsEmpty())
				col.GetName(strColName);
			
			int nType = col.GetInternalDataType();
			if( FSI_COMPLEX != nType )
				strName = strColName;
			else
				strName = strColName + "_real";
			
			strComment = col.GetComments();
			strUint = col.GetUnits();
			/// Hong 01/08/08 ADD_WARNING_WHEN_DUPLICATE_CHANNEL_NAME
			//if( DDC_AddChannel(cgHandle, nDataType, strName, strComment, strUint, &chHandle) )
			//	return FAIL_CREATE_CHANNEL;
			int nRetVal;
			if ( nRetVal = DDC_AddChannel(cgHandle, nDataType, strName, strComment, strUint, &chHandle) )
			{
				if ( DDC_DuplicateChannelName == nRetVal )
				{
					warning_msg_box(XFERR_TDM_DUPLICATE_CHANNEL_NAME, strName, false);
					continue;	
				}				
				return FAIL_CREATE_CHANNEL;
			}
			/// end ADD_WARNING_WHEN_DUPLICATE_CHANNEL_NAME
			
			// add set channel property here
			/// Hong 02/24/07 CONVERT_SAMPLING_INTERVAL_TO_WAVEFORM_IN_TDM
			double dX0, dxInc;
			string strXUnits, strXLongName;
			if( col.IsEvenSampling( &dX0, &dxInc, &strXUnits, &strXLongName ) )
			{
				ocDDC_CreateChannelPropertyV(chHandle, DDC_CHANNEL_WAVEFORM_NAME, DDC_String, &strXLongName);
				ocDDC_CreateChannelPropertyV(chHandle, DDC_CHANNEL_WAVEFORM_UNIT, DDC_String, &strXUnits);
				DDC_CreateChannelPropertyV(chHandle, DDC_CHANNEL_WAVEFORM_OFFSET, DDC_Double, (va_list)&dX0);
				DDC_CreateChannelPropertyV(chHandle, DDC_CHANNEL_WAVEFORM_INCREMENT, DDC_Double, (va_list)&dxInc);
			}
			/// end CONVERT_SAMPLING_INTERVAL_TO_WAVEFORM_IN_TDM
			vector<string> vstrNames, vstrValues;
			get_user_parameters(col, vstrNames, vstrValues);
			for(int ii=0; ii < vstrNames.GetSize(); ii++)
			{
				string strName;
				strName = convertToValidTDMPorpertyName(vstrNames[ii]);
				ocDDC_CreateChannelPropertyV(chHandle, strName, DDC_String, &vstrValues[ii]);
			}
			
			if( !bNeedDataConvert )
			{
				/// Hong 03/14/07 QA80-9441 FIX_REDUNDANT_ROW
				/*
				int nElementSize;				
				UINT nNumElements;	
				LPVOID lpData = col.GetInternalDataBuffer(&nElementSize, &nNumElements);	

				if( nNumElements <= 0 )
					continue; // skip set data if column is empty
				
				if( DDC_SetDataValues(chHandle, lpData, nNumElements) )
				*/
				vectorbase &vbData = col.GetDataObject();
				UINT unNum = vbData.GetSize();
				
				if( unNum <= 0 )
					continue; // skip set data if column is empty
				
				if( DDC_SetDataValues(chHandle, &vbData, unNum) ) 
				/// end FIX_REDUNDANT_ROW
					return FAIL_WRITE_CHANNEL_DATA;
			}
			else
			{
				if( FSI_COMPLEX == nType ) // // seperate deal complex type data
				{
					vectorbase &vbData = col.GetDataObject();
					UINT unNum = vbData.GetSize();
					
					if( unNum <= 0 )
						continue; // skip set data if column is empty
					
					vector vdReal, vdImage;
					vdReal = vbData;
					
					double dInfinite = generateInfiniteDouble();
					vdReal.Replace(NANUM, dInfinite, MATREPL_TEST_EQUAL);
			
					if( DDC_SetDataValues(chHandle, vdReal, unNum) )
						return FAIL_WRITE_CHANNEL_DATA;
					
					for(int mm=0; mm < vbData.GetSize(); mm++)
					{
						double dTemp[2];
						memcpy(dTemp, &vbData[mm], 16);
						vdImage.Add(dTemp[1]);
					}
					DDCChannelHandle chHandleImage;
					strName = strColName + "_image";
					if( DDC_AddChannel(cgHandle, nDataType, strName, strComment, strUint, &chHandleImage) )
						return FAIL_CREATE_CHANNEL;
					
					for(int jj=0; jj < vstrNames.GetSize(); jj++)
					{
						string strName;
						strName = convertToValidTDMPorpertyName(vstrNames[jj]);
						ocDDC_CreateChannelPropertyV(chHandleImage, strName, DDC_String, &vstrValues[jj]);
					}
					
					vdImage.Replace(NANUM, dInfinite, MATREPL_TEST_EQUAL);
					
					if( DDC_SetDataValues(chHandleImage, vdImage, unNum) )
						return FAIL_WRITE_CHANNEL_DATA;
				}
				else
					if( SetDataValuesWithConvert(col, chHandle, nDataType) )
						return FAIL_WRITE_CHANNEL_DATA;
			}
		}
		
		return IMPORTED_SUCCEED;
	}
	
private:
	string convertToValidTDMPorpertyName(LPCSTR lpcszName)
	{
		string strPropertyName(lpcszName);
		/// Hong 10/10/07 v8.0721 FIX_MISS_MISSING_VALUE_FLAG_WHEN_EXPORT
		if ( 0 == strPropertyName.CompareNoCase(MISSING_VALUE_FLAG) )
		{
			strPropertyName = DDC_CHANNEL_MISSING_VALUE_FLAG;
		}
		/// end FIX_MISS_MISSING_VALUE_FLAG_WHEN_EXPORT
		strPropertyName.Replace(' ', '_');
		return strPropertyName;
	}
	
	bool checkConvertDataType(Column& col, int& nTDMType, bool& bNeedDataConvert, int nMixTextPercent) 
	{
		int nType = col.GetInternalDataType();
		switch(nType)
		{
		case FSI_BYTE:
			nTDMType = DDC_UInt8;
			break;
		case FSI_CHAR:
			nTDMType = DDC_Int16;
			bNeedDataConvert = true;
			break;
		case FSI_SHORT:
			nTDMType = DDC_Int16;
			break;
		case FSI_USHORT:
			nTDMType = DDC_Int32;
			bNeedDataConvert = true;
			break;
		case FSI_LONG:
			nTDMType = DDC_Int32;
			break;
		case FSI_ULONG:
			nTDMType = DDC_Double;
			bNeedDataConvert = true;
			break;
		case FSI_REAL:
			nTDMType = DDC_Float;
			break;
		case FSI_DOUBLE:
			nTDMType = DDC_Double;
			bNeedDataConvert = true;
			break;
		case FSI_TEXT:
			nTDMType = DDC_String;
			bNeedDataConvert = true;
			break;
		case FSI_MIXED:
			DatasetObject dobj(col);
			int nn = dobj.PercentText();
			
			if( nn >= nMixTextPercent )
				nTDMType = DDC_String;
			else
				nTDMType = DDC_Double;
			
			bNeedDataConvert = true;
			break;
		case FSI_COMPLEX:
			nTDMType = DDC_Double;
			bNeedDataConvert = true;
			break;
		default:
			return false;
		}
		return true;
	}

	int SetDataValuesWithConvert(Column& col, DDCChannelHandle chHandle, int nChannelType)
	{
		int nType = col.GetInternalDataType();
		// seperate deal string type data
		if( FSI_TEXT == nType )
		{	// FAIL_WITH_STRING_TYPE, fail to write string type data by this code
			vector<string> vs;
			col.GetStringArray(vs);
			
			if( vs.GetSize() <= 0 )
				return IMPORTED_SUCCEED; // skip set data if column is empty
			/// Hong 02/02/07 MOVE_TO_FUNCTION
			/*
			/// AW 01/31/07 QA80-9322 GET_VECTOR_STRING_ADD_IN_VECTOR_DWORD
			//return DDC_SetDataValues(chHandle, vs, vs.GetSize()); // fail
			vector<DWORD> vDWAddr;
			okutil_make_pointers_array(&vDWAddr, &vs);
			int nSize = vs.GetSize();
			ASSERT(nSize == vDWAddr.GetSize());
			DWORD *pDWAddr = vDWAddr;
			LPSTR	*ppchAddr = (LPSTR*)(void*)pDWAddr;
			return DDC_SetDataValues(chHandle, ppchAddr, nSize); // fail
			/// END GET_VECTOR_STRING_ADD_IN_VECTOR_DWORD
			*/
			return SetTextData(vs, chHandle);
			/// end MOVE_TO_FUNCTION

		}
		
		vectorbase &vbData = col.GetDataObject();
		UINT unNum = vbData.GetSize();
		
		if( unNum <= 0 )
			return IMPORTED_SUCCEED; // skip set data if column is empty
		
		switch(nType)
		{
		case FSI_CHAR:
			vector<short> vn;
			vn = vbData;
			
			return DDC_SetDataValues(chHandle, vn, unNum);
		case FSI_USHORT:
			vector<int> vn;
			vn = vbData;
			
			return DDC_SetDataValues(chHandle, vn, unNum);
		case FSI_ULONG:
			vector<double> vd;
			vd = vbData;
			double dInfinite = generateInfiniteDouble();
			vd.Replace(NANUM, dInfinite, MATREPL_TEST_EQUAL);
			
			return DDC_SetDataValues(chHandle, vd, unNum);
		case FSI_DOUBLE:
			vector<double> vd;
			vd = vbData;		
			double dInfinite = generateInfiniteDouble();	
			
			int nFmtType = col.GetFormat();
			/// Hong 03/14/07 QA80-9301 DIRECTLY_EXPORT_TIME_AS_DOUBLE
			//if( OKCOLTYPE_TIME == nFmtType || OKCOLTYPE_DATE == nFmtType )
			if( OKCOLTYPE_DATE == nFmtType )
			/// end DIRECTLY_EXPORT_TME_AS_DOUBLE
			{
				vd -= DIFF_TIME_OFFSET_BETTEEN_TDM_OC;
				vd *= DAY_OF_A_YEAR;
				/// Hong 03/14/07 UPDATE_TO_NEW_TDM_DLL
				// new dll have some problem to deal time display format, it require to put data first, then set format can be OK
				//ocDDC_CreateChannelPropertyV(chHandle, TDM_DISPLAY_TYPE, DDC_String, TDM_TIME_DISPLAY_TYPE);
				/// end UPDATE_TO_NEW_TDM_DLL
			}
			
			vd.Replace(NANUM, dInfinite, MATREPL_TEST_EQUAL);
			/// Hong 03/14/07 UPDATE_TO_NEW_TDM_DLL
			//return DDC_SetDataValues(chHandle, vd, unNum);
			int nRet = DDC_SetDataValues(chHandle, vd, unNum);
			
			/// Hong 03/14/07 QA80-9301 DIRECTLY_EXPORT_TIME_AS_DOUBLE
			//if( OKCOLTYPE_TIME == nFmtType || OKCOLTYPE_DATE == nFmtType )
			if( OKCOLTYPE_DATE == nFmtType )
			/// end DIRECTLY_EXPORT_TME_AS_DOUBLE
			{
				ocDDC_CreateChannelPropertyV(chHandle, TDM_DISPLAY_TYPE, DDC_String, TDM_TIME_DISPLAY_TYPE);
			}
			
			return nRet;
			/// end UPDATE_TO_NEW_TDM_DLL
		case FSI_MIXED:
			// more code here
			vector<string> vs;
			col.GetStringArray(vs);
			
			if( DDC_Double == nChannelType )
			{
				double dInfinite = generateInfiniteDouble();
				vector<double> vd;
				for(int ii=0; ii < vs.GetSize(); ii++)
				{
					if( !is_numeric(vs[ii]) )
						vd.Add(dInfinite);
					else
					{
						double dTemp = atof(vs[ii]);
						vd.Add(dTemp);
					}
				}				
				vd.Replace(NANUM, dInfinite, MATREPL_TEST_EQUAL);
			
				return DDC_SetDataValues(chHandle, vd, unNum);
			}
			else
				return SetTextData(vs, chHandle);

			break;
		}
		return IMPORTED_SUCCEED;
	}
	
	int SetTextData(vector<string>& vs, DDCChannelHandle chHandle)
	{
		vector<DWORD> vDWAddr;
		okutil_make_pointers_array(&vDWAddr, &vs);
		int nSize = vs.GetSize();
		ASSERT(nSize == vDWAddr.GetSize());
		DWORD *pDWAddr = vDWAddr;
		LPSTR	*ppchAddr = (LPSTR*)(void*)pDWAddr;
		return DDC_SetDataValues(chHandle, ppchAddr, nSize); 
	}
	
	double generateInfiniteDouble()
	{
		double dd;
		LONG lHi = 0x7ff00000;
		LONG lLow =  0x00000000;
		memcpy(&dd, &lLow, 4);
		memcpy(((char*)(&dd)) + 4, &lHi, 4);
		return dd;
	}
	
protected:
	BOOL Create()
	{			
		/// Hong 09/24/07 QA80-10343 v8.0707 UPDATE_TO_NEW_DLL_SUPPORT_TDMS
		// file extension should be specified in m_strFileName already
		//if( DDC_CreateFile( m_strFileName, _L("TDM"), _L(""), _L(""), _L(""), _L(""), &m_File) )
		if( DDC_CreateFile( m_strFileName, NULL, _L(""), _L(""), _L(""), _L(""), &m_File) )
		/// end UPDATE_TO_NEW_DLL_SUPPORT_TDMS
			return FALSE;
		return TRUE;		//// AW 01/30/07 MORE_WORK_IN_EXPORT_TDM
	}
	
	BOOL Close()
	{
		if( DDC_SaveFile(m_File) )
			return FALSE;
		
		if( DDC_CloseFile(m_File) )
			return FALSE;
		
		return TRUE;
	}
private:
	string		m_strFileName;
	DDCFileHandle	m_File;
	BOOL		m_bValidTDM;
};

#endif //EXPORTTDM_H
