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

string NAME = "Abacki";

//--------ZAD1--------------------------------------------------------------------------
//

boost::mutex global_mutex;

const int NUM = 10;

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

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

    int get() {
        boost::mutex::scoped_lock lock(global_mutex);
        if(token_ > 0) {
            ++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(global_mutex);
                    token_ = x + 1;
                    if( counter_ > NUM ) {
                        break;
                    }
                }
            }
        }
    }
private:
    int id_;
    Process* in_;
    int token_;
    int counter_;
};

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(global_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(global_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(1);
    Process p2(2, &p1, 1);
    Process p3(3, &p2, 1);
    p1.setIn(&p3);

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

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

// //--------ZAD2--------------------------------------------------------------------------
// dostarczyć napisy, które wykorzystują znaki ASCII (typ char), ale operator porównania nie jest wrażliwy na wielkie i małe litery.
// funkcja standardowa 'int toupper(int)' dostarcza argument przekształcony na wielką literę,
// funkcja standardowa 'int tolower(int)' dostarcza argument przekształcony na małą literę,
// szablon std::basic_string ma dwa argumenty: typ znaku oraz klasę, wykorzystywaną do porównywania.
// Klasa ta powinna mieć metodę
// static int compare( const char_type* s1, const char_type* s2, std::size_t count );
// która zwraca 0 gdy napisy są równe, -1 gdy s1 jest mniejszy niż s2, +1 gdy s1 jest większy niż s2.


struct ci_char_traits : public std::char_traits<char> {

    static int compare(const char* s1, const char* s2, size_t n) {
        while ( n-- != 0 ) {
            if ( std::toupper(*s1) < std::toupper(*s2) ) return -1;
            if ( std::toupper(*s1) > std::toupper(*s2) ) return 1;
            ++s1; ++s2;
        }
        return 0;
    }

};

typedef std::basic_string<char, ci_char_traits> ci_string;

std::ostream& operator<<(std::ostream& os, const ci_string& str) {
    return os.write(str.data(), str.size());
}



void zad2() {
    ci_string s1 = "HELLO";
    ci_string s2 = "Hello";
    ci_string s3 = "hello";

    if (s1 == s2 )
        cout << s1 << " and " << s2 << " are equal" << endl;
    if (s1 == s3 )
        cout << s1 << " and " << s3 << " are equal" << endl;
    if (s2 == s3 )
        cout << s2 << " and " << s3 << " are equal" << endl;
}



// //--------ZAD3--------------------------------------------------------------------------
// podaj wydruk

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

int countNumLess(const std::vector<V>& v, int threshold) {

    class Vis : public boost::static_visitor<void> {
    public:
        Vis(int threshold) : threshold_(threshold), count_(0) {}
        void operator()(const int& i){ if(i < threshold_) ++count_; }
        void operator()(const std::string& s) { }
        int threshold_;
        int count_;
    } vis(threshold);
    for_each(v.begin(), v.end(), [&](const V& v){apply_visitor(vis, v);});
    return vis.count_;
}

void zad3() {

    std::vector<V> v;
    v.push_back("Abacki");
    v.push_back(1);
    v.push_back(v.size());
    v.push_back(NAME.size());
    v.push_back(NAME.size()/2);
    v.push_back(NAME.size()/4);

    cout << countNumLess(v, 3) << endl;
}

//--------ZAD4--------------------------------------------------------------------------
//zadanie: pierwszą osobę z parzystym identyfikatorem
typedef std::pair<string, int> Person;

vector<std::string> getNamesEven(const vector<Person>& v) {
    vector<string> out;
    for_each( v.begin(), v.end(),
              [&](const Person& p) { if( p.second % 2 == 0) out.push_back(p.first); } );
    return out;
}

void zad4() {
    vector<Person> persons = { Person("Abacki", 1), Person("Babacki", 3),
                               Person("Cabacki", 2), Person("Dabacki", 4) };
    vector<string> names_even = getNamesEven(persons);
    copy(names_even.begin(), names_even.end(), ostream_iterator<string>(cout," ") );
}

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

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

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

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

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

