//Kolokwium ZPR, 2021.06.08 - prosze zwrocic uwage na 2 polecenia ogolne (linia 3 i linia 8)

//1. Prosze umiescic swoje imie i nazwisko

const char* IMIE = "ROBERT";
const char* NAZWISKO = "NOWAK";

//2. Prosze oznaczyc linie, ktore zostaly zmodyfikowane dodajac krotki komentarz

//3. Proszę zmienić nazwę pliku na zpr_nazwisko.cpp, gdzie nazwisko nie uzywa polskich znaków i proszę plik załadować do MS Teams

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

#include <memory>

#include <iostream>
#include <algorithm>

#include <tuple>
#include <optional>

#include <thread>
#include <mutex>

#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

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

using namespace std;

//--------ZAD1 (5pkt)--------------------------------------------------------------------------
/*
Każdy z obiektów Data w kolekcji Out jest tworzony wykorzystując stan systemu podczas obsługi zdarzenia zegara.
Kolekcja Out nie jest używana zanim nie zostanie uzupełniona.

Dostarczone rozwiązanie jest mało wydajne. Spróbuj poprawić kod funkcji 'serve_event'.
*/

class Data {}; //nie zamieszczono skladowych

using PData = shared_ptr<Data>;

struct Out {
    Out(int size) : size_(size) {}

    int size_;
    std::vector<PData> v_;
    mutex m_;
};

void serve_event( Out& out, boost::asio::deadline_timer& t, const boost::system::error_code& error) {

    if( error ) return;

    lock_guard<mutex> guard(out.m_);

    PData data = PData(new Data()); //tworzenie paczki danych, nie ma dużych kosztów obliczeniowych, tego nie poprawiamy

    out.v_.push_back(data);

    if(out.v_.size() < out.size_) {
        t.async_wait([&](const boost::system::error_code& error){ serve_event(out, t, error); });
    }
}

void zad1() {

    Out out(500);

    boost::asio::io_service io;

    boost::asio::deadline_timer t1(io, boost::posix_time::milliseconds(8) );
    boost::asio::deadline_timer t2(io, boost::posix_time::milliseconds(13) );
    boost::asio::deadline_timer t3(io, boost::posix_time::milliseconds(21) );
    boost::asio::deadline_timer t4(io, boost::posix_time::milliseconds(34) );
    boost::asio::deadline_timer t5(io, boost::posix_time::milliseconds(55) );

    t1.async_wait([&](const boost::system::error_code& error){ serve_event(out, t1, error);});
    t2.async_wait([&](const boost::system::error_code& error){ serve_event(out, t2, error);});
    t3.async_wait([&](const boost::system::error_code& error){ serve_event(out, t3, error);});
    t4.async_wait([&](const boost::system::error_code& error){ serve_event(out, t4, error);});
    t5.async_wait([&](const boost::system::error_code& error){ serve_event(out, t5, error);});

    io.run();
}

 //--------ZAD2 (5pkt)-------------------------------------------------------------------------
// 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.

struct Queue {
    Queue() {}

    bool empty() {
        lock_guard<mutex> guard(m_);
        return q_.empty();
    }

    void push(PData data) {
        lock_guard<mutex> guard(m_);
        q_.push(data);
    }

    PData pop() {
        PData out;
        lock_guard<mutex> guard(m_);
        if(!q_.empty()) {
            out = q_.front();
            q_.pop();
        }
        return out;
    }

    mutex m_;
    std::queue<PData> q_;
};

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

void calc_thrd(Queue& q) {
    while(!q.empty()) {
        lock_guard<mutex> guard(q.m_);
        calculate(*q.q_.front());
        q.q_.pop();
    }
}

void zad2() {
     Queue q;
     //pominięto tworzenie wątków, które wstawiają obiekty PData do kolejki
     q.push(PData(new Data()));
     q.push(PData(new Data()));
     q.push(PData(new Data()));
     q.push(PData(new Data()));
     q.push(PData(new Data()));

     //tutaj wątki, które pobierają i przetwarzają obiekty PData z kolejki
     thread thrd1( calc_thrd, ref(q) );
     thread thrd2( calc_thrd, ref(q) );
     thread thrd3( calc_thrd, ref(q) );
     thread thrd4( calc_thrd, ref(q) );
     thread thrd5( calc_thrd, ref(q) );

     thrd1.join();
     thrd2.join();
     thrd3.join();
     thrd4.join();
     thrd5.join();
}

//--------ZAD3 (5pkt)-------------------------------------------------------------------------
// Objects of trivially-copyable types that are not potentially-overlapping subobjects are the only C++ objects that may be safely copied with std::memcpy
//
// template< class T >
// struct is_trivially_copyable;

