// autor: MATEUSZ KOŁODZIEJCZYK
// grupa: ISI, 7sem
// temat: strumienie plikowe fstream, ifstream, ofstream
// program: klasa tworzaca i zarzadzajaca plikami konfiguracyjnymi + program testowy

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

class ConfigFile
{
public:
	ConfigFile()
	{}

	~ConfigFile()
	{
		if(fileStream.is_open())
		{
			Close();
		}
	}

	// Utworzenie łącza logicznego z plikiem o nazwie file
	// Jeżeli plik nie istnieje, zostaje utworzony jako nowy
	// Wyjście: 0 – powodzenie, 1 – niepowodzenie
	int Open(const char *file)
	{
		// zapisanie nazwy pliku
		fileName = file;
		// proba otwarcia pliku
		fileStream.open(file, ios_base::in | ios_base::out);

		// jesli nie udalo sie otworzyc to znaczy ze trzeba go stworzyc
		if(!fileStream.is_open())
		{
			// stworzenie pliku
			ofstream fileTmp(file);
			fileTmp.close();

			// otwarcie stworzonego pliku do zapisu i do odczytu
			fileStream.open(file, ios_base::in | ios_base::out);

			if(!fileStream.is_open())
				return 1;
		}

		return 0;
	}

	
	//Zamknięcie łącza z plikiem.
	void Close()
	{
		if(fileStream.is_open())
			fileStream.close();
	}

	// Sprwadzenie obecności pozycji o nazwie name
	// Wyjście: true – pozycja istnieje, false – pozycja nieistnieje
	bool Check(const char *name)
	{
		// sprawdzenie czy plik zostal otwarty
		if(!fileStream.is_open())
			return false;

		// zerowanie wskaznikow pisania i czytania
		clearFilePointers();

		// wczytuje kazda linie z pliku
		string line;
		while(getline(fileStream, line))
		{
			// odczytuje nazwe z linii
			string _name = this->findName(line);

			// sprawdza czy sie zgadza z ta w arg
			if(!_name.compare(name))
				return true;
		}

		return false;
	}

	// Odczytanie i umieszczenie w tablicy value wartości pozycji o nazwie name
	// Wyjście: 0 – powodzenie, 1 – nie istnieje pozycja o nazwie name
	int Read(const char *name, char *value)
	{
		if(!fileStream.is_open())
			return 1;

		clearFilePointers();

		string line;
		while(getline(fileStream, line))
		{
			string _name = this->findName(line);

			if(!_name.compare(name))
			{
				string _value = this->findValue(line);

				// kopiuje znaleziona wartosc
				strcpy(value,_value.c_str());

				return 0;
			}
		}

		return 1;
	}

	// Sprawdzenie, czy pozycja o nazwie name ma wartość tekstową value
	// Wyjście: true – tak, false – nie
	bool Compare(const char *name, const char *value)
	{
		if(!fileStream.is_open())
			return false;

		clearFilePointers();

		string line;
		while(getline(fileStream, line))
		{
			// znalezienie w linii nazwy
			string _name = this->findName(line);
			
			if(!_name.compare(name))
			{
				// odczytanie z linii wartosci
				string _value = this->findValue(line);

				// porownanie z wartoscia podana w argumencie
				if(!strcmp(value, _value.c_str()))
					return true;

				return false;
			}
		}

		return false;
	}

	// Dodanie nowej pozycji o nazwie name i wartości telstowej value
	// Jeżeli podana pozycja już istnieje, jej wartość ulega zmianie na nową
	// W pliku konfiguracyjnym wartości tekstowe zapisywane w cudzysłowach
	void Add(const char *name, const char *value)
	{
		// spr czy plik jest otwarty i czy nazwa jest poprawna
		if(!fileStream.is_open() || !checkName(name))
			return;

		clearFilePointers();

		string line;
		bool flag = false;

		// otwieram plik tymczasowy
		string tmpName = fileName + ".tmp";
		ofstream tempStream(tmpName.c_str());

		while(getline(fileStream, line))
		{
			string _name = this->findName(line);

			// porownuje z nazwa podana w argumencie
			if(!_name.compare(name))
			{
				// jesli juz taka istanieje to tworze nowa linie i podmieniam ja
				tempStream << createString(name, value) << endl;
				flag = true;
			}
			else
			{
				// przepisuje nieinteresujace mnie linie
				tempStream << line << endl;
			}
		}

		// zamykam tymczasowy strumien
		tempStream.close();

		// spr czy byla modyfikacja
		if(!flag)
		{
			// nie bylo wiec usuwam plik tymczasowy i dodaje nowa linie na koncu pliku
			remove(tmpName.c_str());

			fileStream.seekg(0);

			fileStream << createString(name, value) << endl;
		}
		else
		{
			// byla, a wiec usuwam stary plik, zmieniam nazwe nowego na taka jak mial stary,
			// i otwieram nowy plik funkcja open
			fileStream.close();
			
			remove(fileName.c_str());
			rename(tmpName.c_str(), fileName.c_str());

			this->Open(fileName.c_str());
		}
	}

