// 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>

using namespace std;

static const string NAME = "Abacki";

//wyścig (2 muteksy synchronizują ten sam token_, więc nie ma sekcji krytycznej
// zakleszczenie

const int NUM = 10;

boost::mutex cout_mutex;

class Process {
public:
    Process(Process* in = nullptr, int token = 0) : in_(in), token_(token), counter_(0) {
    }

    void setIn(Process* p) { in_ = p; }

    int get() {
        if(token_ > 0) {
            {
                boost::mutex::scoped_lock lock(cout_mutex);
                cout << "id:" << " get:" << token_ << " counter:" << counter_ << endl;
            }
            ++counter_;
        }
        int out = token_;
        token_ = 0;
        return out;
    }

    void operator()() {
        for(;;) {
            //boost::this_thread::sleep_for( boost::chrono::milliseconds(100) );
            if(in_) {
                boost::mutex::scoped_lock lock1(inMutex_);
                boost::mutex::scoped_lock lock2(outMutex_);
                int x = in_->get();
                if( x > 0) {
                    {
                        boost::mutex::scoped_lock lock(cout_mutex);
                        cout << "x:" << x << endl;
                    }
                    token_ = x + 1;
                    if( counter_ > NUM ) {
                        break;
                    }
                }
            }
        }
    }
private:
    Process* in_;
    int token_;
    int counter_;
    boost::mutex inMutex_;
    boost::mutex outMutex_;
};

class ProcessBetter {
public:
    ProcessBetter(int id, ProcessBetter* in = nullptr, int token = 0) : id_(id), in_(in), token_(token), counter_(0) { }

    void setIn(ProcessBetter* p) { in_ = p; }

    int get() {
        boost::mutex::scoped_lock lock(mutex_);
        if(token_ > 0) {
            {
                boost::mutex::scoped_lock lock(cout_mutex);
                cout << "id:" << id_ << " get:" << token_ << " counter:" << counter_ << endl;
            }
            ++counter_;
        }
        int out = token_;
        token_ = 0;
        return out;
    }

    void operator()() {
        for(;;) {
            //boost::this_thread::sleep_for( boost::chrono::milliseconds(100) );
            if(in_) {
                int x = in_->get();
                if( x > 0) {
                    boost::mutex::scoped_lock lock(mutex_);
                    {
                        boost::mutex::scoped_lock lock(cout_mutex);
                        cout << "x:" << x << endl;
                    }
                    token_ = x + 1;
                    if( counter_ > NUM ) {
                        break;
                    }
                }
            }
        }
    }
private:
    int id_;
    ProcessBetter* in_;
    int token_;
    int counter_;
    boost::mutex mutex_;
};



void zad1() {
    // Process p1;
    // Process p2(&p1, 1);
    // p1.setIn(&p2);

    ProcessBetter p1(1);
    ProcessBetter p2(2, &p1, 1);
    p1.setIn(&p2);

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

// //--------ZAD2--------------------------------------------------------------------------

template<typename T> void replace_value(T& c, const typename T::value_type& from, const typename T::value_type& to) {
    for_each(c.begin(), c.end(), [&](typename T::value_type& v) {
            if(v == from)
                v = to;
        });
}

template<typename T> void replace_value2(T& c, const typename T::value_type& from, const typename T::value_type& to) {
    for( typename T::value_type& v : c) {
        if( v == from)
            v = to;
    }
}


template<typename T> void replace_value3(T& c, const typename T::value_type& from, const typename T::value_type& to) {
    replace(c.begin(), c.end(), from, to );
}


void zad2() {
    vector<int> v{1,2,3,4};
    replace_value(v,1,-1);
    replace_value2(v,2,-2);
    replace_value3(v,3,-3);
    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " ") );
    cout << endl;
}


// //--------ZAD3--------------------------------------------------------------------------
// //wydruk (kod)

class Vis : public boost::static_visitor<void> {
public:
    Vis() {}
    void operator()(const int& i){ str_ += "i"; }
    void operator()(const std::string& s) { str_ += "s"; }
    string str_;
};

void zad3() {
    typedef boost::variant<int,string> V;
    std::vector<V> v;
    v.push_back(NAME);
    v.push_back(NAME.size());
    v.push_back(v.front() );
    v.push_back(v.back() );

    Vis vis;
    for_each(v.begin(), v.end(), [&](const V& v){apply_visitor(vis, v);});
    cout << vis.str_ << endl;
}

//--------ZAD4--------------------------------------------------------------------------
//zadanie: ustawić wektor, który jest właścicielem elementów (zestaw G)
//
//rozwiązanie : wykorzystać bind
//

class Element;
typedef shared_ptr<Element> PElement;
typedef vector<PElement> VecElement;

class Element {
public:
    Element() : parent_(nullptr) {}

    const VecElement* getParent() const { return parent_; }

    void setParent(const VecElement* v) { parent_ = v; }
private:
    const VecElement* parent_;
};

void setParent(VecElement& vec) {
    for(PElement e : vec ) {
        e->setParent( &vec );
    }
}

void setParent2(VecElement& vec) {
    for_each( vec.begin(), vec.end(), [&vec](PElement e){ e->setParent(&vec); } );
}

void setParent3(VecElement& vec) {
    for_each( vec.begin(), vec.end(), bind(&Element::setParent, _1, &vec) );
}


void zad4() {
    vector<PElement> v;
    v.push_back( PElement(new Element()));
    v.push_back( PElement(new Element()));

    setParent( v );
    assert( v.size() == 2 );
    assert( v[0]->getParent() == &v );
    assert( v[1]->getParent() == &v );
}

// //--------ZAD5--------------------------------------------------------------------------
// // // podaj wydruk

int sum(int& s, int a) {
    s += 2*a + 1;
    return s;
}

void zad5() {
    vector<int> v;
    v.push_back(1);
    v.push_back(NAME.size());
    int s = 0;
    for_each( v.begin(), v.end(), boost::bind(sum, ref(s), boost::bind(sum, ref(s), _1) ) );
    cout << s << endl;
}

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

