// aplINI.cpp: implementation of the CaplINI class.
//
/////////////////////////////////////////////////////////
#include "stdafx.h"

#ifndef _MFC_VER
#include <QDir>
#endif

#include "aplAggr.h"

#ifdef _DEBUG
#define LOAD_REFRESH_INTERVAL 1000
#else
#define LOAD_REFRESH_INTERVAL 1000
#endif

#define APL_INI_SECT_LEFT 0x01
#define APL_INI_SECT_RIGHT 0x02

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CaplINI::SSection::SSection(CaplTAggr<SSection*,SSection*,APLAGGR_AUTOKILLREF> &sections, LPCTSTR  name):
	m_after(_T("")),
	m_prev(_T("")),
	m_last_option(0),
	m_flags(0)
{
	m_name=name;
	sections.Add(this);
}

// добавляет опцию в конец списка опций секции
int CaplINI::SSection::Add(SOption* option)
{
	int pos;
	if(0==option) return -1;
	pos=options.Add(option);
	if(option->m_name != _T(""))
	{
		m_last_option=pos+1;
	}
	return pos;
}

// вставляет опцию перед завершающими пустыми опциями
int CaplINI::SSection::Insert(SOption* option, int pos_insert)
{
	int pos = m_last_option;
	if(pos_insert!=-1)
		pos = pos_insert;

	if(0==option) return -1;
	options.Insert(pos,option);
	if(option->m_name != _T(""))
	{
		m_last_option++;
	}
	return pos;
}

CaplINI::SOption::SOption(LPCTSTR  name)
{
	m_name=name;
	m_value.Empty();
	m_need_equal=false;
}


CaplINI::CaplINI(LPCTSTR  ini_name, bool find_current)
{
	m_LastParseTimer.Zero();
	m_ErrDescription = _T("");
	m_write_to_file = true;
	m_IniName = _T("");
	m_file_exist = false;
	if(ini_name!=0)SetINI(ini_name, find_current);
	m_readonly = false;
	m_FileCoding = aplUnknownEncoding;
	m_isPrintExpandMessages = true;
	m_is_home = false;
#ifdef __linux__
	m_etc_folder = _T("");
#endif
	m_env_for_find = _T("");

}

CaplINI::~CaplINI()
{
	//delete m_LastParseTimer;
	//m_LastParseTimer = 0;

}

CString CaplINI::GetErrDescription()
{
	CaplEnterCriticalSection CriticalSection(&m_DataProtect);
	return m_ErrDescription;
}


/** Устанавливает имя ini-файла с параметрами.
	@param ini_name - имя ini
	@param find_current - искать ли в текущем каталоге
	Если имя ini-файла указано без пути и find_current==true, то функция ищет указанный ini по очереди: сначала в каталоге с приложением,
потом для windows в папке c:\windows; для линукс - в домашней папке пользователя.
	Если имя ini-файла указано без пути и find_current==false, то сразу ищет для windows в папке c:\windows; для 
линукс - в домашней папке пользователя.
	Если имя ini-файла указано с абсолютным путем, то ищет только по этому пути
	@return true если файл ini найден и false если не найден */
