/*------------------------------------------------------------------------------*
 * File Name: CMATFile.h				 										*
 * Creation: Soapy 																*
 * Purpose: CMATFile class for MAT file import									*
 * Copyright (c) ABCD Corp.	2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010		*
 * All Rights Reserved															*
 * 																				*
 * Modification Log:															*
 *------------------------------------------------------------------------------*/

#ifndef _CMATFILE_H
#define _CMATFILE_H
 
#include <origin.h>

#include <OArrayBase.h>

#define MAX_VAR_NAME_SIZE 32		// maximum length of MAT file name
#define SUPPORT_DIMESION 2			// Currently, Origin only support array of 1 or 2 dimension
#define DATA_BOUNDARY_IN_BYTE 8		// Data Elements in MAT file are align to 8 byte boundary

enum MATArrayClass{
MAT_ARRAY_mxCELL_CLASS = 1,		// Not support currently
MAT_ARRAY_mxSTRUCTURE_CLASS = 2,// Not support currently
MAT_ARRAY_mxOBJECT_CLASS = 3,	// Not support currently
MAT_ARRAY_mxCHARACTER_CLASS = 4,
MAT_ARRAY_mxSPARSE_CLASS = 5,
MAT_ARRAY_mxDOUBLE_CLASS = 6,
MAT_ARRAY_mxSINGLE_CLASS = 7,
MAT_ARRAY_mxINT8_CLASS = 8,
MAT_ARRAY_mxUINT8_CLASS = 9,
MAT_ARRAY_mxINT16_CLASS = 10,
MAT_ARRAY_mxUINT16_CLASS = 11,
MAT_ARRAY_mxINT32_CLASS = 12,
MAT_ARRAY_mxUINT32_CLASS = 13,
MAT_ARRAY_mxCOMPLEX_CLASS = 14 
};

enum MATDataType{
MAT_DataType_miINT8 = 1,
MAT_DataType_miUINT8 = 2,
MAT_DataType_miINT16 = 3,
MAT_DataType_miUINT16 = 4,
MAT_DataType_miINT32 = 5,
MAT_DataType_miUINT32 = 6,
MAT_DataType_miSINGLE = 7,
MAT_DataType_miReserved1 = 8,
MAT_DataType_miDOUBLE = 9,
MAT_DataType_miReserved2 = 10,
MAT_DataType_miReserved3 = 11,
MAT_DataType_miINT64 = 12,		// Not support currently
MAT_DataType_miUINT64 = 13,		// Not support currently
MAT_DataType_miMATRIX = 14
};


class CElementBase : public OObject
{
public:

	CElementBase(bool bIsLittleEndian, file *pfMATFile)
	{
		Init(bIsLittleEndian, pfMATFile);
	}

	~CElementBase()
	{
		
	}
	
private:
	void Init(bool bIsLittleEndian, file *pfMATFile)
	{
		m_bIsLittleEndian = bIsLittleEndian;
		m_pfMATFile = pfMATFile;
	}

public:
	virtual BOOL ReadTag() 			// Read the tag of the data element
	{
		if(!m_pfMATFile->IsOpen())
			return false;
		
		m_lPosition = m_pfMATFile->GetPosition();		
		
		unsigned int iTemp;
			
		if(1!=m_pfMATFile->ReadInt(&iTemp, sizeof(int), 1, m_bIsLittleEndian))
			return false;
	
		if ( iTemp > 0x10000 )		// Is the data stored in a compressed form?
		{
			m_dwSize = iTemp / 0x10000;
			m_dwDataTye = mod(iTemp, 0x10000);
		}
		else
		{	
			m_dwDataTye = iTemp;
			if(1!=m_pfMATFile->ReadInt(&m_dwSize, sizeof(int), 1, m_bIsLittleEndian))
				return false;
		}
		
		return TRUE;
	}
	
