/********************************************************************************
*	ファイル名：IniFile.cpp														*
*	用　　　途：ＩＮＩ形式のファイル書式展開									*
*	備　　　考：セクション名およびキーワードは大文字小文字を区別する			*
*	履　　　歴：																*
********************************************************************************/
/****************************
*	 コンパイル環境設定		*
****************************/
#include "stdafx.h"
/****************************
*	インクルードファイル	*
****************************/
#include "IniFile.h"
/****************************
*		 マクロ定数			*
****************************/
/****************************
*		 構造体定義			*
****************************/
/****************************
*		 クラス定義			*
****************************/
/****************************
*		 関数　宣言			*
****************************/
/****************************
*		 変数　宣言			*
****************************/
/********************************************************************************
*	関数名：CIniFile															*
*	用　途：デフォルトコンストラクタ											*
*	引き数：																	*
*	戻り値：																	*
*	備　考：																	*
********************************************************************************/
CIniFile::CIniFile() : m_FileName()
{
	m_nSearchIndex	= 0;
}

/********************************************************************************
*	関数名：~CIniFile															*
*	用　途：デストラクタ														*
*	引き数：																	*
*	戻り値：																	*
*	備　考：																	*
********************************************************************************/
CIniFile::~CIniFile()
{
	RemoveAll();
}

/********************************************************************************
*	関数名：SetFileName															*
*	用　途：ファイル読み込み													*
*	引き数：ファイル名															*
*	戻り値：																	*
*	備　考：																	*
********************************************************************************/
void CIniFile::SetFileName(LPCTSTR szFileName)
{
	m_FileName = szFileName;
}

/********************************************************************************
*	関数名：Load																*
*	用　途：ファイル読み込み													*
*	引き数：ファイル名															*
*	戻り値：成功でＴＲＵＥ、失敗でＦＡＬＳＥ									*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::Load(LPCTSTR szFile)
{
	FILE			   *tpFile = 0;
	errno_t				nError;

	CString				strFileMode;
	CString				strBuf;
	CString				strKey, strVal;
	CString				strTmp;
	INT_PTR				nIndex;
	INT32				n;

	if (szFile)
	{
		m_FileName = szFile;
	}

	/***** UNICODEテキストとして読み込むかどうかでオープン方法を変える *****/
#ifndef _UNICODE
	strFileMode = _T("rt");
#else
	strFileMode = _T("rt, ccs=UNICODE");
#endif

	nError = _tfopen_s(&tpFile, m_FileName, strFileMode);
	if (nError != 0)
	{
		return FALSE;
	}

	RemoveAll();

	while ( ReadFileString(tpFile, &strBuf) )	/* delete '\n'*/
	{
		n = Parse(&strKey, &strVal, strBuf, TRUE);
		TRACE(_T("Read %s %s\n"), strKey, strVal);
		switch(n)
		{
		case 1:
			nIndex = AddSection(strKey);
			ASSERT(nIndex >= 0);
			break;
		case 2:
			if (LookUp(nIndex, strKey) < 0)
			{
				SetAt(nIndex, strKey, strVal);
			}
			else
			{
				TRACE(_T("[%s].%sは既に定義済。２つ目以降は無視されます。\n"), strKey, strVal);
			}
			break;
		default:
			TRACE(_T("無効なラインを検出しました。スキップします\n"));
			break;
		}
	}

	fclose( tpFile );

	//INT64 i, j;

	//for (i = 0; i < m_SectionArray.GetCount(); i++)
	//{
	//	CSection	section;
	//	section = m_SectionArray.GetAt(i);
	//	TRACE(_T("[%s] %lld\n"), (LPCTSTR)section.GetName(), section.GetCount());
	//	for (j = 0; j < section.GetCount(); j++)
	//	{
	//		CString key, val;
	//		section.GetAt(j, &key, &val);
	//		TRACE(_T("%s = %s\n"), key, val);
	//	}
	//}

	return TRUE;
}