bool CaplINI::SetINI(LPCTSTR ini_name, bool find_current, int levels)
{
	m_IniName=ini_name;
	CaplEnterCriticalSection CriticalSection(&m_DataProtect);
	m_file_exist = false;
	bool need_found = true;
	SetNeedReload();

	if(m_IniName.Find(aplDirRazd)==-1)
	{
		// задано только имя инишки. Надо поискать
		CString tmp_path;

		if(need_found && find_current)
		{
			aplGetModuleFilePath(tmp_path);
			if(CaplFile::IsFileExist(tmp_path + ini_name))
			{
				m_IniName = tmp_path + ini_name;
				need_found = false;
			}
			if(need_found)
			{
				LPCSTR env = 0;
				if(!m_env_for_find.IsEmpty())
				{
					CaplStringAdapter ad(m_env_for_find);
					env = getenv((LPCSTR)ad);
				}
				if(env != NULL)
				{
					tmp_path = env;
					tmp_path.Trim();
					if(tmp_path.Right(4).MakeLower() != _T(".ini"))
					{
						if(tmp_path.Right(1) != aplDirRazd)
							tmp_path += aplDirRazd;

						tmp_path += ini_name;
					}
					if(CaplFile::IsFileExist(tmp_path))
					{
						m_IniName = tmp_path;
						need_found = false;
					}
				}
				while(need_found && levels > 0)
				{
					// поднимаемся уровнем выше
					if(tmp_path.Right(1)==aplDirRazd)tmp_path.TrimRight(aplDirRazd);
					int pos = tmp_path.ReverseFind(aplDirRazd);
					if(pos > 0)
					{
						tmp_path.Delete(pos+1, tmp_path.GetLength());
						if(CaplFile::IsFileExist(tmp_path + ini_name))
						{
							m_IniName = tmp_path + ini_name;
							need_found = false;
							break;
						}
					}
					levels--;
				}
			}
		}
		if(need_found)
		{
			// пытаемся найти в рабочем каталоге
#ifdef _MFC_VER
			TCHAR dir[2048];
			GetCurrentDirectory( 2048, dir);
			CString c_curr_dir = dir;
#else
			CString c_curr_dir = QS2CS(QDir::currentPath());
#ifndef __linux__
			// QDir::currentPath() под виндой выдает путь с неправильными линуксовми слешами
			c_curr_dir.Replace(aplMissedDirRazd, aplDirRazd);
#endif
#endif
			if(c_curr_dir.Right(1) != aplDirRazd)
				c_curr_dir+= aplDirRazd;

			if(CaplFile::IsFileExist(c_curr_dir + ini_name))
			{

				m_IniName = c_curr_dir + ini_name;
				need_found = false;
			}
		}
		if(need_found)
		{

#ifdef __linux__
			//?? Для линуха сначала ищем в домашнем каталоге, потом в /etc/ayatzk или что там прочитается из *.ver
			tmp_path.GetEnvironmentVariable(_T("HOME")); // да-да, именно без $ !!!

			CString test_name_etc = _T("/etc/");
			if(!m_etc_folder.IsEmpty()){test_name_etc += m_etc_folder;if(test_name_etc.Right(1)!=aplDirRazd){test_name_etc+=aplDirRazd;}}
			test_name_etc+= ini_name;
			CString test_name_home = tmp_path + aplDirRazd; test_name_home+= ini_name;

			if(CaplFile::IsFileExist(test_name_home))
			{
				m_IniName = test_name_home;
				need_found = false;
				m_is_home = true;
			}
			if(need_found)
			{
				if(CaplFile::IsFileExist(test_name_etc))
				{
					m_IniName = test_name_etc;
				}
				else
				{
					// если инишка не найдена ни в текущей папке, ни в HOME, ни в etc - поместим ее в HOME,
					// там ее хотя бы сможет записать любой процесс
					m_IniName = test_name_home;
					m_is_home = true;
				}
			}

#else
#ifndef _MFC_VER
			CString userprofile_path=_T("");
#endif
			// Ищем в каталоге пользователя
			tmp_path.GetEnvironmentVariable(_T("USERPROFILE"));
			if(tmp_path!=_T(""))
			{
				if(tmp_path.Right(1)!=aplDirRazd) tmp_path+=aplDirRazd;
				tmp_path+= ini_name;
				if(CaplFile::IsFileExist(tmp_path))
				{
					m_IniName = tmp_path;
					need_found = false;
					m_is_home = true;
				}
#ifndef _MFC_VER
				userprofile_path = tmp_path;
#endif
			}
			if(need_found)
			{
				// Берем ее из каталога винды
				tmp_path.GetEnvironmentVariable(_T("SystemRoot"));
				if(tmp_path!=_T(""))
				{
					if(tmp_path[tmp_path.GetLength()-1]!=aplDirRazd) tmp_path+=aplDirRazd;
				}
				tmp_path+= ini_name;
#ifdef _MFC_VER
				// Для студии: если ini нет, пытаемся поместить его в каталоге винды
				m_IniName = tmp_path;
#else
				// Для Qt используем ini из каталога винды только если она там есть. А если
				// в c:\windows ее нет, используем из каталога текущего пользователя
				if(CaplFile::IsFileExist(tmp_path) || userprofile_path == _T(""))
				{
					m_IniName = tmp_path;
				}
				else
				{
					m_IniName = userprofile_path;
					m_is_home = true;
				}
#endif
				need_found = !CaplFile::IsFileExist(tmp_path);
			}
#endif
		}
		m_file_exist = !need_found;
	}
	else
	{
		m_file_exist = CaplFile::IsFileExist(m_IniName);
	}

	m_IniPath = m_IniName.Left(m_IniName.ReverseFind(aplDirRazd)+1);

#ifdef _DEBUG
	// PrintInfoWithSaveFlags(m_IniName); - при отладке этот вывод мешает
#endif
	return true;
}


