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

#include <iostream>
#include <iomanip>

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

#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/lexical_cast.hpp>
#include <boost/type_traits.hpp>

#include <boost/optional.hpp>
#include <boost/lexical_cast.hpp>

using namespace std;

string NAME = "Abacki";

//--------ZAD1--------------------------------------------------------------------------
//obiekty Reader rejestrują się w losowej kolejności w obiekcie Writer

class Writer;

class Reader {
public:
    Reader(int id, const vector<Writer*>& v) : id_(id), writers_(v), count_(0) {}

    void call() {
        count_ = count_ + 1;
        cout << "reader " << id_ << " count = " << count_ << endl;
    }

    boost::mutex m;
    void operator()();
    int id_;
private:
    vector<Writer*> writers_;
    int count_;
};

class Writer {
public:
    void addReader(Reader* r) { readers_.push_back(r); }
    void operator()() {
        for(int i=0; i< NUM_SEND; ++i) {
            send();
        }
    }
private:
    void send() {
        // cout << "kolejnosc readers: ";
        // for(Reader* r : readers_) {
        //  cout << r->id_;
        // }
        for(Reader* r : readers_) {
            r->m.lock();
        }
        for(Reader* r : readers_ ) {
            r->call();
        }
        for(Reader* r : readers_) {
            r->m.unlock();
        }
    }
    // void send() {
    //  for(Reader* r : readers_) {
    //      r->m.lock();
    //      r->call();
    //      r->m.unlock();
    //  }
    // }
private:
    static const int NUM_SEND = 10000;
    vector<Reader*> readers_;
};

void Reader::operator()() {
    for(Writer* w : writers_) {
        w->addReader(this);
        if(id_==1)
            boost::this_thread::sleep_for( boost::chrono::milliseconds(100) );
    }
    //konczy watek
}


void zad1() {
    Writer w1, w2;
    vector<Writer*> v;
    v.push_back(&w1);
    v.push_back(&w2);

    Reader r1(1, v), r2(2, v);

    boost::thread thrd1( ref(r1) );
    boost::thread thrd2( ref(r2) );
    thrd1.join();
    thrd2.join();

    boost::thread thrd3( ref(w1) );
    boost::thread thrd4( ref(w2) );
    thrd3.join();
    thrd4.join();
}

// //--------ZAD2--------------------------------------------------------------------------
// static const bool is_specialized;        (until C++11)
// static constexpr bool is_specialized;    (since C++11)

// The value of std::numeric_limits<T>::is_specialized is true for all T for which there exists a specialization of std::numeric_limits. 


template<typename T>
T max_element(const T* begin, const T* end) {
    static_assert( std::numeric_limits<T>::is_specialized, "type not supported by numeric_limits" );
    T m = std::numeric_limits<T>::min();
    for(;begin!=end;++begin) {
        if(m < *begin) {
            m = *begin;
        }
    }
    return m;
}


void zad2() {
    const int TAB1[] = { 1, 2, 3, 4 };
    for(int i = 0; i < sizeof(TAB1)/sizeof(TAB1[0]); ++i) {
        cout << max_element(TAB1, TAB1 + i) << endl;
    }
    const double TAB2[] = { 3.0, 2.0, 1.0 };
    for(int i = 0; i < sizeof(TAB2)/sizeof(TAB2[0]); ++i) {
        cout << max_element(TAB2, TAB2 + i) << endl;
    }
}



// //--------ZAD3--------------------------------------------------------------------------
// podaj wydruk

typedef boost::variant<int,double,string> V;

int countNumLess(const std::vector<V>& v, int threshold) {

    class Vis : public boost::static_visitor<void> {
    public:
        Vis(int threshold) : threshold_(threshold), count_(0) {}
        void operator()(const int& i){ if(i < threshold_) ++count_; }
        void operator()(const double& d){ if(d < threshold_) ++count_; }
        void operator()(const std::string& s) { }
        int threshold_;
        int count_;
    } vis(threshold);
    for_each(v.begin(), v.end(), [&](const V& v){apply_visitor(vis, v);});
    return vis.count_;
}

void zad3() {
    std::vector<V> v;
    v.push_back(NAME);
    v.push_back(1);
    v.push_back(NAME.size() + 1.0);
    v.push_back(NAME.size()/2 + 1.0);
    cout << countNumLess(v, NAME.size() ) << endl;
}

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


//--------ZAD4--------------------------------------------------------------------------
//zadanie: daj nazwiska osób urodzonych w danym roku

class Person : public std::pair<string, int> {
public:
    Person(const string& name, int year) : pair<string,int>(name, year) {}
    const string& getName() const { return first; }
    int getYear() const { return second; }
};

vector<std::string> getNamesYear(const vector<Person>& v, int year) {
    vector<string> out;
    for_each( v.begin(), v.end(),
              [&](const Person& p) { if( p.getYear() == year) out.push_back(p.first); } );
    return out;
}

void zad4() {
    vector<Person> persons = { Person("Abacki", 1980), Person("Babacki", 1981),
                               Person("Cabacki", 1982), Person("Dabacki", 1981) };
    vector<string> names_even = getNamesYear(persons, 1981);
    copy(names_even.begin(), names_even.end(), ostream_iterator<string>(cout," ") );
}

// //--------ZAD5--------------------------------------------------------------------------

boost::optional<int> divInt(int a, int b) {
    boost::optional<int> ret;
    if(b) ret = a / b;
    return ret;
}

void printDivMatrix(const vector<int>& v) {
    for_each(v.begin(), v.end(),
             [&](int i){ for_each(v.begin(), v.end(),
                                  [=](int j) {
                                      boost::optional<int> d = divInt(i,j);
                                      if(d) cout << d.get();
                                      else cout << "-";
                                  });
                 cout << endl;
             });
}

void zad5() {
    vector<int> v = { 0, 1, 2, 3};
    printDivMatrix(v);
}

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