	// Dodanie nowej pozycji o nazwie name1 i wartości telstowej value nad pozycją o
	// nazwie name2
	// Jeżeli pozycja o nazwie name1 istnieje, wóczas powinna być przeniesiona do nowego
	// miejsca z nową wartością
	// Wyjście: 0 – powodzenie, 1 – nie istnieje pozycja o nazwie name2
	int Insert(const char *name1, const char *value, const char *name2)
	{
		if(!fileStream.is_open() || !checkName(name1))
			return 1;

		clearFilePointers();

		bool flag = false;

		// bardzo podobne rozumowanie do funkcji add
		string tmpName = fileName + ".tmp";
		ofstream tempStream(tmpName.c_str());

		string line;
		while(getline(fileStream, line))
		{
			string _name = this->findName(line);

			if(!_name.compare(name2))
			{
				// jesli znalazlem wpis z nazwa 'nazwa2' to przed nim wstawiam nowa linie
				tempStream << createString(name1, value) << endl;
				// i przepisuje go za nia
				tempStream << line << endl;
				// ustawiam flage modyfikacji
				flag = true;
			}
			else if(_name.compare(name1))
			{
				// jesli nie nastrafilem na wpis to przepisuje do nowego pliku
				tempStream << line << endl;
			}
		}

		tempStream.close();

		// spr czy byla modyfikacja - podobnie jak w add
		if(!flag)
		{
			remove(tmpName.c_str());

			return 1;
		}
		else
		{
			fileStream.close();
			
			remove(fileName.c_str());
			rename(tmpName.c_str(), fileName.c_str());

			this->Open(fileName.c_str());
		}

		return 0;
	}

	// Usunięcie wiersza z pozycją o nazwie name
	void Delete(const char *name)
	{
		if(!fileStream.is_open())
			return;

		clearFilePointers();

		bool flag = false;

		string tmpName = fileName + ".tmp";
		ofstream tempStream(tmpName.c_str());

		string line;
		while(getline(fileStream, line))
		{
			string _name = this->findName(line);

			// sprawdzam czy natrafilem na pozycje o nazwie 'name'
			if(_name.compare(name))
			{
				// jesli nie to przepisuje
				tempStream << line << endl;
			}
			else
			{
				// jesli natrafilem to pomijam ja i ustawiam flage modyfikacji
				flag = true;
			}
		}

		tempStream.close();

		if(!flag)
		{
			remove(tmpName.c_str());
		}
		else
		{
			fileStream.close();
			
			remove(fileName.c_str());
			rename(tmpName.c_str(), fileName.c_str());

			this->Open(fileName.c_str());
		}
	}

	// Wstawienie nad pozycją o nazwie name komentarza o treści comm
	// Wyjście: 0 – powodzenie, 1 – nie istnieje pozycja o nazwie name, 2 – nad pozycją
	// name nie ma komentarza
	int InsertComment(const char *name, const char *comm)
	{
		if(!fileStream.is_open())
			return 1;

		clearFilePointers();

		int flag = 1;

		string tmpName = fileName + ".tmp";
		ofstream tempStream(tmpName.c_str());

		string line;
		// bedzie potrzebna zeby badac poprzedni linie .. a w zasadzie jej nazwe
		string prevLineName;
		// bedziemy pamietac linie zeby moc zapisywac z opoznieniem (jakby pojawil sie komentarz ktory trzeba zmodyfikowac)
		string prevLine;

		while(getline(fileStream, line))
		{
			string _name = this->findName(line);

			if(!_name.compare(name))
			{
				if(!prevLineName.compare("#"))
				{
					// trzeba nadpisac komentarz
					prevLine = "#" + string(comm);
				}
				else
				{
					// trzeba dodac komantarz
					tempStream << "#" << comm << endl;
				}

				flag = 0;
			}

			// nie zapisujemy od razu do tylko z opoznieniem
			if(!prevLine.empty())
			{
				tempStream << prevLine << endl;
			}
			
			prevLine = line;
			prevLineName = _name;
		}

		// zapisanie ostatniej pozycji wynikajacej z wprowadzonego opoznienia
		tempStream << prevLine << endl;

		tempStream.close();

		if(flag != 0)
		{
			remove(tmpName.c_str());

			return flag;
		}
		else
		{
			fileStream.close();
			
			remove(fileName.c_str());
			rename(tmpName.c_str(), fileName.c_str());

			this->Open(fileName.c_str());
		}

		return flag;
	}

private: 
	// strumien plikowy
	fstream fileStream;
	// nazwa otwartego/stworzonego pliku
	string fileName;

