package dev.banking.model;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class BankAccount {

    private final int accountId;
    private long balance;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition sufficientFunds = lock.newCondition();
    /*
     * Students may introduce additional fields
     * such as:
     * - Lock / ReentrantLock
     * - ReadWriteLock
     * - Object monitor
     * - etc.
     */

    public BankAccount(int accountId, long initialBalance) {
        this.accountId = accountId;
        this.balance = initialBalance;
    }

    public int getAccountId() {
        return accountId;
    }


    public long getBalance() {
        lock.lock();
        try{
            return balance;
        }finally {
            lock.unlock();
        }
    }

    public void deposit(long amount) {
        lock.lock();
        try {
            this.balance += amount;
            sufficientFunds.signalAll();
        }finally {
            lock.unlock();
        }
    }


    public void withdraw(long amount) {
       lock.lock();
        try {
            while (balance < amount){
                sufficientFunds.await();
            }
            balance -= amount;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public void transfer(BankAccount target, long amount) {
        if (this == target){
            return;
        }

        BankAccount first = this.accountId < target.getAccountId() ? this : target;
        BankAccount second = this.accountId < target.getAccountId() ? target : this;

       first.lock.lock();
       try{
           second.lock.lock();
           try{
               BankAccount source = this;
               BankAccount dest = target;

               if (first == target){
                   source = target;
                   dest = this;
               }

               while (source.balance < amount){
                   source.sufficientFunds.await();
               }

               source.balance -= amount;
               dest.balance += amount;
               dest.sufficientFunds.signalAll();

           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           } finally {
               second.lock.unlock();
           }
       }finally {
           first.lock.unlock();
       }
    }
}