#include "stdafx.h"
#include "Ini.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#define new DEBUG_NEW
#endif

CIni::CIni() :
	SetOfSections(NULL),
	m_strFileName(_T("")),
	m_iniStatus(INI_NOTREADED),
	m_iniError(INI_OK)
{
	m_LineNum = 0;
	m_Encoding = 0;
}

CIni::~CIni()
{
	Clear();
}

void CIni::Clear()
{
	if (SetOfSections)
	{
		if ((m_iniError != INI_WRITEERROR) && (m_iniError != INI_READERROR))
		{
			FlushIni();
		}

		INI_SECTION_STRUCT *pISS = SetOfSections;
		INI_SECTION_STRUCT *pISS_prev = NULL;
		SetOfSections = NULL;
		m_iniStatus = INI_NOTREADED;

		while (pISS)
		{
			INI_KEY_STRUCT *pIKS = pISS->Keys;
			INI_KEY_STRUCT *pIKS_prev = NULL;
			pISS->Keys = NULL;

			while (pIKS)
			{
				pIKS_prev = pIKS;
				pIKS = pIKS->next;
				delete(pIKS_prev);
			}

			pISS_prev = pISS;
			pISS = pISS->next;
			delete(pISS_prev);
		}
	}
}
//получение хеша строки для быстрого поиска, чтобы сравнивать числа, а не строки
DWORD CIni::GetHash(const CString &str)
{
	DWORD h = 0;

	if (!str.IsEmpty())
	{
		int nLen = str.GetLength();

		if (nLen == 1)
		{
			h += str.GetAt(0);
		}
		else
		{
			for (int i = 1; i < nLen; ++i)
			{
				h += (str.GetAt(i - 1) + (i << 8)) * (str.GetAt(i) + ((i + 1) << 8));

				if (i & 1)
				{
					h += str.GetAt(i) << 8;
				}
			}
		}
	}

	return h;
}
/*
выход: TRUE - добавили новый элемент
FALSE - не добавили новый элемент, по разным причинам
*/
BOOL CIni::Add(CString strSection, CString strKey, CString strVal, CString strComm, BOOL bChangeComment)
{
	if (strSection.IsEmpty() || strKey.IsEmpty())
	{
		return FALSE;
	}

	DWORD hashSection = GetHash(strSection);
	// начинаем поиск
	INI_SECTION_STRUCT *pISS = SetOfSections;
	INI_SECTION_STRUCT *pISS_prev = NULL;

	while (pISS)
	{
		if (pISS->Hash == hashSection)
		{
			//нашли секцию
			DWORD hashKey = GetHash(strKey);
			INI_KEY_STRUCT *pIKS = pISS->Keys;
			INI_KEY_STRUCT *pIKS_prev = NULL;

			while (pIKS)
			{
				if (pIKS->Hash == hashKey)
				{
					//нашли ключ
					if (strVal.Compare(CString(pIKS->Value)) != 0)
					{
						//и старое значение не совпадает с новым
						pIKS->Value = strVal;
						return TRUE;
					}
					else
					{
						return FALSE;    //нечего добавлять
					}
				}

				pIKS_prev = pIKS;
				pIKS = pIKS->next;
			}

			//в секции ключа не нашли, надо добавить
			pIKS = new INI_KEY_STRUCT;

			if (pIKS)
			{
				pIKS->Hash = hashKey;
				pIKS->Key = strKey;
				pIKS->Value = strVal;

				if (bChangeComment)
				{
					pIKS->Comment = strComm;
				}

				pIKS->next = NULL;

				if (pIKS_prev) //если есть к чему добавлять
				{
					pIKS_prev->next = pIKS;
				}
				else //такое может быть, если в секции вообще не было ключей
				{
					pISS->Keys = pIKS;    //это событие маловероятное, но вполне возможное,
				}

				//хотя алгоритмом вообще не предусмотрено создание пустых секций
				return TRUE;
			}
			else
			{
				return FALSE;
			}
		}

		pISS_prev = pISS;
		pISS = pISS->next;
	}

	//даже секции такой не нашли, надо создать
	//и новый ключ тоже создадим
	INI_KEY_STRUCT *pIKS = new INI_KEY_STRUCT;

	if (pIKS)
	{
		pIKS->Hash = GetHash(strKey);
		pIKS->Key = strKey;
		pIKS->Value = strVal;

		if (bChangeComment)
		{
			pIKS->Comment = strComm;
		}

		pIKS->next = NULL;
	}
	else
	{
		return FALSE;
	}
	//теперь секцию
	pISS = new INI_SECTION_STRUCT;

	if (pISS)
	{
		pISS->Hash = hashSection;
		pISS->Name = strSection;
		pISS->next = NULL;
		pISS->Keys = pIKS;

		if (pISS_prev) //если есть к чему добавлять
		{
			pISS_prev->next = pISS;
		}
		else //если мы в первый раз создаём первую секцию
		{
			SetOfSections = pISS;
		}

		return TRUE;
	}

	return FALSE;
}