void CaplINI::GetRealIniName(CString &name)
{
	CaplEnterCriticalSection CriticalSection(&m_DataProtect);
	name=m_IniName;
}

CString CaplINI::GetRealIniName()
{
	CString name;
	CaplEnterCriticalSection CriticalSection(&m_DataProtect);
	name=m_IniName;
	return name;
}

CString CaplINI::GetRealIniPath()
{
	CString path;
	CaplEnterCriticalSection CriticalSection(&m_DataProtect);
	path=m_IniPath;
	return path;
}

bool CaplINI::CheckWrite()
{
	if(m_IniName==_T("")) return false;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);
	//
	m_readonly = false;

	CaplFile file;
	if(!file.Open(m_IniName, CFile::modeWrite |CFile::modeCreate|CFile::modeNoTruncate | CFile::shareDenyNone))
	{
		m_readonly = true;
	}
	else
	{
		file.Close();
	}

	return !m_readonly;
}

void CaplINI::SetWriteMode(bool write_to_file)
{
	if(m_write_to_file && !write_to_file)Parse();

	if(!m_write_to_file && write_to_file) InternalSave();

	m_write_to_file = write_to_file;
}

void CaplINI::SetNeedReload()
{
	CaplEnterCriticalSection CriticalSection(&m_DataProtect);
	m_LastParseTimer.Zero();
} // После вызова ini всегда перечитывается


bool CaplINI::Parse()
{
	int i;
	CString tmp;
	CString buf;

	bool line_sec=false; // в текущей строке найдены признаки заголовка секции
	bool line_val=false; // в текущей строке найдены признаки значения
	bool line_comment=false; // текущая строка является комментарием

	SSection* sec=0;
	SOption* opt=0;

	// Перечитываем только при работе с файлом. При работе с памятью все уже прочитано
	if(m_write_to_file && m_LastParseTimer.CheckTimeOut(LOAD_REFRESH_INTERVAL))
	{
		CaplEnterCriticalSection CriticalSection(&m_DataProtect);
		CaplStringFile IniFile;

		// Если ini-шки нет - парсить ее бессмысленно
		if(!CaplFile::IsFileExist(m_IniName))
		{
			return true;
		}
		m_file_exist = true;

		CheckWrite();

		if(IniFile.Open(m_IniName, CFile::modeRead | CFile::shareDenyNone )==FALSE)
		{
			m_LastParseTimer.Zero();
			m_ErrDescription = IniFile.GetErrorMessage();
			return false;
		}

		m_ErrDescription = _T("");
		m_LastParseTimer.Start();
		m_sections.Clear();

		IniFile.ReadAllDataToString(buf);
		if(buf.IsEmpty())
			return true;

		// Для корректной обработки последней строки добавим в конец перевод строки
		if(buf[buf.GetLength()-1] != _T('\n'))
			buf+=_T('\n');

		// кодировка известна только после чтения из файла
		m_FileCoding = IniFile.GetFileEncoding();

		tmp.Empty();
		for(i=0;i<buf.GetLength();i++)
		{
			if(buf[i] == _T(';') || buf[i] == _T('#'))
			{
				if(!line_val && !line_sec)
					line_comment = true;
			}
			else if(buf[i] == _T('['))
			{
				if(!line_val && !line_comment) // в значении и в комментарии скобки пропускаем
				{
					if(sec!=0 && !line_sec) // это новая секция на новой строчке
						sec = 0;

					line_sec=true;
					if(sec==0)
					{
						sec=new SSection(m_sections);
						// левая скобка у секции есть
						sec->m_flags |= APL_INI_SECT_LEFT;
						continue;
					}
					// а если секция sec есть, это может быть что угодно. Обрабатываем как любой символ в тексте
				}
			}
			else if(buf[i] == _T(']'))
			{
				if(!line_val && !line_comment) // в значении и в комментарии скобки пропускаем
				{
					if(sec!=0 && !line_sec)
					{
						// это уже не заголовок предыдущей секции. Считаем, что это заголовок новой секции но без левой скобки
						sec=0;
					}
					line_sec=true;
					if(sec==0)
					{
						// секция сбойная, без левой скобки. Такие все равно обрабатываем
						sec=new SSection(m_sections, tmp);
					}
					// правая скобка есть
					sec->m_flags |= APL_INI_SECT_RIGHT;
					sec->m_name=tmp;

					tmp.Empty();
					continue;

				}
			}
			else if(buf[i] == _T('='))
			{
				// в строке секции символ '=' считаем частью имени секции или комментария
				// в строке значения повторный символ '=' считаем частью значения
				if(!line_sec && !line_comment && !line_val)
				{
					line_val=true;
					if(sec==0)
					{
						// значение до начала секций. Создаем ему пустую секцию
						sec=new SSection(m_sections);
					}
					opt=new SOption(tmp);
					sec->Add(opt);
					opt->m_need_equal = (_T("") == tmp);//если у значения нет имени, но есть '=', запоминаем чтобы сохранить обратно в таком же виде
					tmp.Empty();
					continue;
				}
			}
			else if(buf[i] == _T('\r') || buf[i] == _T('\n'))
			{
				if(buf[i] == _T('\r'))
				{
					// если символ возврата каретки идет вместе с символом переноса строки, пропускаем
					if(i<buf.GetLength()-1 && buf[i+1]==_T('\n'))continue;
					if(i>0 && buf[i-1]==_T('\n'))continue;
					// если символ возврата каретки идет без символа переноса строки, обрабатываем его как перенос строки
				}

				line_val=false;
				line_comment=false;

				if(line_sec)
				{
					if(sec==0)
					{
						m_ErrDescription.Format(APL_T("Ошибка при разборе ini %s: строка помечена как содержащая секцию, но объекта секции нет"), LPCTSTR(m_IniName));
						return false;
					}
					if(sec->m_flags & APL_INI_SECT_RIGHT)
						sec->m_after=tmp; // если есть правая скобка, то это строчка уже после нее. Комментарий или еще чего
					else
						sec->m_name=tmp; // секция неполноценная, без правой скобки, но сохранить ее надо

					tmp.Empty();
					line_sec=false;
					continue;
				}
				if(opt==0)
				{
					// пустые строчки и строчки без '=' тоже сохраняем, чтобы сохранить ini с разделителями
					// сюда же относятся и комментарии
					if(sec==0)
					{
						// для строк перед первой секцией создаем секцию без имени
						sec=new SSection(m_sections);
					}
					opt=new SOption();
					sec->Add(opt);
				}
				opt->m_value=tmp;
				opt->m_value.Trim();
				opt=0;
				tmp.Empty();
				continue;

			}

			tmp+=buf[i];
		}
	}
	return true;
}


