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

static const string NAME = "Abacki";



//--------ZAD1--------------------------------------------------------------------------
//END_ID wstawiany jeden raz i nie wszystkie wątki się kończą

static const int NUM_CONSUMER_THREADS = 2;

class MsgQueue {
public:
    static const int END_ID = 0;
    boost::optional<int> get() {
        boost::mutex::scoped_lock scoped(m_);
        boost::optional<int> out;
        if( ! q_.empty() ) {
            out = q_.front();
            q_.pop();
        }
        return out;
    }
    void put(int i) {
        boost::mutex::scoped_lock scoped(m_);
        q_.push(i);
    }
private:
    boost::mutex m_;
    queue<int> q_;
};

class Producer {
public:
    Producer(MsgQueue& q, const vector<int>& numbers) : q_(q), tasks_(numbers) {}

    void operator()() {
        for_each( tasks_.begin(), tasks_.end(), [&](int i){ q_.put(i); } );
		q_.put(MsgQueue::END_ID);
    }
private:
    MsgQueue& q_;
    vector<int> tasks_;
};

class Consumer {
public:
    Consumer(MsgQueue& q) : q_(q), finish_(false) {}

    void operator()() {
        while(!finish_) {
            boost::optional<int> task = q_.get();
            if( ! task) {
                boost::this_thread::sleep(boost::posix_time::millisec(1000));
            }
            else if( *task == MsgQueue::END_ID) {
                finish_ = true;
				q_.put(MsgQueue::END_ID);
            }
            else {
                cout << "processing task " << *task << endl;
            }
        }
    }
private:
    MsgQueue& q_;
    bool finish_;
};

void zad1() {
    MsgQueue queue;
    Consumer c1(queue);
    Consumer c2(queue);
    vector<int> tasks = { 1, 2, 3, 4, 5, 6 };
    Producer p(queue, tasks);


    boost::thread thrd1( boost::ref(c1) );
    boost::thread thrd2( boost::ref(c2) );
    boost::thread thrd3( boost::ref(p) );
    thrd1.join();
    thrd2.join();
    thrd3.join();
    cout << endl;
}

// //--------ZAD2--------------------------------------------------------------------------
// napisać szablon, który obsługuje kolekcję w specjalnym obszarze pamieci, zwalania obiekt, gdy jest trivial_destructor

//If a type has a trivial destructor then the destructor has no effect: calls to the destructor can be safely omitted.
//   Jeżeli można pominąć wołanie destruktora dla \sKod{T}
//   to
//   \sKod{has\_trivial\_destructor<T>::type} jest typu \sKod{true\_type},
//   \sKod{has\_trivial\_destructor<T>::value} jest typu \sKod{bool} i ma wartość \sKod{true},
//   w przeciwnym wypadku
//   \sKod{has\_trivial\_destructor<T>::type} jest typu \sKod{false\_type},
//   \sKod{has\_trivial\_destructor<T>::value} ma wartość \sKod{false}.
// Podobną rolę ma szablon std::is_trivially_destructible.

template<typename T> class Vector {
public:
    typedef unsigned char BUF_ELEMENT;
    static const int BUFFER_SIZE = 100;
    Vector() : buffer_(new BUF_ELEMENT[BUFFER_SIZE]), size_(0) { }

    void add(const T& t) {
        new(getRawPointer(size_)) T(t);
        ++size_;
    }
    void print() {
        for(int i=0;i<size_;++i) {
            cout << i << ":" << *getPointer(i) << ", ";
        }
        cout << endl;
    }
    void printBuf() {
        for(int i=0;i<BUFFER_SIZE;++i) {
            cout << hex << setfill('0') << setw(2) << static_cast<unsigned int>(buffer_[i]) << " ";
        }
        cout << dec << endl;
    }
    ~Vector() { //wykorzystuje trejty
        delete_impl(boost::has_trivial_destructor<T>());
        delete [] buffer_;
    }
private:
	BUF_ELEMENT* getRawPointer(int idx) { return buffer_ + idx * sizeof(T); }
    T* getPointer(int idx) { return reinterpret_cast<T*>(getRawPointer(idx)); }

    void delete_impl(boost::true_type) {
        cout << "do nothing destructor" << flush;
    }

    void delete_impl(boost::false_type) {
        cout << "loop delete destructor" << flush;
        for(int i=0;i<size_;++i) {
            getPointer(i)->~T(); //wola destruktor
        }
    }
private:
    BUF_ELEMENT* buffer_;
    int size_;
};

class Foo {
public:
    Foo(int i) : i_(i) {}
    ~Foo() { cout << "d-tor Foo " << i_ << endl; }
    int i_;
};

std::ostream& operator<<(std::ostream& o, const Foo& f) {
    o << "Foo:" << f.i_ << ";";
    return o;
}

void zad2() {
    Vector<int> v;
    v.add(1);
    v.add(2);
    v.print();
    v.printBuf();
    Vector<Foo> v2;
    v2.add(Foo(1));
    v2.add(Foo(2));
    v2.print();
    v2.printBuf();
}



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

class Vis : public boost::static_visitor<void> {
public:
    Vis() {}
    void operator()(const int& i){ str_ += boost::lexical_cast<string>(i+1); }
    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.size());
    v.push_back(NAME);
    v.push_back(boost::lexical_cast<string>(v.front()));
    v.push_back(v.front());

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

//--------ZAD4--------------------------------------------------------------------------
//zadanie: znaleźć nazwiska osób o dochodach wyższych lub równych podanym

typedef std::pair<string, int> Person;

vector<string> getNames(const vector<Person>& persons, int min_salary) {
    vector<string> out;
    for_each( persons.begin(), persons.end(),
              [&](const Person& p) { if( p.second >= min_salary) out.push_back(p.first); } );
    return out;
}

void zad4() {
    vector<Person> persons = { Person("Abacki", 1700), Person("Babacki", 2100),
                               Person("Cabacki", 1800), Person("Dabacki", 2200) };
    vector<string> names_over_2000 = getNames(persons, 2000);
    copy(names_over_2000.begin(), names_over_2000.end(), ostream_iterator<string>(cout," ") );
}



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

int sum(int& s, int a) {
    s += a + 1;
	cout << "s:" << s << " a:" << a << endl;
    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;
}