BOOL CIni::ReadIni()
{
	m_iniStatus = INI_NOTREADED;
	m_iniError = INI_OK;

	if (m_strFileName.IsEmpty())
	{
		return FALSE;    //если имя файла не задано, то и читать нечего
	}

	CStdioFile file;

	if (!file.Open(m_strFileName, CFile::modeRead | CFile::shareDenyWrite | CFile::typeText))
	{
		m_iniError = INI_READERROR;
		return FALSE;
	}

	CString strRead;
	CString strCurrentSectionName = _T("");
	m_LineNum = 0;

	while (file.ReadString(strRead))
	{
		m_LineNum++;

		if (strRead.IsEmpty())
		{
			continue;    //если пустая строка - игнорируем
		}

		strRead.Trim(); //уберём пустоту в начале и в конце строки
		TCHAR chFch = strRead.GetAt(0);

		//пояснительные и философские комменты проигнорируем
		if (chFch == _T(';') || chFch == _T('#'))
		{
			continue;    //если комментарий - игнорируем
		}

		if (chFch == _T('/') && strRead.GetAt(1) == _T('/'))
		{
			continue;    //это тоже комментарий, тоже игнорируем
		}

		if (chFch == _T('[')) //мы уже убрали возможную пустоту перед [, поэтому этот символ обязательно должен быть первым, иначе это не имя секции
		{
			//начало секции
			int pos = strRead.Find(_T(']')); //ищем конец секции

			if (pos > 0) //если этот символ найден
			{
				CString strName = strRead.Mid(1, pos - 1);
				strName.Trim(); //вот. мы даже допускаем многословное имя секции,
				//strName.Replace(_T(' '), _T('_')); // хотя можем и заменить пробелы на _
				strCurrentSectionName = strName; //имя текущей секции
				//TRACE(_T("%08X : %s\n"), GetHash(strCurrentSectionName), strCurrentSectionName);
				continue;
			}
			else
			{
				m_iniError = INI_PARSEERROR;
				return FALSE;
			}
		}

		//сюда попадаем, если у нас простая строка имя=значение ;коммент
		int pos = strRead.Find(_T('='));

		//если строка не начинается с =, и = вообще есть
		if (pos > 0)
		{
			//разберёмся
			CString strKey = strRead.Left(pos++);
			strKey.Trim();
			CString strTmp = strRead.Mid(pos);
			CString strComment = _T("");
			CString strValue = _T("");
			//проверим, вдруг есть комментарий
			int commentpos = -1;
			int cpos;

			if ((cpos = strTmp.Find(_T(';'))) >= 0)
			{
				commentpos = cpos;
			}
			else if ((cpos = strTmp.Find(_T('#'))) >= 0)
			{
				commentpos = cpos;
			}
			else if (((cpos = strTmp.Find(_T('/'))) >= 0) && (strTmp.GetAt(cpos + 1) == _T('/')))
			{
				commentpos = cpos + 1;
			}

			if (commentpos >= 0)
			{
				//если комментарий таки есть, то
				strValue = strTmp.Left(commentpos++); //вот значение
				strComment = strTmp.Mid(commentpos); //вот комментарий
			}
			else
			{
				//комментария нету, только значение
				strValue = strRead.Mid(pos);
			}

			strValue.Trim();
			strValue.Trim(_T('"')); //удалим ещё и кавычки (если есть)
			Add(strCurrentSectionName, strKey, strValue, strComment, TRUE);
			//TRACE(_T("%08X : %s\n"), GetHash(strKey), strKey);
		}

		//иначе проигнорируем такое безобразие
	}

	file.Close();
	m_iniStatus = INI_READED;
	return TRUE;
}

