#include <functional>    
#include <iostream>
#include <mutex>
#include <thread>

typedef long Money; //In minor unit.

class Account {
public:
	bool transfer(Account& to,const Money amount);
	Money get_balance() const;
	Account(const Money deposit=0) : balance{deposit} {}
private:
    mutable std::mutex lock;
	Money balance;
};

namespace{
	std::less<void*> less{};
	std::equal_to<void*> equal_to{};
}

bool Account::transfer(Account& to,const Money amount){
	std::unique_lock<decltype(this->lock)> flock{this->lock,std::defer_lock};
	std::unique_lock<decltype(to.lock)> tlock{to.lock,std::defer_lock};
	if(less(&this->lock,&to.lock)){
		flock.lock();
		tlock.lock();
	} else if(equal_to(&this->lock,&to.lock)) {
		flock.lock();
	} else {
		tlock.lock();
		flock.lock();
	}
	this->balance-=amount;
	to.balance+=amount;
	return true;
}

Money Account::get_balance() const{
	const std::lock_guard<decltype(this->lock)> guard{this->lock};
	return this->balance;
}

void hammer_transfer(Account& from,Account& to,const Money amount, const int tries){
	for(int i{1};i<=tries;++i){
	    from.transfer(to,amount);
	}
}

int main() {
	constexpr Money open_a{ 200000L};
     constexpr Money open_b{ 100000L};
    constexpr Money tran_ab{10};
    constexpr Money tran_ba{3};
    constexpr Money tran_aa{7};

    Account A{open_a};
	Account B{open_b};
	
	std::cout << "A Open:" << A.get_balance() << '\n';
	std::cout << "B Open:" << B.get_balance() << '\n';
	
	constexpr long tries{20000}; 
	std::thread TAB{hammer_transfer,std::ref(A),std::ref(B),tran_ab,tries};
	std::thread TBA{hammer_transfer,std::ref(B),std::ref(A),tran_ba,tries};
	std::thread TAA{hammer_transfer,std::ref(A),std::ref(A),tran_aa,tries};

	TAB.join();
	TBA.join();
	TAA.join();

    const auto close_a{A.get_balance()};
    const auto close_b{B.get_balance()};   
	
	std::cout << "A Close:" << close_a<< '\n';
	std::cout << "B Close:" << close_b<< '\n';
	
    int errors{0};
    if((close_a+close_b)!=(open_a+open_b)){
        std::cout << "ERROR: Money Leaked!\n";
        ++errors;
    }
    if(close_a!=(open_a+tries*(tran_ba-tran_ab)) ||
          close_b!=(open_b+tries*(tran_ab-tran_ba))
    ){
        std::cout << "ERROR: 'Lost' Transaction(s)\n";
        ++errors;
    }
    if(errors==0){
        std::cout << "* SUCCESS *\n";
    }else{
        std::cout << "** FAILED **\n";
    }
    std::cout << std::endl;
	return 0;
}