bool CaplINI::Flush()
{
	if(!m_write_to_file)
	{
		m_write_to_file = true;
		return InternalSave();
	}
	return true;
}


bool CaplINI::InternalSave()
{
	int i,j;
	SSection* sec=0;
	SOption* opt=0;
	CaplStringFile IniFile;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);

	// перепроверяем состояние файла на диске
	if(!CheckWrite())
	{
		m_ErrDescription = APL_T("Файл ini доступен только для чтения");
		return false;
	}

	CString ini_data = _T("");
	CString tmp_buf = _T("");
	int num_opt;
	// перед заголовком секции должна быть пустая строка (для лучшей читаемости)
	// если в массиве значений предыдущей секции такая есть - ставим флаг в true
	bool prev_line_empty = true;

	// коллекция для сортировки опций с числовыми именами
	CaplMap map_int_optname;
	map_int_optname.bAutoSort = false;

	// сохраняем секции и опции в текстовый буфер
	for(i=0;i<m_sections.GetSize();i++)
	{
		sec=m_sections[i];
		if(sec==0) continue;
		if(sec->m_name != _T(""))
		{
			if(sec->m_prev!= _T(""))
			{
				ini_data+= _T(";") + sec->m_prev + _T("\r\n");
				sec->m_prev.Empty();
			}
			if(!prev_line_empty) ini_data+= _T("\r\n");
			/*if(sec->m_flags & APL_INI_SECT_LEFT)*/ini_data+= _T("["); // все секции, включая сбойные (без одной скобки) сохраняются с обеими скобками
			ini_data+= sec->m_name;
			/*if(sec->m_flags & APL_INI_SECT_RIGHT)*/ini_data+= _T("]"); // все секции, включая сбойные (без одной скобки) сохраняются с обеими скобками
			ini_data+= sec->m_after + _T("\r\n");
		}
		tmp_buf = _T("");
		// если ни одной опции в секции нет, все равно надо разделять секции строками
		prev_line_empty = false;

		for(j=0;j<sec->options.GetSize();j++)
		{
			opt=sec->options[j];
			if(opt==0) continue;
			if(IsNumber(opt->m_name))
			{
				//опции с числовыми именами заносим в массив на сортировку
				num_opt = _atoi(opt->m_name);
				map_int_optname.Add(num_opt,opt);
			}
			else
			{
				// все остальные заносим в исходном порядке пока что во временный буфер
				if(opt->m_name != _T("") || opt->m_need_equal)
				{
					tmp_buf += opt->m_name + _T("=");
				}
				tmp_buf += opt->m_value + _T("\r\n");
			}
			prev_line_empty = opt->m_name == _T("");
		}
		//опции с числовыми именами сортируем по возрастанию
		map_int_optname.SortIn();

		for(j=0;j<map_int_optname.Size;j++)
		{
			opt = (SOption*)map_int_optname.Data[j].out;
			if(_T("") == opt->m_value) continue;

			if(opt->m_name != _T("") || opt->m_need_equal)ini_data+= opt->m_name + _T("=");

			ini_data+= opt->m_value + _T("\r\n");
		}
		// текстовые опции пишем после цифровых
		ini_data += tmp_buf;
		map_int_optname.Clear();
	}

	// создаем *.bak
	CaplFile::CreateBackupFile(m_IniName);

	// сохраняем в ini.