/********************************************************************************
*	関数名：Save																*
*	用　途：ファイル書き込み													*
*	引き数：ファイル名															*
*	戻り値：																	*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::Save(LPCTSTR szFile)
{
	BOOL			bRet 	= FALSE;
	FILE		   *tpFile	= 0;
	errno_t			nError;
	int				nWritten ;
	CString			strFileMode;

	CString			strKey, strVal;
	CString			strBuf;
	CSection		section;
	INT_PTR			nSectionNum;
	INT_PTR			nProfileNum;
	INT_PTR			i, j;

	if (szFile)
	{
		m_FileName = szFile;
	}

	/***** UNICODEテキストとして書き込むかどうかでオープン方法を変える *****/
#ifndef _UNICODE
	strFileMode = _T("wt");
#else
	strFileMode = _T("wt, ccs=UNICODE");
#endif

	nError = _tfopen_s(&tpFile, m_FileName, strFileMode);
	if (nError != 0)
	{
		return FALSE;
	}

	nSectionNum = m_SectionArray.GetCount();

	for (i = 0; i < nSectionNum; i++)
	{
		section = m_SectionArray.GetAt(i);

		/***** セクション名の記録 *****/
		strBuf.Format(_T("[%s]\n"), (LPCTSTR)(section.GetName()));

		nWritten = _ftprintf_s(tpFile, _T("%s"), (LPCTSTR)strBuf);
		if (nWritten < 0)
		{
			goto EXIT;
		}

		/***** 項目の記録 *****/
		nProfileNum	= section.GetCount();
		for (j = 0; j < nProfileNum; j++)
		{
			section.GetAt(j, &strKey, &strVal);
			strBuf.Format(_T("%s=%s\n"), (LPCTSTR)strKey, (LPCTSTR)strVal);

			nWritten = _ftprintf_s(tpFile, _T("%s"), (LPCTSTR)strBuf);
			if (nWritten < 0)
			{
				goto EXIT;
			}
		}
	}
	
	bRet = TRUE;
	
EXIT:
	fclose( tpFile );

	return bRet;
}

/********************************************************************************
*	関数名：GetProfile															*
*	用　途：値の取得															*
*	引き数：セクション名、キーワード名、値格納場所、値のデータ型				*
*	戻り値：対応する値があればＴＲＵＥ、無ければＦＡＬＳＥ						*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::GetProfile(
							TCHAR  *szSection,
							TCHAR  *szKey,
							void   *vpVal,
							INT32	nAttr,
							void   *vpDefault
						 )
{
	CSection	section;
	CString		strVal;
	INT_PTR		nCnt;
	INT_PTR		i;

	/***** セクション探索 *****/
	nCnt = m_SectionArray.GetCount();

	for (i = 0; i < nCnt; i++)
	{
		section = m_SectionArray.GetAt( (m_nSearchIndex + i) % nCnt);

		if (section.Match(szSection)){

			m_nSearchIndex = (m_nSearchIndex + i) % nCnt;
			i = m_nSearchIndex;
			break;
		}
	}

	if (i >= nCnt)
	{
		/***** デフォルト値が無い場合はセクションは追加しない *****/
		if (vpDefault == 0)
		{
			return FALSE;
		}
		/***** 省略値が指定されている場合、省略を設定しその値を戻す *****/
		i = AddSection(szSection);
		if (i < 0)
		{
			return FALSE;
		}
		section = m_SectionArray.GetAt(i);
	}

	/***** プロファイル探索 *****/
	i = section.LookUp(szKey, &strVal);

	if (i < 0)
	{
		/***** デフォルト値が無い場合は追加しない *****/
		if (vpDefault == 0)
		{
			return FALSE;
		}

		/***** デフォルト値を登録する *****/
		::DataToString(&strVal, vpDefault, nAttr);

		section.Add(szKey, strVal);

		/***** 同一アドレスでなければ設定する *****/
		if (vpVal == vpDefault)
		{
			return TRUE;
		}
	}

	::StringToData(vpVal, strVal, nAttr);

	return TRUE;
}

