/*	autor: Kacper Szkudlarek
 *	grupa: 4I2 */

#include <iostream>
#include <list>
#include <string>

using namespace std;

class Testowa{
private:
	int x_;
	string tekst_;
public:
	Testowa():x_(0), tekst_(""){}
	Testowa(int x, string& tekst): x_(x), tekst_(tekst){}
	friend ostream& operator<< (ostream& str, const Testowa& test);
	friend bool operator<(const Testowa& t1, const Testowa& t2);
};

bool operator <(const Testowa& t1, const Testowa& t2){
	return t1.x_ < t2.x_;
}

ostream& operator<< (ostream& str,const Testowa& test){
		str << test.x_ << " " << test.tekst_ << endl;
		return str;
}

bool singleDigit(const int& val){
	return val < 10;
}

template<class T> void wypiszListe(list<T> &li, const char* tekst){
	list<T>::const_iterator iter;
	cout << "------------------------------------------------------------------" << endl;
	cout << tekst << endl;
	cout << "------------------------------------------------------------------" << endl;
	for(iter = li.begin(); iter != li.end(); ++iter){
		 cout << *iter << endl;
	} 
}


int main(int argc, char** argv){
	cout << "****************************************************" << endl;
	cout << "*  Przykład operaji na liscie typu wbudowanego int *" << endl;
	cout << "****************************************************" << endl << endl << endl;

/*	Nagłówek <list> zawiera deklakrację std::list. Naszym pierwszym krokiem będzie utworzenie pustej listy typu int i nazwanie jej li: */
	 list <int> li;

/*	Aby umieścić na końcu listy pojedynczy element, używamy funkcji: */

	li.push_back(1);

/*	Aby usunąć pojedynczy element z końca listy, używam funkcji: */

	li.pop_back();

/*	Przecieiweństwem do push_back(),  jets funkcja push_front() umieszcająca pojedynczy element na początku listy: */

	li.push_front(0); // Teraz pierwszym elementem jest 0, a nie 1

/*	Podobnie, pop_front() usuwa pierwszy element listy: */

	li.pop_front();

/*	Aby usunść element z listy, nie znająć jego konkretnego położenia, używamy remove(). 
 *	remove(n) usunie wszystkie elementy równe n, znajdujące sie na lisćie:*/
	li.remove(0); // li nie zawiera wiecej elementów  

/*	Możliwe jest także usuwanie warunkowe, element, jest usuwany z listy. Instrukcją warunkową, moze byc dowolne wyrażenie
 *	pobierające element taki jak typ listy i zwracające wartosc bool*/
	li.remove_if(singleDigit);

/*	Jeżeli chcemy, aby lista była "różnowartosciowa" można zastosować metodę unique(). Ma ona dwie przeciązone wersje,
 *	bezparametrową, dokonujacą zwykłęgo porównania elementów, i wersje, w której mozemy zdefiniować metodę porównujacą.
 *	Nalży jednak pamietać, ze funkcja ta usunie element, tylki i wyłacznie, jeżeli będzie on równy elementowi go poprzedającemu.*/

/*	std::list pozwala także na operacje na wielu elementach, dzięki czemu, mozemy postortować,
 *	czy wypełnić listę serią od razu.
 *	
 *	Iteratory:
 *	Funkcje begin()i end()zwracajc iteratory wskazujace odpowiednio na pierwszy i osttatni element listy.
 *	Można ich użyć do dostępu sekwenyjnego do listy:*/
	list<int>::const_iterator iter;
	for(iter = li.begin(); iter != li.end(); ++iter){
	 cout << *iter << endl;
	} 
/*	Funkcje rbegin() i rend() zwracają std::list::reverse_iterator pozwalając na przechodzenie listy
 *	w kolejności odwrotnej, zwarcając odpowiednio elelemtn ostatni i perwszy.*/

/*	Funkcja assign() wypełnia listę serią/sekwencją danych z innej listy, vecotra, tablicy. 
 *	Pobiera dwa iteratory wskazujące początek i koniec serii danych:*/
	int tab[3] = {10,40,30};
	li.assign( &tab[0], &tab[3]);
	wypiszListe(li, "Lista wypelniona za pomoca assign");
	
/*	W celu wstawienia elelemntów do listy, można także użyć metody insert().
 *	Przyjmuje ona jako paramatry, element, przed którym maja być wstawione elementy, a takze element, lub grupe iteratory na początek i koniec grupy elementó do wstawienia.*/

	iter = li.begin();
	++iter; ++iter;
	int tabIns[2] = {5, 6};
	li.insert(iter, &tabIns[0], &tabIns[2]);
	wypiszListe(li, "Wstawienie elementów w środek");

/* Istnieje możliwość zamiany dwóch list miejscami. */

	list <int> x;
	int tab2[3] = {50,20,60};	
	x.assign(&tab2[0], &tab2[3]);
	li.swap(x);
	wypiszListe(li, "Zamian list miejscami");

/*	Można także połaczyć dwie, posortoawne listy w jedną. Funckja merge()pobiera referencje do obiektu drugiej listy, i wstawia jej elementy do listy pierwszej w porządku rosnacym na odpowiednich miejscah: */

	x.sort();
	li.sort();
	li.merge(x);
	wypiszListe(li, "Połączenie list");

/*	merge() dołącza x do li. x staje sie pustą listą. 
 *	Aby wstawić elementy z jednej listy do drugiej, bez potrzeby sortowania, należy użyć metofu splice().
 *	Jako parametry należy podać pozycje na której maja być wstawione elementy, listę, z której elementy będą pobierane, oraz zakres, elementów do wstawienia */
	x.assign(&tab2[0], &tab2[3]);
	iter = li.begin();
	++iter; ++iter;
	li.splice(iter, x, x.begin(), x.end());
	wypiszListe(li, "Wstaienie lementów w środek listy, bez uprzedniego sortowania");

/*	Aby usunąć serię elementów, używam funkcji erase(). Ta funkcja ma dwie przeciażone wersje:
 *		- pierwsza pobiera iterator i usuwa element na który on wskazuje,
 *		- druga, która pobiera iterator dwa iteratory i usuwa wszstkie lementy miedzy nimi*/

	iter = li.begin();
	++iter; ++iter; // przesuwamy iterator na lement 3
	li.erase(iter, li.end()); // usuwamy elementy od 3 do końca
	wypiszListe(li, "Lista po usunięciu elemetnów od trzeciego");

/*	Na koniec możemy liste wyczyścić całkowicie używajać metody celar() */
	
	li.clear();

/*	Użytkownik otrzymuje także, możliwość psrawdzenia stanu listy, istnienia jakichkolwiek elementów, rozmiaru, maksymalnego możliwego rozmiaru.
 *	Udostępniana jest także metoda resize() pozwalajaca na zmianę wielkości listy. Przyjmuje ona jako paramtary, nowy rozmiar listy i obiekt,
 *	który przy rozszeżaniu będzie dodawany. */

	int tab3[10] = {0,1,2,3,4,5,6,7,8,9}; 
	li.assign(&tab3[0], &tab3[10]);
	wypiszListe(li, "Wypełnienie listy 10 elementami");

	if(!li.empty()){
		cout << "------------------------------------------------------------------" << endl;
		cout << "Lista nie jest pusta - if(!empyt())" << endl;
		cout << "------------------------------------------------------------------" << endl;
	}
	cout << "------------------------------------------------------------------" << endl;
	cout << "Rozmiar listy: " << (int)li.size() << " Max rozmiar: " << (int)li.max_size() << endl;
	cout << "------------------------------------------------------------------" << endl;
	li.resize(5);
	wypiszListe(li, "Zmniejszenie do 5 elementów");
	li.resize(10, 199);
	wypiszListe(li, "Ponowne zwiększenie do 10");

/*	Oprócz iteratorów, aby uzyskać dostęp do początku i końca listy można, także użyć funkcji front() i back(). */
	cout << "------------------------------------------------------------------" << endl;	
	cout<< "Pierwszy elemetn listy - fornt(): " << li.front() << ". Ostatli element - back(): " << li.back() << endl;
	cout << "------------------------------------------------------------------" << endl;

/*	std::list oferuje także funkcje pozwalające zmieniać kolejność elementów w liście. Są nimi sort() i revers(). */

	li.clear();
	int tab4[10] = {13,1,15, 76, 5, 99, 1, 0, 56,7};
	li.assign(&tab4[0], &tab4[10]);
	wypiszListe(li, "Lista nie posortowana ");
	li.sort();
	wypiszListe(li, "Lista posortowana ");
	li.reverse();
	wypiszListe(li, "Lista w odwrotnej kolejności");

/*	std::list pozwala na tworzenie list typów wbudowanych jak i zdefiniowanych przez użytkownika, np.:
 *	Operacje takie jak  sort() czy unique() wykonywane na typach zdefiniowanych przez użytkownika,
 *	wymagają przeciążenia operatora <. */
	cout << endl << endl << endl;
	cout << "********************************************************************" << endl;
	cout << "*  Przykład operaji na liscie typu zdefiniowanego przez użytownika *" << endl;
	cout << "********************************************************************" << endl << endl << endl;

	list<Testowa> listTest;
	list<Testowa> listTest2;

	listTest2.push_back(Testowa(5, string("element - lista2 5")));
	listTest2.push_back(Testowa(6, string("element - lista2 6")));
	listTest2.push_back(Testowa(7, string("element - lista2 7")));
	listTest2.push_back(Testowa(8, string("element - lista2 8")));

	listTest.push_back(Testowa(1, string("element 1")));
	listTest.push_back(Testowa(2, string("element 2")));
	listTest.push_back(Testowa(3, string("element 3")));
	listTest.push_back(Testowa(4, string("element 4")));


	wypiszListe( listTest, "Zawartosc listy");

	listTest.push_front(Testowa(5, string("element 5")));
	wypiszListe( listTest, "Dodanie elementu z przodu listy");

	wypiszListe(listTest, "Lista 1");
	wypiszListe(listTest2, "Lista 2");

	listTest.swap(listTest2);
	wypiszListe(listTest, "Zamiana list");

	list<Testowa>::const_iterator it = listTest.begin();
	++it; ++it;
	listTest.splice(it, listTest2);
	wypiszListe( listTest, "Wstawienie listy 2 od 3 miejsca do listy pierwszej");

	listTest.sort();
	wypiszListe( listTest, "Sortowanie listy");

	listTest.reverse();
	wypiszListe( listTest, "Odwrócenie kolejności");
	cout << "------------------------------------------------------------------" << endl;
	cout << "Wypisanie listy w odwrotnej kolejnosci przu uzyciu rbegin i rend" << endl;
	cout << "------------------------------------------------------------------" << endl;
	list<Testowa>::reverse_iterator rit;
	for(rit = listTest.rbegin(); rit != listTest.rend(); ++rit){
	 cout << *rit << endl; 
	} 

	listTest.pop_back();
	wypiszListe( listTest, "Usuniecie ostatniego elementu");
	
	it = listTest.begin();
	++it; ++it;
	listTest.erase(it, listTest.end());
	wypiszListe(listTest, "Usunięcie elementów od 3 do końca");

	return 0;
}
