#include <memory>
#include <string>
#include <vector>

#include <iostream>
#include <algorithm>
#include <tuple>
#include <thread>
#include <mutex>

#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>

using namespace std;


struct Person : public tuple<string,int,int> {
	Person(const string& n, int a, int s) : tuple<string,int,int>(n,a,s) {}

	const string& getName() const { return get<0>(*this); }
	int getAge() const { return get<1>(*this); }
	int getSalary() const { return get<2>(*this); }
};

using Persons = vector<Person>;

string NAME = "Abacki";

//--------ZAD1 (4pkt)--------------------------------------------------------------------------
//
// napisz funkcje: cretePersons, getSalaries oraz operator do wyswietlania Persons.
// uzyj algorytmow, tam, gdzie jest to uzasadnione

// createPersons - tworzy kolekcje krotek; liczba krotek to minimum z welkosci 3 kontenerow (argumentow funkcji)
//
Persons createPersons(const vector<string>& names, const vector<int>& ages, const vector<int>& salaries) {
	vector<Person> out;
	size_t size = min( min(names.size(), ages.size() ), salaries.size() );
	out.reserve(size);

	for(int i = 0; i < size; ++i ) {
		out.push_back( Person(names[i], ages[i], salaries[i]) );
	}
	return out;
};

// getSalaries - tworzy kolekcje reprezentujata pensje na podstawie kolekcji krotek
//
vector<int> getSalaries(const vector<Person>& persons) {
	vector<int> out(persons.size());
	transform(persons.begin(), persons.end(), out.begin(), [](const Person& p){ return p.getSalary(); });
	return out;
}

// pisze na strumien kolekcje krotek w postaci nazwa przecinek spacja wiek przecinek spacja pensja srednik
// np: Abacki, 20, 1500; Babacki, 19, 1600;
ostream& operator<<(ostream& os, const vector<Person>& p) {
	for_each( p.begin(), p.end(), [&](const Person& p){ os << p.getName() << ", " << p.getAge() << ", " << p.getSalary() << ";";});
	return os;
}


void zad1() {
	vector<string> names = { "Abacki", "Babacki", "Cabacki" };
	vector<int> ages = { 20, 19, 22, 22};
	vector<int> salaries = { 1500, 1600, 1700 };

	vector<Person> persons = createPersons(names, ages, salaries );

	cout << persons << endl;

	assert( salaries == getSalaries(persons) );
}


//----------Pytanie 1 (1 pkt)-------------------------------------------------------
// Zakladamy poprawna implementacje funkcji createPersons i getSalaries.
// Czy dla wszystkich wartosci 'salaries' asercja w 'zad1' jest prawdziwa?
// Jezeli nie, to daj przyklad.

//Odpowiedz: Nie. Asercja nie jest prawdziwa, gdy ilosc elementow w kolekcji 'salaries' jest wieksza od ilosci elementow w kolekcji 'persons'.


// //--------ZAD2 (5pkt)-----------------------------------------------------------------------
// Liczba w kodzie trójkowym jest reprezentowana przez tablice Triple
// zakladamy, ze tablica bedzie maksymalnie 20 cyfrowa
// wtedy konwersja na liczbe naturalna moze byc realizowana wzorem:
// int x = 3^19*a[19] + 3^18*a[18] + ... + 3*a[1] + a[0]
// 3^20 = 3486784401, da sie zapisac na na 'int'

template<size_t N> using Triple = array<int, N>;
static const int TRIPLE_SIZE = 20;

//dostarcz funkcje konwertujaca taki zapis trojkowy na wartosc calkowita
//maksymalnie punktowane rozwiazanie wykorzystuje metaprogramowanie, aby konwersja byla szybka podczas dzialania aplikacji

template<size_t N, size_t I>
struct X {
	static const int WEIGHT = 3 * X<N, I-1>::WEIGHT;
	static int toint(const Triple<N>& a) { return a[I]*WEIGHT + X<N, I-1>::toint(a); }
};

template<size_t N>
struct X<N, 0> {
	static const int WEIGHT = 1;
	static int toint(const Triple<N>& a) { return a[0]; }
};

template<size_t N>
int triple2int(const Triple<N>& a) {
	static_assert( N < TRIPLE_SIZE );
	return X<N, N-1>::toint(a);
}


void zad2() {
	Triple<1> t1 = {1}; //1
	Triple<3> t2 = {0,1,0}; //3
	Triple<3> t3 = {0,0,2}; //18; pamietaj, ze przy pisaniu zawartosci tablicy najpierw jest a[0], pozniej a[1]
	Triple<4> t4 = {2,0,0,1}; //29

	cout << triple2int(t1) << endl;
	cout << triple2int(t2) << endl;
	cout << triple2int(t3) << endl;
	cout << triple2int(t4) << endl;
}