BOOL CIni::FlushIni()
{
	if (m_iniStatus == INI_NOTREADED)
	{
		return FALSE;    //если ини не прочитан, то и записывать нечего
	}

	if (m_iniStatus == INI_FLUSHED)
	{
		return TRUE;    //если ини уже записан, то второй раз - не надо
	}

	if (!SetOfSections)
	{
		return FALSE;    //если инифайл пустой, то и делать нечего
	}

	if (m_strFileName.IsEmpty())
	{
		return FALSE;    //если имя файла не задано, то и записывать нечего
	}

	CStdioFile file; //CMemFile - для создания файла в памяти

	if (!file.Open(m_strFileName, CFile::modeCreate | CFile::modeWrite | CFile::shareDenyWrite | CFile::typeText))
	{
		m_iniError = INI_WRITEERROR;
		return FALSE;
	}

	m_LineNum = 0;
	INI_SECTION_STRUCT *pISS = SetOfSections;
	CString strOut;

	while (pISS)
	{
		//printf strout
		strOut.Format(_T("[%s]\n"), pISS->Name);
		file.WriteString(strOut);
		m_LineNum++;
		INI_KEY_STRUCT *pIKS = pISS->Keys;

		while (pIKS)
		{
			strOut.Format(_T("%s = %s"), pIKS->Key, pIKS->Value);

			//если есть комментарий
			if (!pIKS->Comment.IsEmpty())
			{
				strOut += _T("\t") + pIKS->Comment;
			}

			//printf strout
			strOut += _T("\n");
			file.WriteString(strOut);
			m_LineNum++;
			pIKS = pIKS->next;
		}

		file.WriteString(_T("\n")); //и для красоты добавим пустую строку
		m_LineNum++;
		pISS = pISS->next;
	}

	file.Close();
	m_iniStatus = INI_FLUSHED;
	m_iniError = INI_OK;
	return TRUE;
}

ULONGLONG CIni::FlushIniToMemory(BYTE *pBuff, UINT nSize)
{
	if (m_iniStatus == INI_NOTREADED)
	{
		return 0;    //если ини не прочитан, то и записывать нечего
	}

	if (!SetOfSections)
	{
		return 0;    //если инифайл пустой, то и делать нечего
	}

	CMemFile file(pBuff, nSize, 1024); //создаём файл в памяти
	m_LineNum = 0;
	INI_SECTION_STRUCT *pISS = SetOfSections;
	CString strOut;

	while (pISS)
	{
		//printf strout
		strOut.Format(_T("[%s]\n"), pISS->Name);
		file.Write(strOut, strOut.GetLength() * sizeof(TCHAR));
		m_LineNum++;
		INI_KEY_STRUCT *pIKS = pISS->Keys;

		while (pIKS)
		{
			strOut.Format(_T("%s = %s"), pIKS->Key, pIKS->Value);

			//если есть комментарий
			if (!pIKS->Comment.IsEmpty())
			{
				strOut += _T("\t") + pIKS->Comment;
			}

			//printf strout
			strOut += _T("\n");
			file.Write(strOut, strOut.GetLength() * sizeof(TCHAR));
			m_LineNum++;
			pIKS = pIKS->next;
		}

		pISS = pISS->next;
	}

	ULONGLONG len = file.GetLength();
	file.Close();
	m_iniStatus = INI_FLUSHED;
	m_iniError = INI_OK;
	return len;
}

