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

#include <thread>
#include <mutex>
#include <chrono>
#include <boost/optional.hpp>

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


using namespace std;

string NAME = "Abacki";

//--------ZAD1(R)--------------------------------------------------------------------------
// podzial obliczen na 2 watki, rozdzielic calc_keywords od calc_category


using Keywords = vector<string>;

//long operation
Keywords calc_keywords(int id) {
    this_thread::sleep_for(chrono::milliseconds(100));
    Keywords k;
    k.push_back( boost::lexical_cast<string>(id) );
    return k;
}

//long operation
string calc_category(const Keywords& k) {
    this_thread::sleep_for(chrono::milliseconds(100));
    string category("empty");
    if( ! k.empty())
        category = k.front();
    return category;
}

class ThreadCalc {
public:
    ThreadCalc(const vector<int>& ids) :ids_(ids) {}
    void operator()() {
        for(int id : ids_) {
            Keywords k = calc_keywords(id); //long operation
            string c = calc_category(k); //long operation
            out_[id] = c;
        }
    }
    const map<int,string>& getResult() {
        return out_;
    }
private:
    vector<int> ids_;
    map<int,string> out_;
};

using Msg = pair<int, Keywords>;
using OMsg = boost::optional<Msg>;
class MsgQueue {
public:
    static const int END_ID = 0;
    OMsg get() {
        lock_guard<mutex> guard(m_);
        OMsg out;
        if( ! q_.empty() ) {
            out = q_.front();
            if( out->first != END_ID)
				q_.pop();
        }
        return out;
    }
    void put(int i, const Keywords& k) {
        lock_guard<mutex> guard(m_);
        q_.push(make_pair(i,k));
    }
private:
    mutex m_;
    queue<Msg> q_;
};

class ThreadKwd {
public:
    ThreadKwd(const vector<int>& ids, MsgQueue& q) :ids_(ids), q_(q) {}
    void operator()() {
        for(int id : ids_) {
            Keywords k = calc_keywords(id);
            q_.put(id, k);
        }
        q_.put(MsgQueue::END_ID, Keywords() );
    }
private:
    vector<int> ids_;
    MsgQueue& q_;
};


class ThreadCat {
public:
    ThreadCat(MsgQueue& q) : q_(q) {}
    void operator()() {
        for(;;) {
            OMsg msg = q_.get();
            if(msg) {
                if(msg->first == MsgQueue::END_ID)
					return;
                out_[msg->first] = calc_category(msg->second);
            }
        }
    }
    const map<int,string>& getResult() {
        return out_;
    }
private:
    MsgQueue& q_;
    map<int,string> out_;
};


void zad1() {

    ThreadCalc calc(vector<int>({1, 2, 3, 4, 5, 6}));
    thread thrd(ref(calc));
    if(thrd.joinable()) {
        thrd.join();
    }
    const map<int,string>& out = calc.getResult();
    for_each( out.begin(), out.end(), [](const pair<int,string>& p) { cout << "id:" << p.first << " category: " << p.second << endl; });
}

void zad1a() {
    MsgQueue q;
    ThreadKwd calc_kwd(vector<int>({1, 2, 3, 4, 5, 6}), q);
    ThreadCat calc_cat(q);
    thread thrd_kwd(ref(calc_kwd));
    thread thrd_cat(ref(calc_cat));
    if(thrd_kwd.joinable()) {
        thrd_kwd.join();
    }
    if(thrd_cat.joinable()) {
        thrd_cat.join();
    }

    const map<int,string>& out = calc_cat.getResult();
    for_each( out.begin(), out.end(), [](const pair<int,string>& p) { cout << "id:" << p.first << " category: " << p.second << endl; });
}


//--------ZAD2--------------------------------------------------------------------------
//maksymalna wartość z tabeli liczb
//badać, czy jest liczbą


template<typename T, size_t N, size_t i = N-1>
struct X {
    static T max(const std::array<T,N>& a, T m) {
        return m > a[i] ? X<T, N, i-1>::max(a, m) : X<T, N, i-1>::max(a, a[i]);
    }
};

template<class T, size_t N>
struct X<T, N, 0> {
    static T max(const std::array<T,N>& a, T m) {
        return m > a[0] ? m : a[0];
    }
};