#ifdef __linux__
	if(m_FileCoding == aplUnknownEncoding)m_FileCoding = aplUTF8;
#endif
	if(IniFile.Open(m_IniName,CFile::modeWrite|CFile::modeCreate|CFile::typeBinary, m_FileCoding)==FALSE)
	{
		m_ErrDescription = IniFile.GetErrorMessage();
		return false;
	}

	IniFile.WriteString(ini_data);
	IniFile.Close();

	// копия ini в памяти актуальная, заново разбирать в ближайшее время не надо
	m_LastParseTimer.Start();
	return true;
}

CaplINI::SOption *CaplINI::FindCreateOption(LPCTSTR lpAppName, LPCTSTR lpKeyName, bool is_create, LPCTSTR comment)
{
	int i,j;
	SSection* sec=0;
	SOption* opt=0;

	for(i=0;i<m_sections.GetSize();i++)
	{
		if(m_sections[i]->m_name.CompareNoCase(lpAppName)==0)
		{
			if(sec==0)
			{
				// опцию будем искать в первой секции с заданным именем
				sec = m_sections[i];
				if(is_create)
				{
					// имя секции может совпадать по буквам, но с другим регистром символов
					// в режиме записи сохраняем правильное имя секции
					sec->m_name = lpAppName;
				}

			}
			else if(is_create)
			{
				// в режиме записи нового параметра в ini должна остаться только одна секция с заданным именем
				// остальные секции с аналогичным именем переименовываем
				m_sections[i]->m_name += _T("___copy");
			}
			// при чтении берем параметр из первой секции с заданным именем, остальные не трогаем
			if(!is_create)
				break;

		}

	}
	if(sec!=0)
	{
		for(j=0;j<sec->options.GetSize();j++)
		{
			if(sec->options[j]->m_name.CompareNoCase(lpKeyName)==0)
			{
				if(opt==0)
				{
					// берем первую опцию с заданным именем
					opt=sec->options[j];
					if(is_create)
					{
						// имя опции может совпадать по буквам, но с другим регистром символов
						// в режиме записи сохраняем правильное имя ключа
						opt->m_name = lpKeyName;
					}
				}
				else if(is_create)
				{
					// после записи нового параметра в ini должна быть только одна опция с заданным именем
					sec->options[j]->m_name += _T("___copy");

				}
				// при чтении берем первый параметр с заданным именем
				if(!is_create)
					break;
			}
		}
	}
	else if(is_create)
	{
		sec=new SSection(m_sections, lpAppName);
		if(sec==0)
		{
			m_ErrDescription = APL_T("Ошибка при выделении памяти под объект секции ini");
			return NULL;
		}
	}
	if(opt==0 && is_create)
	{
		opt=new SOption(lpKeyName);
		if(opt==0){m_ErrDescription = APL_T("Ошибка при выделении памяти под объект опции ini");return NULL;}
		int pos_opt = sec->Insert(opt);

		if(comment != 0 && _strlen(comment)>1)
		{
			SOption* opt_c=new SOption();
			if(opt_c==0){m_ErrDescription = APL_T("Ошибка при выделении памяти под объект опции ini");return NULL;}
			if(comment[0]!=_T(';'))
				opt_c->m_value = _T(";");
			opt_c->m_value += comment;
			sec->Insert(opt_c, pos_opt); // вставляем комментарий перед опцией
		}
	}

	return opt;
}