	BOOL SkipRemainBytes()			// Skip the remain bytes to align to the 8 byte boundary
	{
		if(m_pfMATFile->IsOpen())
		{
			LONG lOffset;
			if( 0 != mod(m_dwSize, DATA_BOUNDARY_IN_BYTE))  
			{
				if( m_dwSize <= sizeof(int))
					lOffset = DATA_BOUNDARY_IN_BYTE;
				else
					lOffset = (m_dwSize/DATA_BOUNDARY_IN_BYTE+2)*DATA_BOUNDARY_IN_BYTE; 
			}
			else
			{
				lOffset = m_dwSize+DATA_BOUNDARY_IN_BYTE;
			}
			
			m_pfMATFile->Seek(lOffset+m_lPosition, file::begin);

			return true;
		}
		
		return false;		
	}

public:
	LONG 			m_lPosition;		// the position of the data element in the file
	unsigned int	m_dwSize;			// the size of the data element
	unsigned int	m_dwDataTye;		// the data type 
	BOOL			m_bIsLittleEndian;	// Big_Endian or Little_Endian
	file*			m_pfMATFile;		// the file pointer
};


// The Array_Flag Sub_element
class CArrayFlagEle : public CElementBase
{
public:
	CArrayFlagEle(bool bIsLittleEndian, file *pfMATFile) : CElementBase(bIsLittleEndian,pfMATFile)
	{		
	}
	
	BOOL GetData(bool& bIsComplex, uint& bClassType)
	{
		if ( !ReadTag() )
			return FALSE;
		int bArrayFlag[2];
		if( 2 != m_pfMATFile->ReadInt(&bArrayFlag, sizeof(int), 2, m_bIsLittleEndian))
			return false;
			
		bIsComplex = (bArrayFlag[0] & 0x0800)/ 0x100;
		bClassType = bArrayFlag[0] & 0xFF;
		return true;
	}
};


// The Dimension Sub_element
class CDimensionEle : public CElementBase
{
public:
	CDimensionEle(bool bIsLittleEndian, file *pfMATFile) : CElementBase(bIsLittleEndian,pfMATFile)
	{
	}
	
	BOOL GetData(vector<uint>& vecDimension)
	{
		if ( !ReadTag() )
			return FALSE;
		uint nDimensionSize = m_dwSize / sizeof(int);
			
		vecDimension.SetSize(nDimensionSize);
		uint nRet = m_pfMATFile->ReadInt(&vecDimension, sizeof(int), nDimensionSize, m_bIsLittleEndian);
		if(nRet != nDimensionSize)
			return false;
		
		SkipRemainBytes();
		
		return true;
	}
};


// The Array Name Sub_element
class CArrayNameEle : public CElementBase
{
public:
	CArrayNameEle(bool bIsLittleEndian, file *pfMATFile) : CElementBase(bIsLittleEndian,pfMATFile)
	{
	}
	
	BOOL GetData(string& strArrayName)
	{
		if ( !ReadTag() )
			return FALSE;
			
		char strName[MAX_VAR_NAME_SIZE];  ///Soapy 08/21/03 QA70-3572 v7.0664 CODE_UPDATE_FOR_IMPOTR_MAT_FILE_2
		if( m_dwSize != m_pfMATFile->ReadInt(strName, sizeof(char), m_dwSize, m_bIsLittleEndian))
			return false;
		strArrayName = strName;
		
		SkipRemainBytes();
		
		return true;
	}
};

// The Array Data Sub_element
class CArrayDataEle : public CElementBase
{
public:
	CArrayDataEle(bool bIsLittleEndian, file *pfMATFile) : CElementBase(bIsLittleEndian,pfMATFile)
	{
	}
	
