﻿/**
 *	autor:		Tomasz Lechowicz
 *  biblioteka:	boost::fusion
 *
 *  Przykłady zastosowania biblioteki boost::fusion
 */

#include <iostream>
#include <string>
#include <boost/fusion/algorithm.hpp>
#include <boost/fusion/view.hpp>
#include <boost/fusion/container.hpp>
#include <boost/fusion/mpl.hpp>
#include <boost/fusion/sequence.hpp>
#include <boost/fusion/iterator.hpp>

using namespace boost::fusion;

/******************** Definicje pomocniczych struktur	************************************/

struct Str_int{ 
	Str_int(int i): int_(i){};
	int int_; 
	friend std::ostream& operator<<(std::ostream& o, Str_int const& sti);
};

std::ostream& operator<<(std::ostream& o, Str_int const& sti){
	o << "Str_int: " << sti.int_;
	return o;
} 

struct Str_string{ 
	Str_string (std::string s): string_(s){};
	std::string string_; 
	friend std::ostream& operator<<(std::ostream& o, Str_string const& sts);
};

std::ostream& operator<<(std::ostream& o, Str_string const& sts){
	o << "Str_string: " << sts.string_;
	return o;
}

/******************** Definicje struktur funkcji	****************************************
 * Poniżej znajdują się definicje struktur emulujacych funkcję. Takie konstrukcje          *
 * wykorzystuje się w przypadku jeśli chcemy wywolac daną funkcję ukrytą w operatorze()    *
 * dla każdego elementu kontenera. Dzięki takiej konstrukcji możemy w łatwy sposób         *
 * zapisywać funkcje jako elementy kontenera.                                              *
 *******************************************************************************************/

struct increment{
	template<typename T>
    void operator()(T& t) const{
        ++t;
    }
};

struct decrement{
	template<typename T>
    void operator()(T& t) const{
        --t;
    }
};

struct display {
	template<typename T>
	void operator()(T const& t) const{
		std::cout << t << std::endl;
	}
};

/******************** Definicje funkcji	****************************************************/

//Przykład funkcji modyfikującej wszystkie elementy sekwencji po kolei poprzez wywoływanie dla nich zadanej funkcji
template <typename T>
void display_all(T const& t){
	
	//Sposób na wywołanie funkcji ukrytej w operatorze display() dla każdego elementu kontenera
	for_each(t, display());
}

/******************** Definicje typów wektorów	********************************************/

//Definicje typów zwykłych wektorów przechowujących dane
typedef vector<std::string, int, int, int, long, long> vector_example;
typedef vector<std::string, long, long> vector_example_no_int;
typedef vector<std::string, std::string, std::string, std::string, std::string, std::string> vector_label;

//Definicja typu vectora wykorzystywanego w funcji 'zip'
typedef vector<vector_label&, vector_example&> vector_zip;

/******************** Definicje typów map	************************************************/

//Definicja typu mapy przechowującej elementy różnego typu
typedef map<
	pair<int, int>,
	pair<double, double>,
	pair<Str_int, Str_int>,
	pair<Str_string, Str_string>
> map_example;

//Definicje typów mapy przechowujacych struktury emulujące funkcjes
typedef map<
	pair<int, increment>
> map_function_inc;

typedef map<
	pair<int, decrement>
> map_function_dec;

/******************** Klasa filtrująca	****************************************************
 * Klasa prezentująca różne możliwości filtrowania sekwencji biblioteki fusion             *
 *******************************************************************************************/

class Filter{
public:
	//Filtrowanie podanej sekwencji w poszukiwaniu składowych typu int i wykonywanie na nich 
	// zadanej operacji
	template <typename T>
	void filter_int(T& vec){
		//Wykorzystany tutaj typ widoku ('view') nie tworzy kopi podanej sekwencji 'vec'
		// ale leniwe proxy, wskazujące jedynie na część podanej sekwencji (w tym przypadku na
		// wszystkie jego składowe typu int). W przypadku modyfikacji elementów składowych 
		// widoku w rzeczywistości modyfikowane są elementy oryginalnej sekwencji
		filter_view<T, boost::is_same<boost::mpl::_1, int> > view_vec_(vec);
		for_each(view_vec_, increment());
	}

	//Filtrowanie podanej sekwencji w poszukiwaniu składowych typu long i wykonywanie na nich 
	// zadanej operacji
	template <typename T>
	void filter_long(T& vec){
		filter_view<T, boost::is_same<boost::mpl::_1, long> > view_vec_(vec);
		for_each(view_vec_, decrement());
	}

	//Sposób odfiltrowania z podanej sekwencji ustalonego wcześniej zakresu (w tym przypadku 
	// zakres wynosi <drugi element, ostatni element>) i wykonania na nim pewnej operacji
	template <typename T>
	void filter_subrange(T& vec){
		typedef result_of::begin<T>::type Range_first;
		typedef result_of::next<Range_first>::type Range_second;
		typedef result_of::end<T>::type Range_last;

		Range_second range_beg(vec);
		Range_last range_end(vec);

		iterator_range<Range_second, Range_last> range(range_beg, range_end);
		for_each(range, increment());
	}
	
