// 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 (float i) 
{
	std::cout << "\nvoid funkcja_globalna : " << i;
}

struct example
{
	void funkcja_klasy (float i) const
	{
		std::cout << "\nvoid example::funkcja_klasy : " << i;
	}

	virtual ~example(){};
};

struct example_derived : public example 
{
	void funkcja_klasy_pochodnej (float i) const
	{
		std::cout << "void example_derived::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(3.4f);
	vec_test.push_back(666.666f);
	
	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 lambda z biblioteki boost "domyślają się" z czym mają doczynienia.

	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")  );

	// 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>
		 (var(std::cout) << constant("Wybrales 0 \n")),
		 case_statement<1>
		 (var(std::cout) << constant("Wybrales 1 \n")),
		 default_statement
		 (var(std::cout) << constant("Nie mam pojecia co wybrales!\n")))
	 ) ((make_const(666)));

	 // Uzycie (make_const(666)) jest konieczne. Genruje one const referencje przyjmonwaną przez szablony boost.
	 // Ponadto całość jest wyrażeniem lambda, dlatego aby było dobrze zrozumiane prze bibliotekę
	 // należy zmienić obiekt std::cout na element wyrażenia lambda przy pomocy funkcji var().

	 // 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));

	 // Przykład 4. Biblioteka boost umożliwia wprowadzenie rzutowania w wyrażeniu lambda jak również
	 // genrowanie i przechwytywanie wyjątków.

	 example_derived 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(&example_derived::funkcja_klasy_pochodnej, ll_dynamic_cast<example_derived&>(*_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(&example_derived::funkcja_klasy_pochodnej, ll_dynamic_cast<example_derived&>(*_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.

	return 1;
}