// If T is a TriviallyCopyable type, provides the member constant value equal to true. For any other type, value is false.
// is_trivially_copyable<T> is std::true_type
// otherwise
// is_trivially_copyable<T> is std::false_type
// and is_trivially_copyable<T>::value is equal to false.

// Dostarcz szablon tab_copy, który tworzy kopię tablicy obiektów.
// użyj 'memcpy' gdy to możliwe, w przeciwnym wypadku wołaj konstruktor kopiujący dla każdego obiektu.

//void* memcpy(void* destination, const void* source, size_t num_bytes)
// kopiuje blok pamięci, num_bytes to liczba bajtów

//

template<class T>
T* tab_copy(const T tab [], size_t num_elements) {
    T* result = new T[num_elements];
    for(size_t i = 0; i < num_elements; ++i) {
        new ( &result[i] ) T(tab[i]); // placement copy constructor
    }
    return result;
}

class Foo {
public:
    Foo(int i=0) : i_(i) {}
    Foo(const Foo& foo) : i_(foo.i_) { cout << "copy c-tor" << endl; }
    int i_;
};

void zad3() {
    static const int SIZE = 10;
    {
        int sa[SIZE];
        int* copy = tab_copy(sa, SIZE );
        delete [] copy;
    }
    {
        Foo sb[SIZE];
        Foo* copy = tab_copy(sb, SIZE );
        delete [] copy;
    }
}

//--------ZAD4 (3pkt)--------------------------------------------------------------------------
// zmodyfikuj wartości g[0], g[1], g[2], g[3], g[4] aby funkcja zad4 wydrukowała liczbę liter Twojego nazwiska.

void zad4() {
    using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, int>;

    // wierzchołki mają identyfikatory całkowite od 0 do 4. W wierzchołku jest przechowywana dodatkowa liczba całkowita.
    using Edge = pair<int, int>;
    const vector<Edge> EDGES = { Edge(0,1), Edge(1,2), Edge(2,3), Edge(3,4) };
    Graph g(EDGES.begin(), EDGES.end(), 5 );               //liczba wierzchołków = 5

    //wartosci przechowywane w kolejnych wierzcholkach - te linie mozesz modyfikowac
    g[0]=0;
    g[1]=1;
    g[2]=2;
    g[3]=4;
    g[4]=8;

    Graph::vertex_descriptor v = *vertices(g).first;
    int sum = 0;
    while( out_degree(v, g) ) { //if has out edge
        sum += g[v];
        Graph::edge_descriptor e = *out_edges(v, g).first;
        v = target(e, g);
    }
    sum += g[v]; //wartosc dla ostatniego wierzcholka
    cout << sum << endl;
}

//--------ZAD5 (5pkt)--------------------------------------------------------------------------
//
// napisz funkcje: creteArticles, getNames oraz operator do wyswietlania Articles.
// Uzyj algorytmow z biblioteki standardowej.


struct Article : public tuple<int, optional<string> > {
    Article(int c, const optional<string>& n) : tuple<int, optional<string> > (c,n) {}

    int getCode() const { return get<0>(*this); }
    const optional<string>& getName() const { return get<1>(*this); }
};

using Articles = vector<Article>;


// createArticles - tworzy kolekcje krotek na podstawie wektora kodow i slownika z nazwami.
Articles createArticles(const vector<int>& codes, const map<int,string>& names) {
    Articles vec;
    vec.reserve(codes.size() );
    return vec;
};

// getNames - tworzy tablice asocjacyjna, klucz to kod produktu, wartosc to nazwa produktu.
// Jezeli produkt nie ma nazwy to nie wstawiamy go do tablicy.
map<int,string> getNames(const Articles& articles) {
    map<int,string> names;
    return names;
}

// Pisze na strumien produkty w postaci kod dwukropek nazwa srednik. Jezeli produkt nie ma nazwy, pisze "unknown"
ostream& operator<<(ostream& os, const Articles& v) {
    return os;
}


void zad5() {
    vector<int> codes = { 1, 2, 3, 10, 11, 12, 100 };
    map<int, string> names = {
                              {1, "apple"},
                              {2, "pear"},
                              {10, "potato"},
                              {100, "rice"}
    };

    Articles articles = createArticles(codes, names);
    cout << articles.size() << endl; //return 7
    cout << getNames(articles).size() << endl; //return 4
    cout << articles << endl;
}


// Pytanie 1 (1 pkt)
//Ile godzin w poświęciłeś na przedmiot ZPR (podaj jedną liczbę, która obejmuje obecność na wykładzie, naukę własną i projekt)


// SUMA =
// WYKLAD + NAUKA-WLASNA + KOLOKWIUM =
// PROJEKT =


// Pytanie 2 (1 pkt)
//Zaproponuj zagadnienie, które Twoim zdaniem warto byłoby omówić na przedmiocie ZPR

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