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

#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/ref.hpp>
#include <boost/chrono.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/concept_check.hpp>

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

using namespace std;

string NAME = "Abacki";

//--------ZAD1--------------------------------------------------------------------------
// brak skalowalności w aplikacji współbieżnej
struct Document {
    boost::mutex m_;
    std::string str_;
};
typedef std::shared_ptr<Document> PDocument;


struct Dictionary {
    boost::mutex m_;
    map<string,string> dict_;
    string translate(const string& word) {
        map<string,string>::const_iterator it = dict_.find(word);
        if(it != dict_.end())
            return (*it).second;
        else
            return word;
    }
};
typedef std::shared_ptr<Dictionary> PDictionary;

class ThreadTranslate {
public:
    ThreadTranslate(PDocument doc, PDictionary dict) : doc_(doc), dict_(dict) {}

    void operator()() {
        boost::mutex::scoped_lock lock_doc(doc_->m_);
        boost::mutex::scoped_lock lock_dict(dict_->m_);
        cout << "translation start" << endl;
        vector<string> tokens;
        string token;
        stringstream is(doc_->str_);
        while( is >> token) {
            tokens.push_back(token);
        }
        transform( tokens.begin(), tokens.end(), tokens.begin(), [&](string word){return dict_->translate(word);} );
        std::ostringstream os;
        copy( tokens.begin(), tokens.end(), ostream_iterator<string>(os," "));
        out = os.str();
        boost::this_thread::sleep_for( boost::chrono::milliseconds(200));
        cout << "translation end" << endl;
    }
    string getTranslation() { return out; }
private:
    PDocument doc_;
    PDictionary dict_;
    string out;
};


void zad1() {
    PDictionary dictPlEn(new Dictionary); //polsko-angielski
    PDictionary dictPlDe(new Dictionary); //polsko-niemiecki

    PDocument docA(new Document);
    PDocument docB(new Document);

    ThreadTranslate t1(docA, dictPlEn);
    ThreadTranslate t2(docA, dictPlDe);
    ThreadTranslate t3(docB, dictPlEn);
    ThreadTranslate t4(docB, dictPlDe);

    try {
        boost::thread thrd1( ref(t1) );
        boost::thread thrd2( ref(t2) );
        boost::thread thrd3( ref(t3) );
        boost::thread thrd4( ref(t4) );
        thrd1.join();
        thrd2.join();
        thrd3.join();
        thrd4.join();
    } catch(exception&) {
        cout << "error" << endl;
    }
}

//--------ZAD2--------------------------------------------------------------------------
//zwraca medianę (środkową liczbę)


template<typename T> T median(T a, T b, T c) {
    BOOST_CONCEPT_ASSERT(( boost::LessThanComparableConcept<T>));

    if(a < b) {
        if(a < c) {
            if(b < c) return b;
            else return c;
        } else {
            return a;
        }
    } else {
        if( a < c ) {
            return a;
        } else {
            if(b < c) return c;
            else return b;
        }
    }
}

void zad2() {
    cout << median(1,2,3) << endl;
    cout << median(1,3,2) << endl;
    cout << median(2,1,3) << endl;
    cout << median(2,3,1) << endl;
    cout << median(3,1,2) << endl;
    cout << median(3,2,1) << endl;

    cout << median(1.0, 2.0, 3.0) << endl;
    cout << median(1.0, 3.0, 2.0) << endl;
    cout << median(2.0, 1.0, 3.0) << endl;
    cout << median(2.0, 3.0, 1.0) << endl;
    cout << median(3.0, 1.0, 2.0) << endl;
    cout << median(3.0, 2.0, 1.0) << endl;
}

//--------ZAD3--------------------------------------------------------------------------
// 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 {
                max_ = boost::lexical_cast<int>(s);
            } 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() {
    cout << endl;
    for(int i=3;i<15;++i) {
        NAME = std::string(i, 'x');
        cout << "name: " << NAME << ":";
        zad3();
        cout << endl;
    }
}


//--------ZAD4--------------------------------------------------------------------------
//zadanie: podaj wydruk


template <unsigned n> int sum(int k) {
    return n + sum<n-1>(k);
}
template <> int sum<1>(int k) {
    return k;
}

template <> int sum<0>(int k) {
    return 0;
}


void zad4() {
    cout << sum<2>(NAME.size()) << " " << sum<10>(NAME.size());
}

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



//--------ZAD5--------------------------------------------------------------------------
// zwraca ile liczb w danym zakresie (większe równe min i mniejsze równe max) zawiera wektor
int count_in_range(const std::vector<int>& numbers, int min, int max) {
    int counter = 0;
    for_each( numbers.begin(), numbers.end(), [&](int i){ if(i >= min && i <= max) ++counter;});
    return counter;
}


void zad5() {
    cout << count_in_range(vector<int>{1, 1, 2, 3}, 1, 3) << endl;
    cout << count_in_range(vector<int>{1, 1, 2, 3}, 2, 2) << endl;
    cout << count_in_range(vector<int>{1, 1, 2, 3, 2, 2}, 1, 2) << endl;
    cout << count_in_range(vector<int>{},-1000,1000) << endl;
}

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

