#include <iostream>
#include <memory>
#include <queue>
#include <future>

#include <iostream>
#include <mutex>

class RWLocker final
{
    mutable std::mutex general_lock_;
    mutable std::mutex write_lock_;
    mutable std::mutex read_lock_;

    mutable std::size_t number_of_readers_ = 0;

    RWLocker (const RWLocker &) = delete;
    RWLocker & operator= (const RWLocker &) = delete;

public:
    RWLocker () = default;

    //Only read and write abstractions can work with RWLocker
    friend class ReadGuard;
    friend class WriteGuard;
};

class WriteGuard final
{
    const RWLocker & locker_;

    WriteGuard (const WriteGuard &) = delete;
    WriteGuard & operator= (const WriteGuard &) = delete;

public:

    //Strong exception guarantee
    explicit WriteGuard (const RWLocker & input_lock) :
        locker_ { input_lock }
    {
        locker_.general_lock_.lock ();
        locker_.write_lock_.lock ();
        locker_.general_lock_.unlock ();
    }

    ~WriteGuard ()
    {
        //Anyway UB if unlock fails
        locker_.write_lock_.unlock ();
    }
};

class ReadGuard final
{
    const RWLocker & locker_;

    ReadGuard (const ReadGuard &) = delete;
    ReadGuard & operator= (const ReadGuard &) = delete;

public:

    //Strong exception guarantee
    explicit ReadGuard (const RWLocker & input_lock) :
        locker_ { input_lock }
    {
        locker_.general_lock_.lock ();
        locker_.read_lock_.lock ();
        locker_.general_lock_.unlock ();

        if (0 < ++locker_.number_of_readers_)
            locker_.write_lock_.lock ();

        locker_.read_lock_.unlock ();
    }

    ~ReadGuard ()
    {
        //Anyway UB if unlock fails
        if (1 > --locker_.number_of_readers_)
            locker_.write_lock_.unlock ();

        locker_.read_lock_.unlock ();
    }
};

template<typename T>
std::chrono::milliseconds measure_execution_time(T _t)
{
    std::chrono::steady_clock clock;
    auto tp_begin = clock.now();
    _t();
    auto tp_end = clock.now();
    auto execution_time =
    std::chrono::duration_cast<std::chrono::milliseconds>(
        tp_end.time_since_epoch() - tp_begin.time_since_epoch()
    );
    return execution_time;
}

int main()
{
    const int n = 10000000;
    {
        RWLocker locker;
        auto time = measure_execution_time([n, &locker](){
            long c = 0;
            for (int i = 0; i< n; ++i)
            {
                ReadGuard guard(locker);
                c += i;
            }
        });
        
        std::cout << "Yoba mutex: " << time.count() << std::endl;
    }
    
    {
        std::mutex m;
        auto time = measure_execution_time([n, &m](){
            long c = 0;
            for (int i = 0; i< n; ++i)
            {
                std::lock_guard<std::mutex> lock(m);
                c += i;
            }
        });
        
        std::cout << "Mutex: " << time.count() << std::endl;
    }
    
    
    return 0;
}