	int GetData(vector& vData)   
	{
		if ( !ReadTag() )
			return FALSE;

		bool bSuccess = false;
		
		int nItem;
		
		switch(m_dwDataTye)
		{
		case MAT_DataType_miINT8: // INT8
				nItem = m_dwSize / sizeof(char);
				vector<char> vecCh(nItem);
				if(nItem == m_pfMATFile->ReadInt(&vecCh, sizeof(byte), nItem, m_bIsLittleEndian))
				{
					vData=vecCh; 
					bSuccess = true;
					
				}
				break;
					
		case MAT_DataType_miUINT8: // Unsigned INT8
				nItem = m_dwSize / sizeof(byte);
				vector<byte> vecByte(nItem);
				if(nItem == m_pfMATFile->ReadInt(&vecByte, sizeof(byte), nItem, m_bIsLittleEndian))
				{
					vData=vecByte;
					bSuccess = true;
					
				}
				break;
			
		case MAT_DataType_miINT16: // INT16
				nItem = m_dwSize / sizeof(short);
				vector<short> vecInt16(nItem);
				if(nItem == m_pfMATFile->ReadInt(&vecInt16, sizeof(short), nItem, m_bIsLittleEndian))
				{
					vData=vecInt16;
					bSuccess = true;
				}
				break;
			
		case MAT_DataType_miUINT16: // Unsigned INT16
				nItem = m_dwSize / sizeof(short);
				vector<unsigned short> vecUInt16(nItem);
				if(nItem == m_pfMATFile->ReadInt(&vecUInt16, sizeof(short), nItem, m_bIsLittleEndian))
				{
					vData=vecUInt16;
					bSuccess = true;
				}
				break;
			
		case MAT_DataType_miINT32: // INT32
				nItem = m_dwSize / sizeof(int);
				vector<int> vecInt32(nItem);
				if(nItem == m_pfMATFile->ReadInt(&vecInt32, sizeof(int), nItem, m_bIsLittleEndian))
				{
					vData=vecInt32;
					bSuccess = true;
				}
				break;
				
		case MAT_DataType_miUINT32: // Unsigned INT32
				nItem = m_dwSize / sizeof(int);
				vector<unsigned int> vecUInt32(nItem);
				if(nItem == m_pfMATFile->ReadInt(&vecUInt32, sizeof(int), nItem, m_bIsLittleEndian))
				{
					vData=vecUInt32;
					bSuccess = true;
				}
				break;
			
		case MAT_DataType_miSINGLE: // Single Format
				nItem = m_dwSize / sizeof(float);
				vector<float> vecFloat(nItem);
				if(nItem == m_pfMATFile->ReadFloat(&vecFloat, sizeof(float), nItem, m_bIsLittleEndian))
				{
					vData=vecFloat;
					bSuccess = true;
				}
				break;
					 
			
		case MAT_DataType_miDOUBLE: // Double Format
				nItem = m_dwSize / sizeof(double);
				vData.SetSize(nItem);
				if(nItem == m_pfMATFile->ReadFloat(&vData, sizeof(double), nItem, m_bIsLittleEndian))
				{
					//vecTemp=vecFloat;
					bSuccess = true;
				}
				break;
				
		case MAT_DataType_miINT64: // INT64 -- Not Support Now			
		case MAT_DataType_miUINT64: // Unsigned INT64 -- Not Support Now
				return 0;
				break;		
			
		default: return -1;// 8, 10, 11 Are Reserved
		}
		
		if(bSuccess)
		{
			SkipRemainBytes();
			return 1;
		}
		
		return -1;		
	}
		
};


// The Array Element Which Contains Particular Sub_elements
class CDataElement : public CElementBase
{
public:
	CDataElement(bool bIsLittleEndian, file *pfMATFile) : CElementBase(bIsLittleEndian,pfMATFile)
	{
		Reset();
	}

	BOOL ReadTag()
	{
		if(!CElementBase::ReadTag())
			return false;
		
		if(m_dwDataTye != MAT_DataType_miMATRIX) 
			return false;
		
		return TRUE;
	}
	
	BOOL GetData()
	{
		if ( !ReadTag() )
			return FALSE;
		
		if(!ReadSubElement())
		{
			Reset();
			return false;
		}
		
		return true;
	}
	
private:
	void Reset();
	
	bool ReadSubElement();
	
	int ReadRegularArray(bool bIsComplex);
	
	int ReadSparseArray(bool bIsComplex);
	
public:
	string m_strVarName;					// Variable Name	
	vector<uint> m_vDimesion;  				// The size of each dimension
	bool m_bImportable;						// Origin import support or not
	vector m_vReal;							// The real part of the data
	vector m_vImaginary;
	
};
	
void CDataElement::Reset()
{
	m_bImportable = false;
	m_vDimesion.SetSize(0); 
	m_vReal.SetSize(0);
	m_vImaginary.SetSize(0);
}
	