//--------ZAD3 (5pkt)--------------------------------------------------------------------------
// Popraw kod, aby zlikwidowac wyscig
// Zaznacz zmienione linie (wstaw komentarz //zmiana)

class Singleton {
public:
    static Singleton& getInstance() {
        if(!instance_) {
			lock_guard<mutex> guard(mutex_);
            if(!instance_) {
                instance_ = new Singleton();
            }
        }
        return *instance_;
    }
    void message(const string& msg) {
		lock_guard<mutex> guard(mutex_);  //zmiana - sekcja krytyczna
        ++counter_;
        cout << counter_ << ' ' << msg << endl;
    }
private:
    Singleton() : counter_(0) { }
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
private:
    static mutex mutex_;
    static Singleton* instance_;
    int counter_;
};

Singleton* Singleton::instance_ = nullptr;
mutex Singleton::mutex_;

void thrd_fun(const string& msg) {
	static const int NUM = 30; //how many trays
	for(int i=0;i<NUM;++i)
        Singleton::getInstance().message(msg);
}

void zad3() {
    std::thread thrd1( bind(&thrd_fun, "A") );
    std::thread thrd2( bind(&thrd_fun, "B") );
    std::thread thrd3( bind(&thrd_fun, "C") );


    thrd1.join();
    thrd2.join();
	thrd3.join();
}

//--------ZAD4 5pkt--------------------------------------------------------------------------
// zmodyfikuj w taki sposób topologię grafu, aby wydrukować liczbę liter swojego nazwiska
// wskazówka - zmodyfikuj jedynie linie w których dostarczamy wektor krawędzi.
// wierzchołki mają identyfikatory całkowite od 0 do 19.

void zad4() {
    using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS>;
    using Edge = pair<int, int>;
    const vector<Edge> EDGES = { Edge(0,1), Edge(1,2), Edge(2,3), Edge(3,4), Edge(4,5), Edge(5,6) };   //zmiana - dla nazwiska 'Abacki'
    Graph g(EDGES.begin(), EDGES.end(), 20 );               //liczba wierzchołków = 20

	int sum = 0;
	Graph::vertex_descriptor v = *vertices(g).first;
	while( out_degree(v, g) ) { //if has out edge
		++sum;
		Graph::edge_descriptor e = *out_edges(v, g).first;
		v = target(e, g);
	}
    cout << sum << endl;
}

//--------ZAD5 3pkt--------------------------------------------------------------------------
// Co zwraca funkcja 'funkcja' pokazana nizej jezeli kolekcja 'words' nie jest pusta ?
// Dla pustej kolekcji funkcja zwraca 0
// Przyklad uzycia oraz poprawne wartosci w 'zad5'


// Odpowiedz: 'funkcja' zwraca ilosc slow o najwiekszej liczbie liter (z kolekcji 'words')
int funkcja(const std::vector<string>& words) {
    using Hist = map<size_t,size_t>;
    using Pair = Hist::value_type;
    Hist hist;
    for_each(words.begin(), words.end(), [&](const string& s){
											 Hist::iterator it = hist.find(s.size());
											 if( it != hist.end() )
												 ++(it->second);
											 else
												 hist.insert( make_pair(s.size(), 1 ) );
										 });
	if(! hist.empty() )
		return hist.rbegin()->second;
	else
		return 0;
}

void zad5() {
    cout << funkcja(vector<string>{"ALA", "A", "O", "ELA"}) << endl; //zwraca 2
    cout << funkcja(vector<string>{"ALA", "AL", "A", "L"}) << endl;  //zwraca 1
    cout << funkcja(vector<string>{}) << endl; //zwraca 0
}

// Pytanie 2 (1 pkt)
//Ile godzin w poświęciłeś na przedmiot ZPR (podaj jedną liczbę, która obejmuje obecność na wykładzie, naukę własną i projekt)


// SUMA =
// WYKLAD + NAUKA-WLASNA + KOLOKWIUM =
// PROJEKT =


// Pytanie 3 (1 pkt)
//Zaproponuj zagadnienie, które Twoim zdaniem warto byłoby omówić na przedmiocie ZPR



int main() {
    cout << "zad1: " << endl; zad1(); cout << endl;
    cout << "zad2: " << endl; zad2(); cout << endl;
    cout << "zad3: " << endl; zad3(); cout << endl;
    cout << "zad4: " << endl; zad4(); cout << endl;
    cout << "zad5: " << endl; zad5(); cout << endl;
    return 0;
}
