// dodać pytanie o czas spędzony nad projektem

#include <iostream>
#include <iomanip>

#include <memory>
#include <queue>
#include <string>
#include <vector>
#include <complex>

#include <type_traits>
#include <algorithm>

#include <boost/variant.hpp>

#include <boost/ref.hpp>
#include <boost/chrono.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>

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


using namespace std;

string NAME = "Abacki";

//--------ZAD1(R)--------------------------------------------------------------------------
// brak skalowalności w aplikacji współbieżnej

// W systemie istnieje jeden obiekt typu Queue, do którego wstawia dane jeden wątek (kodu nie zamieszczono). Ilość danych w kolejce
// jest duża i rośnie. Aby temu zapobiec system przeniesiono na platformę wieloprocesorową  i zwiększono ilość wątków przetwarzających
// dane (wykonują one funcję calc_thrd). Niestety nie obserwuje się poprawy. Popraw kod funkcji calc_thrd.

class Data {}; //nie zamieszczono skladowych
typedef std::shared_ptr<Data> PData;

class Queue {
public:
    Queue() {}
    bool empty() {
        return q_.empty();
    }

    PData get() { //uchwyt do pierwszego elementu
        PData out;
        if(!q_.empty()) {
            out = q_.front();
            q_.pop();
        }
        return out;
    }
    void put(PData data) {
        q_.push(data);
    }

    mutable boost::mutex m_;
private:
    std::queue<PData> q_;
public:
};

void calculate(const Data& d) { //dlugotrwale obliczenia
    cout << "calc start" << endl;
    boost::this_thread::sleep_for( boost::chrono::milliseconds(1000) );
    cout << "calc end" << endl;
}

void calc_thrd(Queue& q) {
    while(!q.empty()) {
        boost::mutex::scoped_lock l(q.m_);
        if(!q.empty()) {
            calculate(*q.get());
        }
    }
}

void calc_thrd_better(Queue& q) {
    while(!q.empty()) {
        PData data;
        {
            boost::mutex::scoped_lock l(q.m_);
            data = q.get();
        }
        //obliczenia poza sekcja krytyczna
        if(data)
            calculate(*data);
    }
}

void zad1() {
     Queue q;
     //pominięto kod wątków, które wstawiają obiekty PData
     {
            boost::mutex::scoped_lock l(q.m_);
            q.put(PData(new Data()));
            q.put(PData(new Data()));
            q.put(PData(new Data()));
            q.put(PData(new Data()));
            q.put(PData(new Data()));
     }
     //tutaj wątki, które pobierają i przetwarzają obiekty PData
        boost::thread_group thrds;
        for (int i=0; i < 5; ++i)
            thrds.create_thread(boost::bind(&calc_thrd, boost::ref(q)));

        thrds.join_all();
}

//--------ZAD2(L)--------------------------------------------------------------------------
//iloczyn kartezjanski dwoch wektorow liczb
//badać, czy jest liczbą


template<typename T, size_t N, size_t i = N-1>
struct X {
    static T dot(const std::array<T,N>& a, const std::array<T,N>& b) {
        return a[i] * b[i] + X<T, N, i-1>::dot(a, b);
    }
};

template<class T, size_t N>
struct X<T, N, 0> {
    static T dot(const std::array<T,N>& a, const std::array<T,N>& b) {
        return a[0] * b[0];
    }
};

template<class T, size_t N> T dot(const std::array<T,N>& a, const std::array<T,N>& b) {
   static_assert(N > 0);
   static_assert(is_arithmetic<T>());
   return X<T,N>::dot(a,b);
}


void zad2() {
    std::array<int, 4> A = { 1, 1, 1, 1 };
    std::array<int, 4> B = { 1, 2, 3, 4 };

    cout << dot(A, B) << endl;
}


//--------ZAD3--------------------------------------------------------------------------
//zadanie: podaj wydruk

template <unsigned n> int fun(int k) {
    return n + fun<n-1>(k+1);
}

template <> int fun<0>(int k) {
    return k;
}

void zad3() {
    cout << fun<1>(NAME.size()) << " " << fun<2>(NAME.size()) << " " << fun<5>(NAME.size())  << flush;
}

void zad3x() {
    cout << endl;
    for(int i=3;i<15;++i) {
        NAME = std::string(i, 'x');
        cout << "name: " << NAME << ":";
        zad3();
        cout << endl;
    }
}


//--------ZAD4--------------------------------------------------------------------------
// BGL - podaj wydruk
void zad4() {
    using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS>;
    using Edge = pair<int, int>;
	enum { A, B, C, D, E, F, G, NUM_VERTICES };
    const vector<Edge> EDGES =
        { Edge(A,B), Edge(A,C),
          Edge(A,D), Edge(A,E),
          Edge(E,F), Edge(F,G) };
    Graph g(EDGES.begin(), EDGES.end(), NUM_VERTICES);
    for_each( vertices(g).first, vertices(g).second,
              [&](Graph::vertex_descriptor v){
                  for_each(out_edges(v, g).first, out_edges(v, g).second,
                           [&](Graph::edge_descriptor e){ cout << target(e, g) << ' ';});
              });
    cout << endl;
}

//--------ZAD5--------------------------------------------------------------------------
// STL

int max_letter(const std::vector<string>& words) {
    using Map = map<char,int>;
    using Pair = Map::value_type;
    map<char,int> histogram;
    int max = 0;
    for_each(words.begin(), words.end(),
             [&](string s){ for_each(s.begin(), s.end(),
                                     [&](char c){
                                         ++histogram[c];
                                         if(histogram[c] > max)
                                             max = histogram[c];
                                     });});
    return max;
}

void zad5() {
    cout << max_letter(vector<string>{"ALA", "ELA", "OLA", "ALA"}) << endl;
    cout << max_letter(vector<string>{"ALA", "ELA", "ALA", "ALA"}) << endl;
    cout << max_letter(vector<string>{}) << endl;
}

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