int CaplINI::GetInt(LPCTSTR lpAppName, LPCTSTR lpKeyName, int nDefault, bool isCreate)
{
	UINT retval=nDefault;
	SOption *opt;
	bool f=false;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);

	if(!Parse()) return retval;

	opt = FindCreateOption(lpAppName, lpKeyName, isCreate);

	if(opt!=0)
	{
		if(IsNumber(opt->m_value))
			retval = _atoi(opt->m_value);
		else f=true;
	}
	if(isCreate && opt!=0 && f)
	{
		opt->m_value.Format(_T("%i"), nDefault);
		InternalSave();
	}
	return retval;
}

// Установка значений опций. Если параметр в ini не задан и указан параметр comment, то
// перед строчкой с параметром добавляется строка комментария из comment
bool CaplINI::WriteInt(LPCTSTR lpAppName, LPCTSTR lpKeyName, int value, LPCTSTR comment)
{
	SOption* opt=0;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);

	if(!Parse()) return false;

	if(m_readonly)
	{
		m_ErrDescription = APL_T("Файл ini доступен только для чтения");
		return false;
	}

	opt = FindCreateOption(lpAppName, lpKeyName, true, comment);

	if(opt==0)
	{
		m_ErrDescription = APL_T("Ошибка при выделении памяти под объект опции ini");
		return false;
	}

	opt->m_value.Format(_T("%i"), value);

	bool res = true;
	if(m_write_to_file) res = InternalSave();

	return res;
}

bool CaplINI::GetListKeys(LPCTSTR lpAppName, CStringArray &keys)
{
	int i,j;
	SSection* sec=0;

	keys.RemoveAll();

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);

	if(!Parse()) return false;

	for(i=0;i<m_sections.GetSize();i++)
	{
		if(m_sections[i]->m_name.CompareNoCase(lpAppName)==0)
		{
			sec=m_sections[i];
			break;
		}
	}
	if(sec!=0)
	{
		for(j=0;j<sec->options.GetSize();j++)
		{
			if(sec->options[j]->m_name.IsEmpty())continue;
			keys.Add(sec->options[j]->m_name);
		}
		return true;
	}
	m_ErrDescription = APL_T("Секция ini не найдена");
	return false;
}

bool CaplINI::GetListKeysVals(LPCTSTR lpAppName, CaplTAggr<SKeyVal*, SKeyVal*, APLAGGR_AUTOKILLREF> &values)
{
	int i,j;
	SSection* sec=0;
	SKeyVal* kv;
	values.Clear();

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);

	if(!Parse()) return false;

	for(i=0;i<m_sections.GetSize();i++)
	{
		if(m_sections[i]->m_name.CompareNoCase(lpAppName)==0)
		{
			sec=m_sections[i];
			break;
		}
	}
	if(sec!=0)
	{
		for(j=0;j<sec->options.GetSize();j++)
		{
			if(sec->options[j]->m_name.IsEmpty())continue;
			kv = new SKeyVal(sec->options[j]->m_name, sec->options[j]->m_value);
			values.Add(kv);
		}
		return true;
	}
	m_ErrDescription = APL_T("Секция ini не найдена");
	return false;
}

bool CaplINI::isParamSet(LPCTSTR lpAppName, LPCTSTR lpKeyName)
{
	SOption* opt=0;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);

	if(!Parse()) return false;

	opt = FindCreateOption(lpAppName, lpKeyName);

	if(opt==0)
	{
		return false;
	}
	return true;
}

bool CaplINI::isGroupeSet(LPCTSTR lpAppName)
{
	SSection* sec=0;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);

	if(!Parse()) return false;

	for(int i=0;i<m_sections.GetSize();i++)
	{
		if(m_sections[i]->m_name.CompareNoCase(lpAppName)==0)
		{
			sec=m_sections[i];
			break;
		}
	}

	if(sec==0)
	{
		return false;
	}
	return true;
}

CString CaplINI::GetString(LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, bool isCreate)
{
	CString value;
	if(!GetString(lpAppName, lpKeyName, value, lpDefault, isCreate))value=lpDefault;
	return value;
}