	// odszukuje w podanej linii nazwe i ja zwraca ... jesli natrafi na linie
	// z komentarzem to zwroci indentyfikator komentarza '#'
	string findName(string line)
	{
		string _name = "";
		for(string::iterator it = line.begin(); it<line.end(); ++it)
			if(isalnum(*it))
				_name += *it;
			else if(*it == '=')
				break;
			else if(*it == '#')
				return "#";

		return _name;
	}

	// zwraca wartosc z podanej linii
	string findValue(string line)
	{
		string _value = "";
		string::iterator it;
		for(it=line.begin(); it<line.end(); ++it)
			if(*it == '=')
				break;

		for(; it<line.end(); ++it)
			if(isalnum(*it) || *it == '.')
				_value += *it;

		return _value;
	}

	// tworzy wpis konfiguracyjny
	string createString(const char *name, const char *value)
	{
		string newLine = name;
		newLine += " = ";
		newLine += value;

		return newLine;
	}

	// zeruje wstazniki pliku
	void clearFilePointers()
	{
		if(fileStream.fail() || fileStream.eof())
			fileStream.clear();
		
		// przesuwam wskaznik odczytu i pisania na poczatek pliku
		fileStream.seekg(0, ios_base::beg);
		fileStream.seekp(0, ios_base::beg);
	}

	// sprawdza poprawnosc nazwy
	bool checkName(const char *name)
	{
		string _name(name);

		for(string::const_iterator it = _name.begin(); it < _name.end(); ++it)
		{
			if(!isalnum(*it))
				return false;
		}

		return true;
	}
};

void helpMenu()
{
	cout << "KOMENDA \tOPIS" << endl << endl;
	cout << "open\t\t- otwarcie pliku" <<endl;
	cout << "close\t\t- zamkniecie pliku" << endl;
	cout << "check\t\t- sprawdzenie czy istnieje wpis o danej nazwie w pliku"<<endl;
	cout << "read\t\t- odczytanie wartosci danej pozycji z pliku" <<endl;
	cout << "cpr\t\t- sprawdzenie czy dana pozycja ma dana wartosc " << endl;
	cout << "add\t\t- dodanie wpisu o danej nazwie i wartosci" << endl;
	cout << "ins\t\t- wstawienie wpisu o danej nazwie i nartosci przed innym wpisem" <<endl;
	cout << "del\t\t- usuniecie spisu o danej nazwie" << endl;
	cout << "com\t\t- dodanie komentarza przed danym wpisem" << endl << endl;
}

int main()
{
	// program testowy nie posiada idiotoodpornosci

	cout << "Wybierz opcje (pomoc- help)" << endl;
	string cmd;

	ConfigFile * p = new ConfigFile();

	string name;
	string name2;
	string value;

	do
	{
		cout << "<$config>: ";
		cin >> cmd;
		
		if(cmd == "help")
			helpMenu();
		else if(cmd == "open")
		{
			cout << "nazwa pliku: ";
			cin >> name;

			cout << "powodzenie ? " << p->Open(name.c_str()) << endl;
		}
		else if(cmd == "close")
		{
			p->Close();
		}
		else if(cmd == "check")
		{
			cout << "nazwa pozycji: ";
			cin >> name;

			cout << "wystepuje? : " <<  p->Check(name.c_str()) << endl;
		}
		else if(cmd == "read")
		{
			cout << "nazwa pozycji: ";
			cin >> name;

			char value[50];

			if(p->Read(name.c_str(),value))
				cout << "  : nie ma pozycji" << endl;
			else
				cout << "  : " << value << endl;
		}
		else if(cmd == "cpr")
		{
			cout << "nazwa pozycji: ";
			cin >> name;

			cout << "wartosc pozycji: ";
			cin >> value;

			cout << "takie same? : " << p->Compare(name.c_str(), value.c_str()) << endl;
		}
		else if(cmd == "ad3")
		{
			cout << "nazwa pozycji: ";
			cin >> name;

			cout << "wartosc pozycji: ";
			cin >> value;

			p->Add(name.c_str(), value.c_str());
		}
		else if(cmd == "ins")
		{
			cout << "nazwa pozycji: ";
			cin >> name;

			cout << "wartosc pozycji: ";
			cin >> value;

			cout << "nazwa pozycji przed ktora wstawiamy: ";
			cin >> name2;

			cout << "powodzenie? : " << p->Insert(name.c_str(), value.c_str(), name2.c_str()) << endl;
		}
		else if(cmd == "del")
		{
			cout << "nazwa pozycji: ";
			cin >> name;

			p->Delete(name.c_str());
		}
		else if(cmd == "com")
		{
			cout << "nazwa pozycji: ";
			cin >> name;

			cout << "komentarz: ";
			cin >> name2;

			p->InsertComment(name.c_str(), name2.c_str());
		}
		else if(cmd == "exit")
			cout << "goodbye" << endl;
		else 
			cout << "Nie ma takiego polecenia ... spis polecen - komenda 'help' " << endl;
	}
	while(cmd != "exit");

	delete p;

	return 0;
}
