/*
  Asynchroniczna obsluga zdarzen
*/


#include <vector>

#include <iostream>
#include <chrono>
#include <boost/asio.hpp>
#include <functional>
#include <mutex>
#include <thread>

using namespace std;

using Data = int; //nieistotne

struct Out {
    std::vector<Data> v; //nieistotne
    mutex m;

    //pobiera ostatni element z kolekcji i go usuwa.
    Data pop() {
        Data ret = 0;
        lock_guard<mutex> lock(m);
        if( ! v.empty() ) {
            ret = v.back();
            v.pop_back();
        }
        return ret;
    }
};


using Error=const boost::system::error_code&;
using Event=function<void(Error)>;

class Socket {
public:
    Socket(int n, boost::asio::io_service& i) : N(n), io(i), timeout(std::chrono::milliseconds(100)), t(io, timeout) {}

    void send_sync(Data d) {
        cout << "socket" << N << " send_sync" << d << endl;
        t.expires_at(t.expiry() + timeout );
        t.wait();
    }
    void send_async(Data d, Event event) {
        cout << "socket" << N << " send_async " << d << endl;
        t.expires_at(t.expiry() + timeout );
        t.async_wait(event);
    }
private:
    const int N;
    boost::asio::io_service& io;
    chrono::milliseconds timeout;
    boost::asio::steady_timer t;
};


class Thread {
public:
    Thread(int n, boost::asio::io_service& i, Out& o) : N(n), io(i), out(o) { }
    void operator()() {
        Socket s(N, io); //local socket
        while(1) {
            Data x = out.pop();
            if(x) {
                s.send_sync(x);
            } else {
                break;
            }
        }
    }
private:
    const int N;
    boost::asio::io_service& io;
    Out& out;
};

void my_service_event( Out& o, Socket& t, Error error) {
    if( error ) return;

    Data x = o.pop();

    if(x) {
        t.send_async(x, [&](Error error){ my_service_event(o, t, error);});
    }
}


//sync
int main() {

    Out out;
    out.v = vector(100, 1313); //tworzenie paczki danych, tego nie poprawiamy

    boost::asio::io_service io;

    Thread t1(1, io, out);
    Thread t2(1, io, out);
    Thread t3(1, io, out);

    io.run();

    std::thread thrd1( ref(t1) );
    std::thread thrd2( ref(t2) );
    std::thread thrd3( ref(t3) );

    thrd1.join();
    thrd2.join();
    thrd3.join();

    return 0;
}

//async
int main() {

    Out out;
    out.v = vector(100, 1313); //tworzenie paczki danych, tego nie poprawiamy

    boost::asio::io_service io;

    Socket s1(1, io);
    Socket s2(2, io);
    Socket s3(3, io);
    boost::system::error_code success; //success as default
    my_service_event(out, s1, success );
    my_service_event(out, s2, success );
    my_service_event(out, s3, success );

    io.run();

    return 0;
}