bool CaplINI::GetString(LPCTSTR lpAppName, LPCTSTR lpKeyName, CString &value, LPCTSTR lpDefault, bool isCreate)
{
	SOption* opt=0;
	value=lpDefault;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);
	if(!Parse()) return false;
	opt = FindCreateOption(lpAppName, lpKeyName, isCreate);

	if(opt!=0)
		value=opt->m_value;

	if(isCreate && opt!=0 && value.IsEmpty())
	{
		opt->m_value=lpDefault;
		opt->m_value.Trim();
		InternalSave();
		value=lpDefault;
	}
	return true;
}

// Установка значений опций. Если параметр в ini не задан и указан параметр comment, то
// перед строчкой с параметром добавляется строка комментария из comment
bool CaplINI::WriteString(LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR value, LPCTSTR comment)
{
	SOption* opt=0;
	if(!Parse()) return false;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);
	opt = FindCreateOption(lpAppName, lpKeyName, true, comment);

	if(opt==0)
	{
		m_ErrDescription = APL_T("Ошибка при выделении памяти под объект опции ini");
		return false;
	}

	opt->m_need_equal = (_T("") == opt->m_name);
	opt->m_value=value;
	opt->m_value.Trim();

	bool res = true;
	if(m_write_to_file) res = InternalSave();

	return res;
}

bool CaplINI::Delete(LPCTSTR lpAppName, LPCTSTR lpKeyName)
{
	if(!Parse()) return false;

	bool res = true;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);

	int i,j;
	SSection* sec=0;

	for(i=0;i<m_sections.GetSize();i++)
	{
		if(m_sections[i]->m_name.CompareNoCase(lpAppName)==0)
		{
			sec = m_sections[i];
			break;
		}
	}
	if(sec==0)
		return true;

	for(j=0;j<sec->options.GetSize();j++)
	{
		if(sec->options[j]->m_name.CompareNoCase(lpKeyName)==0)
		{
			sec->options.Remove(j);
			j--;
		}
	}
	if(m_write_to_file) res = InternalSave();

	return res;

}

void CaplINI::RenameGroupe(LPCTSTR lpAppNameOld, LPCTSTR lpAppNameNew)
{
	if(!Parse()) return;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);

	int i;
	SSection* sec=0;
	CString str;

	for(i=0;i<m_sections.GetSize();i++)
	{
		if(m_sections[i]->m_name.CompareNoCase(lpAppNameOld)==0)
		{
			sec = m_sections[i];
			break;
		}
	}
	if(sec==0)
		return;

	sec->m_name = lpAppNameNew;

	// Добавляем комментарий о переименовании
	sec->m_prev.Format(APL_T("Группа опций [%s] была переименована. Старое название группы [%s]"),
					   (LPCTSTR)lpAppNameNew, (LPCTSTR)lpAppNameOld);

	InternalSave();
	SetNeedReload();
	Parse();
}

void CaplINI::RenameParam(LPCTSTR lpAppName, LPCTSTR lpKeyNameOld, LPCTSTR lpKeyNameNew)
{
	if(!Parse()) return;

	CaplEnterCriticalSection CriticalSection(&m_DataProtect);

	int i,j;
	SSection* sec=0;
	SOption* opt=0;

	for(i=0;i<m_sections.GetSize();i++)
	{
		if(m_sections[i]->m_name.CompareNoCase(lpAppName)==0)
		{
			sec = m_sections[i];
			break;
		}
	}
	if(sec==0)
		return;

	for(j=0;j<sec->options.GetSize();j++)
	{
		if(sec->options[j]->m_name.CompareNoCase(lpKeyNameOld)==0)
		{
			opt = sec->options[j];
			break;
		}
	}
	if(opt==0)
		return;

	opt->m_name = lpKeyNameNew;

	// Добавляем комментарий о переименовании
	opt=new SOption(_T(""));
	opt->m_value.Format(APL_T("; Опция '%s' была переименована. Старое название опции '%s'"), (LPCTSTR)lpKeyNameNew, (LPCTSTR)lpKeyNameOld);
	sec->Insert(opt, j);


	InternalSave();

}



//////////////////////////////////////////////////////////
bool IsNumber(LPCTSTR _str, bool is_hex)
{
	CString str = _str;
	return IsNumber(str, is_hex);
}

bool IsNumber(CString &str, bool is_hex)
{
	if(str.GetLength()<1) return false;
	int start=0;
	if(str.GetAt(0)==_T('-'))start++;
	else if(is_hex && str.GetLength() >=3 && str.GetAt(0)==_T('0') && str.GetAt(1)==_T('x')) start +=2;

	for(int i=start;i<str.GetLength();i++)
		if(!IsNumber(str[i], is_hex))return false;
	return true;
}