bool CDataElement::ReadSubElement()
{
	bool bIsComplex;
	
	// Read the Array_Flag
	CArrayFlagEle ArrayFlag(m_bIsLittleEndian, m_pfMATFile);
	if(!ArrayFlag.GetData(bIsComplex, m_dwDataTye))
		return false;
	
	// Read the Dimension Array
	CDimensionEle DimensionArray(m_bIsLittleEndian, m_pfMATFile);
	if(!DimensionArray.GetData(m_vDimesion))
		return false;
	
	// Read the Array Name
	CArrayNameEle ArrayName(m_bIsLittleEndian, m_pfMATFile);
	if(!ArrayName.GetData(m_strVarName))
		return false;
	
	// Skip the remain bytes of this data element if it's not supported by Origin
	if(( m_dwDataTye < MAT_ARRAY_mxCHARACTER_CLASS) ||
		(m_vDimesion.GetSize() > SUPPORT_DIMESION))		
	{
		SkipRemainBytes();
		return true;
	}
	
	// Read the Array Data
	int nRet;
	if( MAT_ARRAY_mxSPARSE_CLASS != m_dwDataTye)    //regular array  
	{			
		nRet = ReadRegularArray(bIsComplex);                
	}
	else
	{
		nRet = ReadSparseArray(bIsComplex); 
	}
		
	// Skip the remain bytes
	if(nRet>=0)
	{
		SkipRemainBytes();
		if(nRet == 1)
			m_bImportable = true;
		return true;
	}
		
	return false;
}

// Read the regular Array	
int CDataElement::ReadRegularArray(bool bIsComplex)
{
	int nRet;

	CArrayDataEle ArrayData(m_bIsLittleEndian, m_pfMATFile);
	nRet = ArrayData.GetData(m_vReal);
	if(nRet <= 0)
		return nRet;
		
	if(bIsComplex)
	{
		m_dwDataTye = MAT_ARRAY_mxCOMPLEX_CLASS;
		nRet = ArrayData.GetData(m_vImaginary);
		if(nRet <= 0)
			return nRet;
	}
		
	return 1;
}

// Read the Sparse Array which is stored in a packed form	
int CDataElement::ReadSparseArray(bool bIsComplex)
{
	int nRet;
	
	// Get the row index and column index of the non-zero array element
	vector<uint> vDimRows, vDimCols;
	CDimensionEle NonZDim(m_bIsLittleEndian, m_pfMATFile);
	if(!NonZDim.GetData(vDimRows))
		return -1;
	if(!NonZDim.GetData(vDimCols))
		return -1;
	int nNonZero = vDimRows.GetSize();
	vDimCols.SetSize(nNonZero);
	
	// Calculate the size of the array
	int nSize = m_vDimesion[0] * m_vDimesion[1];
	m_vReal.SetSize(nSize);
	m_vImaginary.SetSize(nSize);
	
	// Calculate the offset of each non-zero array element
	int nRows = m_vDimesion[0];
	vector<uint> vIndex(nNonZero);
	vIndex = vDimRows + vDimCols * nRows;
	
	// Get the values of the non-zero array element 
	vector vReal, vImaginary;
	CArrayDataEle ArrayData(m_bIsLittleEndian, m_pfMATFile);
	nRet = ArrayData.GetData(vReal);
	if(nRet <= 0)
		return nRet;

	// Assign the value to the Data Element
	for(int i=0; i<nNonZero; i++)
	{
		uint nIndex = vDimRows[i]+vDimCols[i]*m_vDimesion[0];
		m_vReal[vIndex[i]] = vReal[i];
	}
	
	// Read the imaginary part
	if(bIsComplex)
	{
		m_dwDataTye = MAT_ARRAY_mxCOMPLEX_CLASS;
		nRet = ArrayData.GetData(vImaginary);
		if(nRet <= 0)
			return nRet;
		
		for(i=0; i<nNonZero; i++)
		{
			m_vImaginary[vIndex[i]]  = vImaginary[i];
		}
	}
	else
	{
		m_vImaginary.SetSize(0);
	}
		
	return 1;
}


#define MAT_FILE_HEAD_SIZE 126