BOOL CIni::ReadIniFromMemory(BYTE *pBuff, UINT nSize)
{
	m_iniStatus = INI_NOTREADED;
	m_iniError = INI_OK;
	Clear();
	CMemFile file(pBuff, nSize, 0); //создаём файл в памяти
	CString strRead;
	CString strCurrentSectionName = _T("");
	m_LineNum = 0;
	BOOL bEof = FALSE;
	TCHAR tch;

	while (!bEof)
	{
		strRead = _T("");

		while (1)
		{
			file.Read(&tch, sizeof(TCHAR));

			if (file.GetPosition() >= nSize)
			{
				bEof = TRUE;
				break;
			}

			if (tch == L'\n')
			{
				break;
			}
			else
			{
				strRead += tch;
			}
		}

		m_LineNum++;

		if (strRead.IsEmpty())
		{
			continue;    //если пустая строка - игнорируем
		}

		strRead.Trim(); //уберём пустоту в начале и в конце строки
		TCHAR chFch = strRead.GetAt(0);

		if (chFch == _T('[')) //мы уже убрали возможную пустоту перед [, поэтому этот символ обязательно должен быть первым, иначе это не имя секции
		{
			//начало секции
			int pos = strRead.Find(_T(']')); //ищем конец секции

			if (pos > 0) //если этот символ найден
			{
				CString strName = strRead.Mid(1, pos - 1);
				strName.Trim(); //вот. мы даже допускаем многословное имя секции,
				//strName.Replace(_T(' '), _T('_')); // хотя можем и заменить пробелы на _
				strCurrentSectionName = strName; //имя текущей секции
				//TRACE(_T("%08X : %s\n"), GetHash(strCurrentSectionName), strCurrentSectionName);
				continue;
			}
			else
			{
				m_iniError = INI_PARSEERROR;
				return FALSE;
			}
		}

		//сюда попадаем, если у нас простая строка имя=значение ;коммент
		int pos = strRead.Find(_T('='));

		//если строка не начинается с =, и = вообще есть
		if (pos > 0)
		{
			//разберёмся
			CString strKey = strRead.Left(pos++);
			strKey.Trim();
			CString strTmp = strRead.Mid(pos);
			CString strComment = _T("");
			CString strValue = _T("");
			//проверим, вдруг есть комментарий
			int commentpos = -1;
			int cpos;

			if ((cpos = strTmp.Find(_T(';'))) >= 0)
			{
				commentpos = cpos;
			}
			else if ((cpos = strTmp.Find(_T('#'))) >= 0)
			{
				commentpos = cpos;
			}
			else if (((cpos = strTmp.Find(_T('/'))) >= 0) && (strTmp.GetAt(cpos + 1) == _T('/')))
			{
				commentpos = cpos + 1;
			}

			if (commentpos >= 0)
			{
				//если комментарий таки есть, то
				strValue = strTmp.Left(commentpos++); //вот значение
				strComment = strTmp.Mid(commentpos); //вот комментарий
			}
			else
			{
				//комментария нету, только значение
				strValue = strRead.Mid(pos);
			}

			strValue.Trim();
			strValue.Trim(_T('"')); //удалим ещё и кавычки (если есть)
			Add(strCurrentSectionName, strKey, strValue, strComment, TRUE);
			//TRACE(_T("%08X : %s\n"), GetHash(strKey), strKey);
		}

		//иначе проигнорируем такое безобразие
	}

	file.Close();
	m_iniStatus = INI_READED;
	return TRUE;
}

CString CIni::GetValueString(int nSection, int nKey, const CString &strDefault)
{
	CString strSection, strKey;
	strSection.LoadString(nSection);
	strKey.LoadString(nKey);

	if (m_iniStatus == INI_NOTREADED)
	{
		ReadIni();
	}

	DWORD hashSection = GetHash(strSection);
	INI_SECTION_STRUCT *pISS = SetOfSections;

	while (pISS)
	{
		if (pISS->Hash == hashSection)
		{
			//нашли секцию
			DWORD hashKey = GetHash(strKey);
			INI_KEY_STRUCT *pIKS = pISS->Keys;

			while (pIKS)
			{
				if (pIKS->Hash == hashKey)
				{
					//нашли ключ
					return pIKS->Value;
				}

				pIKS = pIKS->next;
			}
		}

		pISS = pISS->next;
	}

	//не нашли. тогда надо его добавить
	SetValueString(nSection, nKey, strDefault);
	return CString(strDefault);
}