bool IsNumber(TCHAR smb, bool is_hex)
{
	if((_T('0'))<=smb&&(smb<=_T('9'))) return true;
	if(is_hex)
	{
		if((_T('A'))<=smb&&(smb<=_T('F')))return true;
		if((_T('a'))<=smb&&(smb<=_T('f')))return true;
	}
	return false;
}

void CaplINI::PrintWarningWithSaveFlags(LPCTSTR message)
{
	bool prevWarn = g_bShow_aplMessageBox_Warning;
	g_bShow_aplMessageBox_Warning = true;
	aplMessagePrint(message, MB_ICONWARNING);
	g_bShow_aplMessageBox_Warning = prevWarn;
	if(m_ErrDescription != _T(""))m_ErrDescription+=_T("\n");
	m_ErrDescription += message;
}

void CaplINI::PrintInfoWithSaveFlags(LPCTSTR message)
{
	if(m_isPrintExpandMessages)
	{
		aplMessagePrint(message);
	}
	else
	{
		if(m_ErrDescription != _T(""))m_ErrDescription+=_T("\n");
		m_ErrDescription += message;
	}
}



// Преобразует короткий путь типа ./db/ и ../AAA/ в полный путь относительно расположения ini
// Если в начале пути точка - то относительный каталог прибавляется к каталогу в котором находится ini
// Если в начале пути две точки - то относительный каталог прибавляется к каталогу уровнем выше каталога с ini
// В остальных случаях путь не меняется
// Если каталог начинается с символа ~  то в консоль выводится предупреждение
CString CaplINI::ExpandPath(LPCTSTR lpSourcePath)
{
	CString fullPath = lpSourcePath;
	CString sourcePath = lpSourcePath;
	CString test_curr = _T("."), test_up = _T("..");
	CString mess;
	test_curr += aplDirRazd; test_up += aplDirRazd;
	if(sourcePath.Left(2) == test_curr)
	{
		int pos1 = m_IniName.ReverseFind(aplDirRazd);
		if(pos1 ==-1)
		{
			PrintWarningWithSaveFlags(APL_T("Отсутствует полный путь к ini, преобразование путей из ini не выполнено!"));
		}
		else
		{
			fullPath = m_IniName.Left(pos1) + sourcePath.Mid(1);

			mess.Format(APL_T("Путь из ini '%s' заменен полным путем '%s'"), (LPCTSTR) lpSourcePath, (LPCTSTR) fullPath);
			PrintInfoWithSaveFlags(mess);
		}
	}
	else if(sourcePath.Left(3) == test_up)
	{
		int pos1 = m_IniName.ReverseFind(aplDirRazd);
		if(pos1 ==-1)
		{
			mess.Format(APL_T("Отсутствует полный путь к ini; преобразование путя '%s' из ini не выполнено!"), (LPCTSTR)lpSourcePath);
			PrintWarningWithSaveFlags(mess);
		}
		else
		{
			CString p1 = m_IniName.Left(pos1);
			int pos2 = p1.ReverseFind(aplDirRazd);
			if(pos2 ==-1)
			{
				mess.Format(APL_T("Полный путь к ini начинается от корня файловой системы; преобразование путя '%s' из ini не выполнено!"), (LPCTSTR)lpSourcePath);
				PrintWarningWithSaveFlags(mess);
			}
			else
			{

				fullPath = m_IniName.Left(pos2) + sourcePath.Mid(2);

				mess.Format(APL_T("Путь из ini '%s' заменен полным путем '%s'"), (LPCTSTR) lpSourcePath, (LPCTSTR) fullPath);
				PrintInfoWithSaveFlags(mess);
			}
		}
	}
#ifdef __linux__
	else if(sourcePath.Left(1) == _T("~"))
	{
		CString home;
		home.GetEnvironmentVariable(_T("HOME"));
		fullPath = home + sourcePath.Mid(1);

		mess.Format(APL_T("Путь из ini '%s', указывающий на домашний каталог текущего пользователя,  заменен полным путем '%s'"),
					(LPCTSTR)lpSourcePath, (LPCTSTR)fullPath);
		//PrintWarningWithSaveFlags(mess);
		PrintInfoWithSaveFlags(mess);
	}
#endif
	fullPath.TrimRight();
	if(fullPath.Left(1)==_T('"') && fullPath.Right(1)==_T('"'))
		fullPath.Trim(_T('"'));

	return fullPath;
}

