// Autor: Przemysław Paprocki G1ISI

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/if.hpp>
#include <boost/lambda/switch.hpp>
#include <boost/lambda/loops.hpp>
#include <boost/lambda/casts.hpp>
#include <boost/lambda/exceptions.hpp>

/*Biblioteka standardowa algorithm udostępnia wiele przydatnych szablonów umożliwiających
wykonywanie często potrzebnych algorytmów na kontenerach. Konstrukcje te umożliwiają np. wyszukiwanie,
sortowanie, przegladanie z modyfikacją itp. Problem w tym, że dla każdej operacji trzeba było implementować
drobną klasę tzw.obiekt funkcyjny. Obiekt ten miał za zadanie dostarczyć odpowiedniego operatora czy funkcji 
która miała za zadanie określić logikę operacji na elementach kontenera. Następnie obiekt był przekawzywany 
do szablonu. Taka metoda powodowała powstanie wiele obiektów funkcyjnych które niekiedy w dużym projekcie
były wykorzystywane tylko raz. Biblioteka boost wprowadza mechanizm (wraz z mechanizmami pomocniczymi) który 
umożliwia konstruowanie tak zwanych wyrażeń lambda. Można je rozumieć jako nienazwane funkcje które są wykonywane
w miejscu definicji. Poniższe przykłady pokazują (w niewielkim stopniu) możliwości tych wyrażeń.*/

void funkcja_globalna (const float i) 
{
	std::cout << "\nvoid funkcja_globalna : " << i;
}

struct Example
{
	void funkcja_klasy (const float i) const
	{
		std::cout << "\nvoid Example::funkcja_klasy : " << i;
	}

	virtual ~Example(){};
};

struct ExampleDerived : public Example 
{
	void funkcja_klasy_pochodnej (float i) const
	{
		std::cout << "void ExampleDerived::funkcja_klasy_pochodnej : " << i << std::endl;
	}
};