int CIni::GetValueInt(int nSection, int nKey, const int nDefault)
{
	int nReturn = nDefault;
	CString strReturn = GetValueString(nSection, nKey, CString(_T("")));

	if (!strReturn.IsEmpty())
	{
		nReturn = _ttoi(strReturn);
	}

	return nReturn;
}

int CIni::GetValueOct(int nSection, int nKey, const int nDefault)
{
	int nReturn = nDefault;
	CString strReturn = GetValueString(nSection, nKey, CString(_T("")));

	if (!strReturn.IsEmpty())
	{
		_stscanf_s(strReturn, _T("%o"), &nReturn);
	}

	return nReturn;
}

double CIni::GetValueFloat(int nSection, int nKey, const double fDefault)
{
	double fReturn = fDefault;
	CString strReturn = GetValueString(nSection, nKey, CString(_T("")));

	if (!strReturn.IsEmpty())
	{
		fReturn = _ttof(strReturn);
	}

	return fReturn;
}

BOOL CIni::GetValueBool(int nSection, int nKey, const BOOL bDefault)
{
	BOOL bReturn = bDefault;
	CString strReturn = GetValueString(nSection, nKey, CString(_T("")));

	if (!strReturn.IsEmpty())
	{
		if ((!strReturn.CompareNoCase(_T("yes"))) || (!strReturn.Compare(_T("1"))))
		{
			bReturn = TRUE;
		}
		else
		{
			bReturn = FALSE;
		}
	}

	return bReturn;
}

BOOL CIni::SetValueString(int nSection, int nKey, const CString &strVal)
{
	CString strSection, strKey;
	strSection.LoadString(nSection);
	strKey.LoadString(nKey);

	if (Add(strSection, strKey, strVal, _T(""), FALSE))
	{
		m_iniStatus = INI_READED;
		return TRUE;
	}

	return FALSE;
}

BOOL CIni::SetValueInt(int nSection, int nKey, const int iVal)
{
	CString strEntry;
	strEntry.Format(_T("%d"), iVal);
	return SetValueString(nSection, nKey, strEntry);
}

BOOL CIni::SetValueOct(int nSection, int nKey, const int oVal)
{
	CString strEntry;
	strEntry.Format(_T("%o"), oVal);
	return SetValueString(nSection, nKey, strEntry);
}

BOOL CIni::SetValueFloat(int nSection, int nKey, const double fVal)
{
	CString str;
	str.Format(_T("%e"), fVal);
	return SetValueString(nSection, nKey, str);
}

BOOL CIni::SetValueBool(int nSection, int nKey, const BOOL bVal)
{
	CString str = (bVal) ? _T("Yes") : _T("No");
	return SetValueString(nSection, nKey, str);
}

LPSTR CIni::ConvertUnicodetoStr(UINT type, const CString &ustr)
{
	int sizeOfString = (ustr.GetLength() + 1);
	LPSTR lpszAscii = new CHAR[sizeOfString]; //а потом, необходимо самим делать delete [] использованных строк

	if (lpszAscii)
	{
		int t_len = WideCharToMultiByte(type, 0, ustr, -1, lpszAscii, sizeOfString, NULL, NULL);
	}

	return lpszAscii;
}

CString CIni::ConvertStrtoUnicode(UINT type, LPCSTR astr)
{
	CString sret = _T("");
	int sizeOfString = (int)strlen(astr) + 1;
	LPWSTR ustr = new WCHAR[sizeOfString];

	if (ustr)
	{
		int t_len = MultiByteToWideChar(type, 0, astr, -1, ustr, sizeOfString);
		sret = CString(ustr);
		delete [] ustr;
	}

	return sret;
}