	template <typename T0, typename T1>
	void filter_map(T0& map, T1& i){
		at_key<T1>(map)(i);
	}
};

int main(){
	Filter filtr;
	//Sposób tworzenia elementu boost::fusion::vector
	vector_example vec("Przykladowy wektor 6-o elementowy", 1, 2, 3, 10000, 20000);
	
	//Boost::fusion::vector ma zaimplementowany domyślny operator wypisywania do strumienia
	std::cout << vec << std::endl << std::endl;
	
	//Przykład modyfikacji wybranej części sekwencji. Metoda filter_int wybiera z sekwencji wszystkie
	// składowe typu int i inkrementuje je.
	filtr.filter_int(vec);
	std::cout << "Filtracja skladowych typu int:" << std::endl << vec << std::endl << std::endl;
	
	//Podobny przykład tylko tym razem składowe typu long poddawane są dekrementacji
	filtr.filter_long(vec);
	std::cout << "Filtracja skladowych typu long:" << std::endl << vec << std::endl << std::endl;
	
	//Przykład filtracji ustalonego wcześniej zakresu sekwencji
	filtr.filter_subrange(vec);
	std::cout << "Filtracja okreslonego zakresu:" << std::endl << vec << std::endl << std::endl;

	vector_label vec2("Nazwa wektora:", "Pierwsza skladowa int:", "Druga skladowa int:", "Trzecia skladowa int:",
		"Pierwsza skladowa long:", "Druga skladowa long:");
	
	//Przykład łączenia dwóch wektorów ze sobą. W wyniku połączenia wektora (1,2,3) i (a,b,c) otrzymujemy wektor(1a,2b,3c).
	// Poniżej wykorzystano to do dodawania etykiet do składowych.
	zip_view<vector_zip> vector_ziped(vector_zip(vec2, vec));
	
	//Domyślnym sposobem wyświetlania sekwencji wektora z biblioteki fusion jest podanie jego składowych ograniczonych
	// nawiasami oraz rozdzielonych spacjami. Poniżej przykład jak w łatwy sposób można zmienić te ustawienia dal wszystkich
	// wywołać operatora<<. Od tego momentu wszystkie wektory będą ograniczone nawiasami kwadratowymi i oddzielone przecinkami
	std::cout << tuple_open('[') << tuple_close(']') << tuple_delimiter(", ") << std::endl;
	
	std::cout << "Łączenie dwóch wektorów funkcją zip:" << std::endl;
	for_each(vector_ziped, display());

	//Większość funkcji biblioteki boost::fusion nie zmienia podanej sekwencji ale zwraca widok na starą 
	// sekwencję. Widok ten zawiera informację jak miałaby wyglądać sekwencja po zmianach. Dlatego w większości 
	// jako argumenty przyjmują one stałe referencje do sekwencji. Za przykład służy tutaj funkcja remove, 
	// która wycina z sekwencji wszystkie elementy danego typu. 
	// Widać, że bazowy wektor nie uległ zmianie po wykonaniu na nim tej funkcji
	vector_example_no_int vec3(remove<int>(vec));
	std::cout << std::endl << "Przyklad dzialania na widokach:" << std::endl;
	std::cout << "Wektor bazowy " << vec << std::endl;
	std::cout << "Wektor utworzony z wektora bazowego " << vec3 << std::endl;
	
	std::cout << tuple_open('(') << tuple_close(')') << tuple_delimiter(" ") << std::endl;
	
	//Tworzenie elementu boost::fusion::map
	map_example map(
		make_pair<int>(1), 
		make_pair<double>(3.14),
		make_pair<Str_int>(Str_int(2)),
		make_pair<Str_string>(Str_string("ZPR"))
	);

	//Odwoływanie się do konkretnego elementu mapy
	std::cout << "Sposob odwolania sie do konkretnego elementu mapy:" << std::endl;
	std::cout << "at_key<typ>(mapa)	=>	" << at_key<Str_int>(map) << std::endl << std::endl;
	
	//Wypisywanie całej mapy na ekran poprzez wywołanie odpowiedniej funkcji dla każdego elementu sekwencji
	std::cout << "Wypisywanie elementow mapy:" << std::endl;
	display_all<map_example>(map);

	//Tworzenie map zawierających jako elementy funkcje
	map_function_inc mapf_inc(make_pair<int>(increment()));
	map_function_dec mapf_dec(make_pair<int>(decrement()));
	int i=1;
	
	//Przykład na wykorzystanie map do dynamicznej zmiany sposobu obsługi zmiennych. 
	// W pierwszym przypadku obsługujemy zmienne typu int za pomocą mapy
	// posiadającej pod kluczem typu int wywołanie funkcji increment. 
	filtr.filter_map<map_function_inc, int>(mapf_inc, i);
	std::cout << "Element po wywolaniu na nim funkcji z mapy pierwszej:" << i << std::endl;

	//Teraz możemy zmienić typ mapy obsługującej elementy typu int na mapę z funkcją decrement pod kluczem int.
	filtr.filter_map<map_function_dec, int>(mapf_dec, i);
	std::cout << "Element po wywolaniu na nim funkcji z mapy drugiej:" << i << std::endl;
	
	return 0;
}
