#include <iostream>
#include <mutex>

// W pewnej grze gracze mogą zawierać transakcje. Każda transakcja może skończyć się
// dodaniem małego bonusu graczowi, który wydaje pieniądze. Kod nie działa poprawnie
// przy wywołaniu metody player1.take_all_money(player2) -- pieniądze zabierane
// są graczowi 2, ale nie są dodawane graczowi 1. Dodatkowo player2 nie otrzymuje
// bonusu (mimo, że powinien). Opisz błąd i popraw go korzystając z reguł borrow
// checkera Rust'a. Podpowiedź: analogiczny kod w Ruście skutkuje następującym błędem
// kompilacji: "cannot borrow `other` as mutable because it is also borrowed as immutable".
// Załóż, że typ Money ma przeciążone odp. operatory.

class PlayerAccount {
    public:
        PlayerAccount(Money money) : money(money) {};

        void take_all_money(PlayerAccount& other) {
            this->transaction(other, other.money);
        }

        void transaction(PlayerAccount& other, const Money& value) {
            if (other.money < value) {
                return;
            }
            other.money -= value;
            this->money += value;
            other.add_bonus_value(value);
        }

    private:
        int money;

        void add_bonus_value(const Money& transaction_value) {
            this->money += transaction_value * 0.01;
        }
};

// main na potrzeby testowania, ale nie dawałbym go w treści zadania
// int main () {
//     PlayerAccount acc1(100);
//     PlayerAccount acc2(200);
//     acc1.take_all_money(acc2, Double);
//     std::cout << acc1.get_balance() << std::endl;
//     std::cout << acc2.get_balance() << std::endl;
//     return 0;
// }

// Błąd polega na jednoczesnym dostępie przez referencje mutowalnie i niemutowalnie do obiektu `other` - złamanie reguły borrow checkera - łapiemy mutowalną referencję na `other` i niemutowalną na jego pole `money`. Rozwiązanie (jedno z kilku możliwych, można było też zmienić `transaction` tak żeby przyjmowało `valeu` przez wartość):

class PlayerAccount {
    public:
        PlayerAccount(Money money) : money(money) {};

        void take_all_money(PlayerAccount& other) {
            Money tmp_money = other.money;
            this->transaction(other, tmp_money);
        }

        void transaction(PlayerAccount& other, const Money& value) {
            if (other.money < value) {
                return;
            }
            other.money -= value;
            this->money += value;
            other.add_bonus_value(value);
        }

    private:
        int money;

        void add_bonus_value(const Money& transaction_value) {
            this->money += transaction_value * 0.01;
        }
};