class CPtrDataElementArray : public OPtrArray
{
public:
	~CPtrDataElementArray()
	{
		RemoveAll();
	}
	
public:
	void RemoveAt(int nIndex)
	{
		CDataElement* pDataElement = GetAt(nIndex);
		NICE_REMOVE(pDataElement);
		OPtrArray::RemoveAt(nIndex);
	}
	
	CDataElement* GetAt(int nIndex)
	{
		return (CDataElement*)OPtrArray::GetAt(nIndex);
	}
	
	int Add(CDataElement* pDataElement)
	{
		return OPtrArray::Add((DWORD)pDataElement);
	}
	
	void RemoveAll()
	{
		int nSize = OPtrArray::GetSize();
		for(int i=nSize-1; i>=0; i--)
		{
			RemoveAt(i);
		}
		OPtrArray::RemoveAll();
	}
};


class CMATFile 
{
public:
	
	CMATFile()
	{
		Init();
	}
	
	~CMATFile()
	{

	}
	
private:
	
	void Init();

	void Reset();

	// Add the data info to the tree
	bool SetDataInfoToTree(CDataElement* FileElement);

public:
	// Read the .mat file
	bool ReadMATFile(LPCSTR lpcstrFileName);
	
	// Set the element data to matrices
	bool SetMatrix(uint nIndex, matrix& mReal, matrix& mImaginary);
		
	// Get all the data elements Origin support
	bool GetAllImportable(vector<uint>& vecIndex);
	
	// Get all the real data elements Origin support
	bool GetAllRealImportable(vector<uint>& vecIndex);

	void OutMatFileInfo();
	
	
private:	
	file m_fMATFile;			// file object 
	bool m_bIsLittleEndian;		
	
	bool m_bReadFileStatus;		// Indicate the status of the reading
	
	CPtrDataElementArray m_Data;
	
public:
	Tree m_trFileInfo;			// the summary of the data elements in the file (for import dialog).

};


void CMATFile::Init()
{
	m_bReadFileStatus = false;	
}

void CMATFile::Reset()
{
	Init();
	if ( m_fMATFile.IsOpen() )
		m_fMATFile.Close();
		
	m_trFileInfo.Reset();
	
	m_Data.RemoveAll();

}
	
// Add the data info to the tree
bool CMATFile::SetDataInfoToTree(CDataElement* FileElement)
{ 
	// Set the TreeNode's name and ID
	int nID = m_Data.GetSize();
	TreeNode trNodeNew = m_trFileInfo.AddNode(FileElement->m_strVarName, nID);
	
	// The description of array dimension
	string strDim = FileElement->m_vDimesion[0];
	for(int ii=1; ii<FileElement->m_vDimesion.GetSize(); ii++)
	{
	   	strDim += " x " + FileElement->m_vDimesion[ii];
	}
	trNodeNew.Size.strVal=strDim;
	    
	// If the data element is imported
	trNodeNew.Imported.nVal = FileElement->m_bImportable;

	// The description of the array type
	string strType;	
	switch(FileElement->m_dwDataTye)
	{
   		case MAT_ARRAY_mxINT8_CLASS: 
   				strType = "INT8";
		 		break;
		case MAT_ARRAY_mxUINT8_CLASS: 
				strType = "UINT8";
	  			break;
		case MAT_ARRAY_mxINT16_CLASS:
				strType = "INT16";
	    		break;
		case MAT_ARRAY_mxUINT16_CLASS:
				strType = "UINT16";
	    		break;
		case MAT_ARRAY_mxINT32_CLASS:
				strType = "INT32";
	    		break;
		case MAT_ARRAY_mxUINT32_CLASS:
				strType = "UINT32";
	    		break;
		case MAT_ARRAY_mxSINGLE_CLASS: 
				strType = "SINGLE";
	    		break;
		case MAT_ARRAY_mxDOUBLE_CLASS: 
				strType = "DOUBLE";
	    		break;
	    case MAT_ARRAY_mxCHARACTER_CLASS: 
	    		strType = "CHAR";
	    		break;
	    case MAT_ARRAY_mxCOMPLEX_CLASS:
	    		strType = "COMPLEX";
	    		break;
		case MAT_ARRAY_mxSPARSE_CLASS:
				strType = "DOUBLE SPARSE";
				break;
	    
		default: strType = "UNKNOWN";
	    		break;
	}
	  
    trNodeNew.Type.strVal=strType;	    
			
	return true;
}	