/********************************************************************************
*	関数名：GetProfile															*
*	用　途：値の取得															*
*	引き数：セクション名、キーワード名、値（文字列）格納場所					*
*	戻り値：対応する値があれば値の格納場所、無ければ０							*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::GetProfile(TCHAR *szSection, TCHAR *szKey, CString *szDst)
{
	CSection	section;
	CString		strVal;
	INT_PTR		nCnt;
	INT_PTR		i;

	/***** セクション探索 *****/
	nCnt = m_SectionArray.GetCount();

	for (i = 0; i < nCnt; i++)
	{
		section = m_SectionArray.GetAt((m_nSearchIndex + i) % nCnt);

		if (section.Match(szSection)) {
			m_nSearchIndex = (m_nSearchIndex + i) % nCnt;
			i = m_nSearchIndex;
			break;
		}
	}

	if (i >= nCnt) return FALSE;

	i =	section.LookUp(szKey);

	if (i < 0) return FALSE;

	*szDst = section[i];

	return TRUE;
}

/********************************************************************************
*	関数名：SetProfile															*
*	用　途：値の設定															*
*	引き数：セクション名、キーワード名、値（文字列）格納場所、値属性			*
*	戻り値：成功でＴＲＵＥ、失敗でＦＡＬＳＥ									*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::SetProfile(TCHAR *szSection, TCHAR *szKey, void  *vpVal, INT32 nAttr)
{
	CString	strKey;
	CString	strVal;
	INT_PTR	nIndex;

	strKey	= szKey;
	::DataToString(&strVal, vpVal, nAttr);

	nIndex	= AddSection(szSection);

	if (nIndex < 0) return FALSE;

	SetAt(nIndex, strKey, strVal);

	return TRUE;
}

/********************************************************************************
*	関数名：SetProfile															*
*	用　途：値の設定															*
*	引き数：セクション名、キーワード名、書式指定子、値（文字列）格納場所、		*
*			値属性																*
*	戻り値：成功でＴＲＵＥ、失敗でＦＡＬＳＥ									*
*	備　考：書式指定が可能														*
********************************************************************************/
BOOL CIniFile::SetProfile(
							TCHAR  *szSection,
							TCHAR  *szKey,
							TCHAR  *szFormat,
							void   *vpVal,
							INT32	nAttr
						  )
{
	CString	strKey;
	CString	strVal;
	INT_PTR	nIndex;

	strKey	= szKey;
	::DataToString(&strVal, szFormat, vpVal, nAttr);

	nIndex	= AddSection(szSection);

	if (nIndex < 0) return FALSE;

	SetAt(nIndex, strKey, strVal);

	return TRUE;
}

