// 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)--------------------------------------------------------------------------
// blokada dla 3 obiektow, in/out

const int NUM = 100;

class Calc {
public:
    Calc(int idx) : idx_(idx), prev_(nullptr) {
        out_.push_back(0);
        out_.push_back(0);
    }

    void setPrev(Calc* p) { prev_ = p; }

    int get() {
        int ret = -1;
        boost::mutex::scoped_lock lock(m_);
         if(!out_.empty() ) {
             ret = out_.front();
             out_.pop_front();
         }
         return ret;
    }
    void operator()() {
        for(int i=0;i<NUM;++i) {
            //boost::mutex::scoped_lock lock(m_);
            if(prev_) {
                boost::this_thread::sleep_for( boost::chrono::milliseconds(10));
                int x = prev_->get();
                if( x >= 0) {
                    cout << "idx:" << idx_ << " X:" << x << endl;
                    boost::mutex::scoped_lock lock(m_);
                    out_.push_back(x + 1);
                }
            }
        }
    }
private:
    int idx_;
    Calc* prev_;
    list<int> out_;
    boost::mutex m_;
};

void zad1() {
    Calc p1(1), p2(2), p3(3);
    p1.setPrev(&p3);
    p2.setPrev(&p1);
    p3.setPrev(&p2);

    boost::thread thrd1( ref(p1) );
    boost::thread thrd2( ref(p2) );
    boost::thread thrd3( ref(p3) );
    thrd1.join();
    thrd2.join();
    thrd3.join();

    cout << "thrd1: " << p1.get() << endl;
    cout << "thrd2: " << p2.get() << endl;
    cout << "thrd3: " << p3.get() << endl;
}


//--------ZAD2(L)--------------------------------------------------------------------------
template<typename T> void copy_impl(const T* first, const T* last, T* result, boost::true_type) {
    cout << "memcpy " << flush;
    memcpy(result, first, (last - first)*sizeof(T) );
}

template<typename T> void copy_impl(const T* first, const T* last, T* result, boost::false_type) {
    cout << "copy " << flush;
    copy( first, last, result );
}

template<class T> void fast_copy(const T* first, const T* last, T* result) {
    copy_impl(first, last, result, boost::has_trivial_assign<T>() );
}

class Foo1 {
public:
    Foo1() : i_(0) {}
private:
    int i_;
};
class Foo2 {
public:
    Foo2() : i_(0) {}
    Foo2& operator=(const Foo2& z) { i_ = z.i_; return *this; }
private:
    int i_;
};

void zad2() {
    static const int SIZE = 10;
    Foo1 sa[SIZE], da[SIZE];
    Foo2 sb[SIZE], db[SIZE];
    fast_copy(sa, sa + SIZE, da );
    fast_copy(sb, sb + SIZE, db );
}


//--------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>;
    static const int NUM_VERTICES = 5;

    const Edge EDGES[] =             // krawedzie dla grafu
        { Edge(0,1), Edge(1,2),
          Edge(1,3), Edge(2,3),
          Edge(3,4) };
    const int NUM_EDGES = sizeof(EDGES)/sizeof(EDGES[0]);

    Graph g(EDGES, EDGES + NUM_EDGES, NUM_VERTICES);

    int m = 0;
    for_each( vertices(g).first, vertices(g).second,
              [&](int idx){
                  int num_edges = out_edges(idx, g).second - out_edges(idx, g).first;
                  if(num_edges > m)
                      m = num_edges;
              });
    cout << m << endl;
}

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

int count_common(const std::vector<string>& words) {
    using Map = map<string,int>;
    using Pair = Map::value_type;
    map<string,int> histogram;
    transform(words.begin(), words.end(), inserter(histogram, histogram.begin()), [](string s){ return make_pair(s,0); });
    int max_occur = 0;
    for_each(words.begin(), words.end(),
             [&](string s){
                 ++histogram[s];
                 if(histogram[s] > max_occur)
                     max_occur = histogram[s];
             });
    return max_occur;
}

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

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;
}
