#include "INI_File.h"


#include <sstream>

#ifdef DEBUG
#define ASSERT(test) if (!test) fail();
#else
#define ASSERT(test) 
#endif

// chargement d'un fichier INI
/*!	\param strFilename : le nom avec chemin complet du fichier à charger
	\return renvoie un booléen indiquant si la lecture du fichier s'est bien passée (true) ou non (false)
*/
bool CINI_File::Load(const std::string& strFilename)
{
	std::string strBuff;
	std::ifstream ficIn;

	// on élimine les données présentes
	RemoveAll();

	// ouverture du fichier
	ficIn.open(strFilename.c_str(), std::ios_base::in);
	if(!ficIn.is_open())
	{
		std::ostringstream strMsg;
		strMsg << "CINI_File::Load" << std::endl << strFilename << std::endl << "Impossible d'ouvrir le fichier en lecture";
		strBuff = strMsg.str();
		return false;
	}

	int nPosDebut, nPosFin, nPosValue;
	CINI_Section* pSection = NULL;
	std::string strKey, strValue;
	// lecture du fichier
	while( std::getline(ficIn, strBuff) )
	{
		// recherche de l'encadrement d'une section
		nPosDebut = (int)strBuff.find('[');
		nPosFin = (int)strBuff.find(']') - 1;
		// si on a trouvé un encadrement
		if(nPosDebut >= 0 && nPosFin > nPosDebut)
		{ // alors on crée une section
			pSection = CreateSection( strBuff.substr(nPosDebut + 1, nPosFin - nPosDebut) );
		}
		else if(pSection != NULL) // sinon, si on est déjà dans une section
		{
			nPosValue = (int)strBuff.find('='); // on recherche un séparateur de valeur
			if(nPosValue > 0) // et si on en a trouvé un, on récupère la clef et la valeur
			{
				strKey = strBuff.substr(0, nPosValue);
				strValue = strBuff.substr(nPosValue + 1);
				Trim(strKey);
				Trim(strValue);
				pSection->SetKey(strKey, strValue);
			}
		}
	}

	// fermeture du fichier
	ficIn.close();

	return true;
}

// sauvegarde sous forme d'un fichier INI
/*!
	\param strFilename : nom et chemin complet du fichier à créer
	\return renvoie un booléen indiquant si l'écriture du fichier s'est bien passée (true) ou non (false)
*/
bool CINI_File::Save(const std::string& strFilename)
{
	std::ofstream ficOut;

	// création/ouverture du fichier où écrire
	ficOut.open(strFilename.c_str(), std::ios_base::out | std::ios_base::binary);
	if(!ficOut.is_open())
	{
		std::ostringstream strMsg;
		strMsg << "CINI_File::Save" << std::endl << strFilename << std::endl << "Impossible d'ouvrir ou de créer le fichier en écriture";
		std::string strBuff = strMsg.str();
		return false;
	}

	CINI_Section* pSection;
	// parcours des sections
	for(int nSectionNb=0; nSectionNb < GetSectionsCount(); nSectionNb++)
	{
		// écriture du libellé de la section entre [...]
		ficOut << "[" << m_arSections[nSectionNb] << "]\r\n";
		// récupération des données de la section
		pSection = GetSectionAt(nSectionNb);
		// écriture des clefs et de la valeur qui leur est associé
		pSection->WriteKeys(ficOut);
		// ajout d'un retour à la ligne pour séparer les sections
		ficOut << "\r\n";
	}

	// fermeture du fichier
	ficOut.close();

	return true;
}

// recherche et création d'une section
/*!
	\param strSection : le libellé de la section à trouver où à créer
	\return renvoie un pointeur vers l'objet CINI_Section (nouvellement créé si elle n'existait pas)
*/
CINI_Section* CINI_File::CreateSection(const std::string& strSection)
{
	CINI_Section* pSection;

	if(!LookupSection(strSection, pSection))
	{
		pSection = new CINI_Section;
		ASSERT(pSection != NULL);
		m_mapSections[strSection] = pSection;
		m_arSections.push_back(strSection);
	}

	return pSection;
}

// recherche d'une section
/*!
	\param strSection : le libellé de la section à rechercher
	\param pSection : un pointeur non initialisé passé en référence qui va pointer vers l'objet CINI_Section trouvé
	\return renvoie un booléen indiquant si la section a été trouvée (true) ou non (false)
*/
bool CINI_File::LookupSection(const std::string& strSection, CINI_Section*& pSection)
{
	StdMapStringToSectionPtr::iterator itMap = m_mapSections.find(strSection);

	if(itMap == m_mapSections.end())
		return false;

	pSection = itMap->second;
	
	return true;
}

// retourne le pointeur vers la section demandée
/*!
	\param nIndex : l'index de l'emplacement où trouver la section
	\return renvoie un pointeur vers l'objet CINI_Section
*/
CINI_Section* CINI_File::GetSectionAt(const int nIndex)
{
	ASSERT(nIndex >= 0);
	ASSERT(nIndex < m_arSections.size());

	CINI_Section* pSection = NULL;

	LookupSection(m_arSections[nIndex], pSection);

	return pSection;
}

// retourne l'index de la section demandée
/*!
	\param strSection : le libellé de la section recherchée
	\return renvoie l'index positif ou nul de l'emplacement de la section
*/
int CINI_File::GetSectionIndex(const std::string& strSection)
{
	for(int nSectionNb=0; nSectionNb < GetSectionsCount(); nSectionNb++)
		if(strSection.compare( m_arSections[nSectionNb] ) == 0)
			return nSectionNb;

	return -1;
}

// supprime la section complète
/*!
	\param strSection : le libellé de la section à supprimer
	\return renvoie un booléen indiquant si la section a été supprimée (true) ou non trouvée (false)
*/
bool CINI_File::RemoveSection(const std::string& strSection)
{
	int nSectionNb = GetSectionIndex(strSection);
	
	if(nSectionNb >= 0)
	{
		RemoveSectionAt(nSectionNb);
		return true;
	}

	return false;
}

// supprime la section complète
/*!
	\param nIndex : l'index de la section à supprimer
*/
void CINI_File::RemoveSectionAt(const int nIndex)
{
	ASSERT(nIndex >= 0);
	ASSERT(nIndex < GetSectionsCount());

	CINI_Section* pSection;
	std::string strSection( m_arSections[nIndex] );

	LookupSection(strSection, pSection);
	m_mapSections.erase(strSection);
	m_arSections.erase(m_arSections.begin() + nIndex);
}

// supprime toutes les sections
void CINI_File::RemoveAll(void)
{
	for(int nSectionNb=0; nSectionNb < GetSectionsCount(); nSectionNb++)
		delete GetSectionAt(nSectionNb);

	m_mapSections.clear();
	m_arSections.clear();
}

// une petite méthode pour supprimer les blancs au début et à la fin d'une chaine
/*!
	\param str : la chaine à traiter
	\return retourne la chaine modifiée
*/
void CINI_File::Trim(std::string& str)
{
	std::string::size_type pos = str.find_last_not_of(' ');
	
	if(pos != std::string::npos)
	{
		str.erase(pos + 1);
		pos = str.find_first_not_of(' ');
		if(pos != std::string::npos)
			str.erase(0, pos);
	}
	else
		str.erase(str.begin(), str.end());
}