/********************************************************************************
*	関数名：RemoveAll															*
*	用　途：ＩＮＩファイル情報の削除											*
*	引き数：																	*
*	戻り値：																	*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::RemoveAll()
{
	CSection	section;
	INT_PTR		nCnt;
	INT_PTR		i;

	nCnt = m_SectionArray.GetCount();

	for (i = 0; i < nCnt; i++)
	{
		section = m_SectionArray.GetAt(i);
		section.RemoveAll();
	}

	m_SectionArray.RemoveAll();

	return TRUE;
}

/********************************************************************************
*	関数名：RemoveSection														*
*	用　途：セクションの削除													*
*	引き数：セクション名、指定セクションが無い場合エラーにするかの判断フラグ	*
*	戻り値：成功でＴＲＵＥ、失敗でＦＡＬＳＥ									*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::RemoveSection(TCHAR *szSection, BOOL bErrNotExist)
{
	CSection	section;
	INT_PTR		nCnt;
	INT_PTR		i;

	nCnt = m_SectionArray.GetCount();

	for (i = 0; i < nCnt; i++)
	{
		section = m_SectionArray.GetAt(i);
		if ( section.Match(szSection) ) break;
	}

	if (i >= nCnt)
	{
		return ~bErrNotExist;
	}

	section.RemoveAll();

	m_SectionArray.RemoveAt(i);

	return TRUE;
}

/********************************************************************************
*	関数名：RemoveKey															*
*	用　途：項目の削除															*
*	引き数：セクション名、キーワード名、										*
*			指定セクションが無い場合エラーにするかの判断フラグ					*
*	戻り値：成功でＴＲＵＥ、失敗でＦＡＬＳＥ									*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::RemoveKey(TCHAR *szSection, TCHAR *szKey, BOOL bErrNotExist)
{
	CSection	section;
	INT_PTR		nCnt;
	INT_PTR		i;

	nCnt = m_SectionArray.GetCount();

	for (i = 0; i < nCnt; i++)
	{
		section = m_SectionArray.GetAt((m_nSearchIndex + i) % nCnt);
		if ( section.Match(szSection) ){
			m_nSearchIndex = (m_nSearchIndex + i) % nCnt;
			i = m_nSearchIndex;
			break;
		}

	}

	if (i >= nCnt) 
	{
		return ~bErrNotExist;
	}

	i = section.LookUp(szKey);

	if (i < 0)
	{
		return ~bErrNotExist;
	}

	section.RemoveAt(i);
	
	return TRUE;
}

/********************************************************************************
*	関数名：AddSection															*
*	用　途：セクションの追加													*
*	引き数：セクション名														*
*	戻り値：要素番号、失敗時はマイナス											*
*	備　考：既に登録済のセクションの場合は追加しない。							*
********************************************************************************/
INT_PTR CIniFile::AddSection(LPCTSTR szSectionName)
{
	CSection	section;
	INT_PTR		nCnt, i;

	nCnt = m_SectionArray.GetCount();

	for (i = 0; i < nCnt; i++)
	{
		section = m_SectionArray.GetAt((m_nSearchIndex + i) % nCnt);
		if (section.Match(szSectionName))
		{
			m_nSearchIndex = (m_nSearchIndex + i) % nCnt;
			return m_nSearchIndex;
		}
	}

	section.RemoveAll();
	section.SetName(szSectionName);

	i =  m_SectionArray.Add(section);

	return i;
}

/********************************************************************************
*	関数名：LookUp																*
*	用　途：キーワード検索														*
*	引き数：セクション配列の要素番号、キーワード、値格納場所					*
*	戻り値：値が格納されている要素番号											*
*	備　考：																	*
********************************************************************************/
INT_PTR CIniFile::LookUp(INT_PTR nSectionIndex, CString &strKey, CString *strVal)
{
	CSection	section;
	INT_PTR		nIndex;

	section	= m_SectionArray.GetAt(nSectionIndex);

	nIndex	= section.LookUp(strKey, strVal);

	return nIndex;
}

