/**
  * Nazwa: boost_thread.cpp
  * Autor: Michał Ulaski	(F1-TIZTK)
  * Data: 11-03-07 13:37
  * Opis: Demonstracja i zastosowanie biblioteki boost::thread
  * Zakres:
            + tworzenie wątku na bazie obiektów
            + tworzenie wątków na bazie funkcji
            + mechanizm usypiania wątków
            + używanie thread specific ptr
  */

#define VERSION "0.2.1"

#include <iostream>
#include <vector>
#include <boost/thread.hpp>

/**
 * Klasa Obiekt:
 * Jest to prosty licznik
 * Metoda run() jest wykorzystywana do utworzenia wątku,
 * jednak jedynym możliwym sposobem uczynienia tego
 * jest zastosowanie wzorca komendy
 */
class Object
{
    private:

        std::string t_name;     ///< nazwa "wątku"
        bool t_run;             ///< wartość logiczna używana do tworzenia wątku
        int t_counter;          ///< licznik

    public:

        Object(std::string t_name)
        {
            this->t_name = t_name;
            this->t_run = false;
            this->t_counter = 0;
        }

        ~Object()
        {
        }

        std::string get_name() const
        {
            return this->t_name;
        }
        
        bool get_run() const
        {
            return this->t_run;
        }

        void set_run(bool t_run)
        {
            this->t_run = t_run;
        }

        /**
         * Metoda run()
         * To na jej podstawie jest tworzony wątek przy użyciu funktora
         */
        int run()
        {
            boost::xtime xt;    // jest to obiekt służący operacji związanych z czasem (o wysokiej precyzji) [jest to jednak rozwiązanie tymczasowe i w kolejnych wersjach biblioteki zostanie zastąpione]

            while(this->t_run)
            {
                this->t_counter++;
                std::cout << *this << std::endl;
                boost::xtime_get(&xt, boost::TIME_UTC);   // w ten sposób "wrzucamy" do powyższego obiektu bieżący czas (jako okres od początku epoki), zgodny z podanym zegarem
                xt.nsec += 500000000;                     // możemy dowolnie zwiększyć wartość (w tym przypadku o pół sekundy)
                boost::thread::sleep(xt);                 // a następnie "uspić" wątek do momentu gdy nadejdzie odpowiedni czas :)
            }
            return t_counter;
        };

        friend std::ostream& operator<<(std::ostream& out, const Object& o);
};

std::ostream& operator<<(std::ostream& out, const Object& o)
{
    out << "NAZWA: " << o.t_name << " LICZNIK: " << o.t_counter;
    return out;
};

/**
 * thread_specific_ptr
 * jest to specjalny mechanizm umożliwający
 * przechowywanie odrębnych danych tego samego typu
 * w każdym z wątków
 * to znaczy, każdy wątek przy odwoływaniu się do utworzonego
 * wskażnika na int (w tym także zapisywaniu nowej wartości)
 * będzie tak naprawdę działać na innych danych
 * dane zapisane przez wątek są czyszczone gdy się on zakończy
 */
boost::thread_specific_ptr<int> count;

/**
 * Klasa Funktor:
 * Jest to wzorzec komendy, służy on do tworzenia wątków na bazie metod
 * (a w zaszadzie metody run()) danego obiektu
 */
class Functor
{
    private:

        Object* object;

    public:

        Functor(Object* object)
        {
            this->object = object;
        }

        ~Functor()
        {
        }

        void operator()()
        {
            count.reset(new int(0));   // sposób inicjalizacji thread_specific_ptr dla wątku
            if(object)
            {
                *count += object->run();   // gdyby nie była to odrębna pamięć dla każdego wątku, to przy zakończeniu ostatniego wątku mieli byśmy wartość inną od faktycznej wartości licznika
                std::cout << std::endl << "\t\tNAZWA: " << object->get_name() << " DOLICZYŁ DO: " << *count << std::endl << std::endl;
            }
            return;
        };
};

/**
 * Wektor wskaźników na obiekty wykorzystywane w funkcji
 * threads_monitor
 */
std::vector<Object*> objects;

/**
 * Wątki można także tworzyć na bazie zwykłych funkcji.
 * Tak się właśnie dzieje z funkcja threads_monitor()
 * Sprawdza ona stan wszystkich wątków (objects)
 * i w przypadku, gdy wszystkie wątki się już zakonczyły
 * kończy działanie.
 */
void threads_monitor()
{
    boost::xtime xt;    // mechanizm uzypiania wątku, analogiczny jak w metodzie run() klasy obiekt
    bool state;
    while(true)
    {
        state = false;
        std::cout << "\t\t\t\tMONITOR:" << std::endl;
        for(int i = 0; i < objects.size(); i++)
        {
            std::cout << "\t\t\t\t" << *(objects.at(i)) << "  --STATE: ";
            if(objects.at(i)->get_run())
            {
                std::cout << "active" << std::endl;
                state = true;
            }
            else
            {
                std::cout << "inactive" << std::endl;
            }
        }
        if(!state)
        {
            break;
        }
        else
        {
            boost::xtime_get(&xt, boost::TIME_UTC);
            xt.nsec += 1500000000;
            boost::thread::sleep(xt);
        }
    }
    return;
}

int main(int argc,char *argv[])
{
    Object object_1("wątek_1");
    objects.push_back(&object_1);
    Functor functor_1(&object_1);
    object_1.set_run(true);
    boost::thread thread_1(functor_1);  // tworzenie nowego wątku na bazie obiektu, przy użyciu funktora

    Object object_2("wątek_2");
    objects.push_back(&object_2);
    Functor functor_2(&object_2);
    object_2.set_run(true);
    boost::thread thread_2(functor_2);  // j.w.

    Object object_3("wątek_3");
    objects.push_back(&object_3);
    Functor functor_3(&object_3);
    object_3.set_run(true);
    boost::thread thread_3(functor_3);  // j.w.

    boost::thread monitor(&threads_monitor);  // tworzenie nowego wątku na bazie zwykłej funkcji

    sleep(5);
    std::cout << std::endl << "\t\t!!!WYŁĄCZAMY 1 WĄTEK!!!" << std::endl << std::endl;
    object_1.set_run(false);
    sleep(3);
    std::cout << std::endl << "\t\t!!!WYŁĄCZAMY 3 WĄTEK!!!" << std::endl << std::endl;
    object_3.set_run(false);
    sleep(2);
    std::cout << std::endl << "\t\t!!!WYŁĄCZAMY 2 WĄTEK!!!" << std::endl << std::endl;
    object_2.set_run(false);
    monitor.join();     // wywołanie funkcji join powoduje, zatrzymanie bieżącego wątku do momentu zakończenia się wątku dla którego wywołaliśmy join

    return 0;
}

//