int main()
{
	std::vector<float> vec_test;
 
    using namespace boost::lambda;

	// Przykład 1. Pierwszy rzut oka na wyrażenie lambda :

	std::cout << "Przyklad 1:\n";
	(std::cout << _2 << " " << _1 << " " << _3 << "\n")				// Definicja wyrażenia (to co sie wykona)
	("zadna lala,","Zadna panna,", "nie zastapi terminala!\n");   // Argumenty wywołania.

	// W wyrażeniach lambda argumenty oznaczamy jako _X gdzie X może być cyfrą od 1 do 9.
	// Bibloteka boost umożliwia zmianę nazwy _X na dowolną poprzez użycię boost::lambda::placeholderX_type.
	// Jednak nie zaleca sie tego robić ze względu na możliwość skonfudowania programistów uczestniczących w
	// projekcie, a przyzwyczajonych do standardowego oznaczenia.

	// Przykład 2. Prezentacja starego dostępu do danych w algorytmach na kontenerze i odpowiedź boost.

	vec_test.push_back(0.5f);
	vec_test.push_back(666.666f) ;
	vec_test.push_back(3.4f);
	
	
	Example ex;
	Example * ex_ptr = &ex;

	std::cout << "Przyklad 2:";

	std::cout << "\nUzycie std::ptr_fun dla wiazania funkcji globalnej.";
	std::for_each(vec_test.begin(), vec_test.end(), std::ptr_fun(funkcja_globalna));

	std::cout << "\n\nWiazanie z metoda klasy za pomoca mem_fun_ref (dla obiektu ex).";
	std::for_each(vec_test.begin(), vec_test.end(), std::bind1st(std::mem_fun_ref(&Example::funkcja_klasy), ex));

	std::cout << "\n\nWiazanie z metoda klasy za pomoca mem_fun (dla wskazania do obiektu ex).";
	std::for_each(vec_test.begin(), vec_test.end(), std::bind1st(std::mem_fun(&Example::funkcja_klasy), ex_ptr));

	// Przy używaniu metod z obecnego standardu musimy rozróżnić wiązania dla obiektów i dla wskazań na te obiekty.
	// Wyrażenia boost::lambda oraz boost::bind "domyślają się" z czym mają do czynienia.

	std::cout << "\n\nUzycie boost::bind dla funkcji globalnych.";
	std::for_each(vec_test.begin(), vec_test.end(), bind(&funkcja_globalna,_1));

	std::cout << "\n\nUzycie boost::bind dla funkcji obiektow klasy.";
	std::for_each(vec_test.begin(), vec_test.end(), bind(&Example::funkcja_klasy, ex ,_1));

	std::cout << "\n\nUzycie boost::bind dla funkcji wskazan do obiektow klasy.";
	std::for_each(vec_test.begin(), vec_test.end(), bind(&Example::funkcja_klasy, ex_ptr ,_1));

	std::cout << "\n\nUzycie wyrazenia lambda dla obiektow (czy tez wskazan do nich) wprost z std::vector.\n";
	std::for_each(vec_test.begin(), vec_test.end(), std::cout << constant("Wyrazenie boost::lambda: ") <<_1 << constant("\n")  );

	std::sort(vec_test.begin(), vec_test.end(), (_1 > _2) );

	std::cout << "\n\nPosortowany vector : \n";
	std::for_each(vec_test.begin(), vec_test.end(), std::cout << constant("Wyrazenie boost::lambda: ") <<_1 << constant("\n")  );

	// Przykład 3. Funkcje sterujące oraz pętle w wyrażeniach lambda.

	// Przekazywane do szablonów standardowych algorytmów funkcje są zazwyczaj bardziej skomplikowane. 
	// Biblioteka boost::lambda umożliwia  standardowe konstrukcje sterujące wymienione poniżej.

	// Przykład 3a. Konstrukcje typu if else

	std::cout << "\nPrzyklad 3a.\nUzycie konstrukcji if_else_then(warunek, instrukcje_dla_true, instrukcje_dla_false)." <<
		" Alternatywnie if_(warunek)[instrukcje_dla_true].else_[instrukcje_dla_false].\n";

	for_each(vec_test.begin(), vec_test.end(), 
		if_then_else(_1 > 3 , 
		std::cout << constant("\n(if_then_else) Liczba : ") << _1 << constant( " jest wieksza niz 3!\n") ,
		std::cout << constant("\n(if_then_else) Liczba : ") << _1 << constant( " jest mniejsza lub rowna 3!\n")));

  	for_each(vec_test.begin(), vec_test.end(), 
		if_(_1 > 3)
		[ 
			std::cout << constant("\n(if_().else_() Liczba : ") << _1 << constant( " jest wieksza niz 3!\n")
		].else_
		[
			std::cout << constant("\n(if_().else_() Liczba : ") << _1 << constant( " jest mniejsza lub rowna 3!\n")
		] );

	// Przykład 3b. Konstrukcje switch.

	 std::cout << "\nPrzyklad 3b.\nUzycie konstrukcji switch.\n\n"   ;
	 (switch_statement(
		 _1,
		 case_statement<0>   
		 (std::cout << constant("Wybrales 0 \n")),
		 case_statement<1>
		 (std::cout << constant("Wybrales 1 \n")),
		 default_statement
		 (std::cout << constant("Nie mam pojecia co wybrales!\n")))
	 ) ((make_const(666)));

	 // Uzycie (make_const(666)) jest konieczne. Szablon tej funkcji jest natępujący i jest zdefinowany w bibliotece boost:
	 // template <class T> inline const T&  make_const(const T& t) { return t; } Przekazanie samego 666 mogłoby sprowokować błąd
	 // ponieważ 666 jest typu int i nie może mieć kwalifikatora const. Szablon natomiast potrzebuje const &.

	 // Przykład 3c. Przykład użycia pętli while.

	 std::cout << "\nPrzyklad 3c:\nUzycie konstrukcji (while_loop(warunek, instrukcje))(argumenty)\n\n";

	 int start = 5;
	 int end = 10;
	 (while_loop(_1 <= _2, 
		 (++_1, std::cout  << _2 << constant(" - ") << _1 << constant(" = ") << _2 -_1 << constant("\n") )))
		 (start,end);

	 // Przykład 3d. Przykład użycia pętli do_while

	 (do_while_loop( _1 == 0 , std::cout << constant ("\nPrzyklad 3d: \nUzycie konstrukcji (do_while_loop(warunek, instrukcje))(argumenty).\n")))(make_const(1));

	 (do_[
		 _1 == 0 , std::cout << constant ("\nUzycie konstrukcji (do_[instrukcje].while_(warunek))(argumenty).\n")
		 ].while_(_1 == 0))(make_const(1));

	 // Przykład 3e. Przykład użycia pętli for.

	 std::cout << "\nPrzyklad 3e.\nUzycie konstrukcji (for_ (war_poczatkowe,warunek, instrukcja iteracji)[instrukcje])(argumenty) : \n\n";
	 int i = 0;

	 (for_ (var(i)=0 , var(i) < _1, ++var(i))
		 [
			 var(std::cout) << var(i) << constant("^2 = ") << make_const( var(i)*var(i) ) << constant("\n")   
		 ]
	 )(make_const (4));

	 //	Ponieważ powyższe wyrażenie jest wyrażeniem lambda więc aby ono zrozumiało zmienne zewnętrzne należy je 
	 // stworzyć jego elementem. Taką konwersje tworzy funkcja var().

	 // Przykład 4. Biblioteka boost::lambda umożliwia wprowadzenie rzutowania w wyrażeniu lambda jak również
	 // genrowanie i przechwytywanie wyjątków.

	 ExampleDerived ex_derv;								    

	 // Rzutowanie w tym przykładzie nie powiedzie się i zostanie wygenerowany wyjątek, a następnie 
	 // przechwycony i obsłużony.

	 std::cout << "\nPrzyklad4.\nUzycie rzutowania i wykorzystanie mechanizmu wyjatkow w wyrazeniach lambda.\n";
	 (try_catch(
		 bind(&ExampleDerived::funkcja_klasy_pochodnej, ll_dynamic_cast<ExampleDerived&>(*_1),_2),
		 catch_exception<std::bad_cast>(bind(&Example::funkcja_klasy,_1,_2))))(ex_ptr, make_const(66.6f));	

	 std::cout << std::endl;
	 // Rzutowanie w tym przykładzie powiedzie się. Przedstawiono dodatkowo schemat zapisu przechwytywania
	 // wyjątków różnych typów oraz dowolonego wyjątku.

	 (try_catch(
		 bind(&ExampleDerived::funkcja_klasy_pochodnej, ll_dynamic_cast<ExampleDerived&>(*_1),_2),
		 catch_exception<std::bad_cast>(bind(&Example::funkcja_klasy,_1,_2)),
		 catch_exception<std::exception>(),
		 catch_all()))(make_const(&ex_derv), make_const(77.7f));

	 // Wyrażenia lambda to przydatne konstrukcje które nie raz mogą zaoszczędzić czas, zmniejszyć ilość kodu czy
	 // zajętą przez program pamięć. Mankamentem jest składnia wyrażeń której należy się po prostu nauczyć i przećwiczyć
	 // na wielu przykładach.

	 getchar();

	return 1;
}