/********************************************************************************
*	関数名：SetAt																*
*	用　途：プロファイルの設定													*
*	引き数：セクション配列の要素番号、キーワード、値格納場所					*
*	戻り値：設定成功でＴＲＵＥ、失敗でＦＡＬＳＥ								*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::SetAt(INT_PTR nSectionIndex, CString &strKey, CString &strVal)
{
	CSection	section;
	INT_PTR		nIndex;

	section	= m_SectionArray.GetAt(nSectionIndex);

	nIndex	= section.LookUp(strKey);

	if (nIndex >= 0)
	{
		section.SetAt(nIndex, strKey, strVal);
	}
	else
	{
		section.Add(strKey, strVal);
	}

	m_SectionArray.SetAt(nSectionIndex, section);

	return TRUE;
}

/********************************************************************************
*	関数名：Parse																*
*	用　途：読み込んだ行の展開													*
*	引き数：キーワード格納場所、値格納場所、読み込んだ行、値省略許可フラグ		*
*	戻り値：読み込んだトークン数、マイナスはエラー								*
*	備　考：あらかじめ改行コードは外しておくこと								*
********************************************************************************/
int CIniFile::Parse(CString *keyword, CString *value, CString &src, BOOL bOmitValue)
{
	CString	line;
	int		n;

	line		= src;
	*keyword	= _T("");
	*value		= _T("");

	n			= line.Find(_T('='));
	
	/***** セクション指定 *****/
	if ( n <= 0 )	/* 先頭に=がある場合も暗にエラー */
	{	
		line.Trim();
		/***** セクションは[]で囲まれている必要がある *****/
		if ( (line.Left(1) == _T('[')) && (line.Right(1) == _T(']')) )
		{
			*keyword = line.Mid(1, line.GetLength() - 2);
			return 1;
		}
		/***** エラー *****/
		return -1;
	}

	line.TrimLeft();

	*keyword	= line.Mid(0, n);
	keyword->TrimRight();

	/***** 値指定がない？ *****/
	if ( n + 1 >= line.GetLength() )
	{
		/***** 値の省略は許されていない？ *****/
		if ( !bOmitValue )
		{
			return -2;
		}
	}
	else
	{
		*value	= line.Mid(n + 1);
//		value->TrimRight();
	}

	return 2;
}

/********************************************************************************
*	関数名：ReadFileString														*
*	用　途：ファイルから文字列を読み込む										*
*	引き数：ファイルポインタ、読み込み文字列格納先								*
*	戻り値：成功でＴＲＵＥ、失敗でＦＡＬＳＥ									*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::ReadFileString(FILE *tpFile, CString *pString)
{
	const int	nReadMaxSize = 512;

	LPTSTR		szp;
	LPTSTR		szpRead;
	int			nLength;
	int			nTotalLength = 0;

	*pString	= _T("");
	szp			= pString->GetBuffer( nReadMaxSize );
	szpRead		= _fgetts(szp, nReadMaxSize + 1 /* +1 => '\0'分 */, tpFile);

	while (szpRead != 0)
	{
		nLength = (int)_tcslen( szp );

		/***** EOF-ファイル終端か、改行文字を入力した段階で終了 *****/
		if (szp[nLength - 1] == _T('\n'))
		{
			nTotalLength	+= nLength ;
			/***** 最後の改行文字は削除する *****/
			szp[nLength - 1] = _T('\0');
			break;
		}

		if (nLength < nReadMaxSize)
		{
			nTotalLength	+= nLength ;
			break;
		}

		pString->ReleaseBuffer();

		/***** バッファ拡張 *****/
		nTotalLength	= pString->GetLength();
		szp				= pString->GetBuffer( nReadMaxSize + nTotalLength ); 
		szp			   += nTotalLength;

		szpRead			= _fgetts(szp, nReadMaxSize + 1 /* +1 => '\0'分 */, tpFile);
	}

	pString->ReleaseBuffer();

	return nTotalLength > 0;
}

/********************************************************************************
*	関数名：AccessProfile														*
*	用　途：ＩＮＩファイルにアクセスする										*
*	引き数：																	*
*	戻り値：																	*
*	備　考：																	*
********************************************************************************/
BOOL CIniFile::AccessProfile(
							FLAG_ACCESS eAccessFlag ,
							TCHAR  *szSection,
							TCHAR  *szKey,
							void   *vpVal,
							INT32	nAttr,
							void   *vpDefault
						 )
{
	BOOL	bRet = FALSE;

	switch(eAccessFlag){

		case FLAG_ACCESS_GET:
			bRet = GetProfile(szSection , szKey , vpVal , nAttr,	vpDefault);
			break;
		case FLAG_ACCESS_SET:
			bRet = SetProfile(szSection , szKey , vpVal , nAttr);
			break;
		default:
			break;
	}

	return bRet;
}