template<class T, size_t N> T max(const std::array<T,N>& a) {
   static_assert(N > 0);
   static_assert(is_arithmetic<T>());

   return X<T,N>::max(a,a[0]);
}


void zad2() {
    std::array<int, 4> A = { 1, 1, 1, 1 };
    std::array<int, 4> B = { 1, 2, 3, 4 };
    std::array<int, 1> C = { 1 };

    cout << max(A) << endl;
    cout << max(B) << endl;
    cout << max(C) << endl;
}


//--------ZAD3--------------------------------------------------------------------------
//zadanie: podaj wydruk

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

int count(const std::vector<V>& v) {

    class Vis : public boost::static_visitor<void> {
    public:
        Vis() : max_(0) {}
        void operator()(const int& i){
            if(i > max_) max_ = i; }
        void operator()(const std::string& s) {
            try {
                int i = boost::lexical_cast<int>(s);
                if(i > max_) max_ = i;
            } catch(boost::bad_lexical_cast &) { }
        }
        int max_;
    } vis;
    for_each(v.begin(), v.end(), [&](const V& v){apply_visitor(vis, v);});
    return vis.max_;
}

void zad3() {
    std::vector<V> v;
    v.push_back(NAME.size());
    v.push_back(1);
    v.push_back("5");
    v.push_back(NAME);
    cout << count(v) << flush;
}

void zad3x() {
    static const char* LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    cout << endl;
    for(int i=3;i<15;++i) {
        NAME = std::string(LETTERS, i);
        cout << "name: " << NAME << ":";
        zad3();
        cout << endl;
    }
}


//--------ZAD4--------------------------------------------------------------------------
// BGL - podaj wydruk
void zad4() {
    using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, char>;
    using Edge = pair<int, int>;
    enum { A, B, C, D, E, NUM_VERTICES };
    const vector<Edge> EDGES =
        { Edge(A,B), Edge(A,C),
          Edge(B,C), Edge(B,D),
          Edge(D,E), Edge(E,A) };
    Graph g(EDGES.begin(), EDGES.end(), NUM_VERTICES);
    for_each( vertices(g).first, vertices(g).second, [&](Graph::vertex_descriptor v){ g[v]=NAME[v%NAME.size()]; });
    for_each(vertices(g).first, vertices(g).second,
             [&](Graph::vertex_descriptor v){
                 for_each(out_edges(v, g).first, out_edges(v, g).second,
                          [&](Graph::edge_descriptor e){ cout << g[target(e, g)];});
             });
    cout << endl;
}


void zad4x() {
    static const char* LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    cout << endl;
    for(int i=3;i<15;++i) {
        NAME = std::string(LETTERS, i);
        cout << "name: " << NAME << ":";
        zad4();
        cout << endl;
    }
}


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

string less_common(const std::vector<string>& words) {
    using Hist = map<string,int>;
    using Pair = Hist::value_type;

    Hist histogram;
    transform(words.begin(), words.end(), inserter(histogram, histogram.begin()), [](const string& s){ return make_pair(s,0); });
    for_each(words.begin(), words.end(), [&](const string& s){ ++histogram[s];});
    int min_occur = std::numeric_limits<int>::max();
    for_each(histogram.begin(), histogram.end(), [&](const Pair& p) { if(p.second < min_occur) min_occur = p.second; });
    auto it_min = find_if(histogram.begin(), histogram.end(), [=](const Pair& p){ return p.second == min_occur; });
    if(it_min != histogram.end())
        return it_min->first;
    else
        return string();
}



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

int main() {
    auto start = std::chrono::system_clock::now();
    cout << "zad1: "; zad1(); cout << endl;
    auto zad1 = std::chrono::system_clock::now();
    cout << "zad1res: "; zad1a(); cout << endl;
    auto zad1a = std::chrono::system_clock::now();
    std::chrono::duration<double> diff1 = zad1 - start;
    std::chrono::duration<double> diff2 = zad1a - zad1;
    cout << "zad1 time" << diff1.count() << endl;
    cout << "zad1a time" << diff2.count() << endl;

    cout << "zad2: "; zad2(); cout << endl;
    cout << "zad3: "; zad3x(); cout << endl;
    cout << "zad4: "; zad4x(); cout << endl;
    cout << "zad5: "; zad5(); cout << endl;
    return 0;
}