// Read the .mat file
bool CMATFile::ReadMATFile(LPCSTR lpcstrFileName)
{
	Reset();

	// Check the input file name
	string strFileName = lpcstrFileName;
	if((strFileName.GetLength() == 0) || !strFileName.IsFile() ||
			!m_fMATFile.Open( strFileName, file::modeRead ) || !m_fMATFile.IsOpen())
	{
		return false;
	}
	
	// Check the file length
	if(mod(m_fMATFile.GetLength(), DATA_BOUNDARY_IN_BYTE) != 0) 
	{
		m_fMATFile.Close();
		return false;
	}	
		
	// Skip the MAT-File Header
	m_fMATFile.Seek(MAT_FILE_HEAD_SIZE, file::begin);
		
	char cTemp[2];
	m_fMATFile.Read( cTemp, 2);
	if(cTemp[0] == 'I' && cTemp[1] == 'M')
	{
		m_bIsLittleEndian = true;
	}
	else if(cTemp[0] == 'M' && cTemp[1] == 'I')
	{
		m_bIsLittleEndian = false;
	}
	else
	{
		m_fMATFile.Close();
		return false;
	}
				
	// Read the Data Elements
	uint nFileSize = m_fMATFile.GetLength();
	while(nFileSize > m_fMATFile.GetPosition())
	{
		CDataElement* FileElement = new CDataElement(m_bIsLittleEndian,&m_fMATFile);
		if(!FileElement->GetData())
		{
			Reset();
			NICE_REMOVE(FileElement);
			return false;
		}
		
		SetDataInfoToTree(FileElement);
		m_Data.Add(FileElement);		
	}
	
	m_fMATFile.Close();
	m_bReadFileStatus = true;
	
	return true;
}

/*---- The following functions should be called after the CMATFile::ReadMATFile() and
		 m_bReadFileStatus = true ------
*/
bool CMATFile::SetMatrix(uint nIndex, matrix& mReal, matrix& mImaginary)
{
	if(!m_bReadFileStatus)
		return false;
	
	CDataElement* pDataElement = m_Data.GetAt(nIndex);
	if((!pDataElement)||(!pDataElement->m_bImportable))
		return false;
	
	int nRows = pDataElement->m_vDimesion[0];
	int nCols = pDataElement->m_vDimesion[1];
	mReal.SetSize(nRows, nCols);
	mImaginary.SetSize(nRows, nCols);
	
	mReal.SetByVector(pDataElement->m_vReal, false);
	if(pDataElement->m_dwDataTye == MAT_ARRAY_mxCOMPLEX_CLASS)
	{
		mImaginary.SetByVector(pDataElement->m_vImaginary, false);
	}
	else
	{
		mImaginary = 0;
	}
	
	return true;
}


// Get all the data elements Origin support
bool CMATFile::GetAllImportable(vector<uint>& vecIndex)
{
	if(!m_bReadFileStatus)
		return false;
		
	vecIndex.SetSize(0);
		
	for(int ii=0; ii<m_Data.GetSize(); ii++)
	{
		CDataElement* pDataElement = m_Data.GetAt(ii);
		if(pDataElement->m_bImportable)
			vecIndex.Add(ii);
	}
		
	return true;
}
	
// Get all the real data elements Origin support
bool CMATFile::GetAllRealImportable(vector<uint>& vecIndex)
{
	if(!m_bReadFileStatus)
		return false;
		
	vecIndex.SetSize(0);
		
	for(int ii=0; ii<m_Data.GetSize(); ii++)
	{
		CDataElement* pDataElement = m_Data.GetAt(ii);
		if(pDataElement->m_bImportable && (pDataElement->m_dwDataTye != MAT_ARRAY_mxCOMPLEX_CLASS))
			vecIndex.Add(ii);
	}
		
	return true;
}
	
void CMATFile::OutMatFileInfo()
{
	out_tree(m_trFileInfo);
}

#endif  //_CMATFILE_H


	
	