package dev.banking.model;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class BankAccount {

    private final int accountId;
    private long balance;

    /*
     * Students may introduce additional fields
     * such as:
     * - Lock / ReentrantLock
     * - ReadWriteLock
     * - Object monitor
     * - etc.
     */
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

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

    public int getAccountId() {
        return accountId;
    }

    /**
     * Thread-safe balance read.
     */
    public long getBalance() {

        lock.readLock().lock();

        try
        {
            return balance;
        }
        finally
        {
            lock.readLock().unlock();
        }
    }

    /**
     * Thread-safe deposit.
     */
    public void deposit(long amount) {

        lock.writeLock().lock();

        try
        {
            balance += amount;
        }
        finally
        {
            lock.writeLock().unlock();
        }
    }

    /**
     * Thread-safe withdrawal.
     */
    public void withdraw(long amount) {

        lock.writeLock().lock();

        try
        {
            balance -= amount;
        }
        finally
        {
            lock.writeLock().unlock();
        }
    }

    /**
     * Atomic deadlock-free transfer.
     *
     * Lock ordering rule:
     * Always acquire the lock of the account with the
     * smaller accountId first.
     */
    public void transfer(BankAccount target, long amount) {

        if (target == null)
            throw new IllegalArgumentException("Target account cannot be null.");

        if (this == target)
            return;

        BankAccount first = this.accountId < target.accountId ? this : target;

        BankAccount second = this.accountId < target.accountId ? target : this;

        first.lock.writeLock().lock();

        try
        {
            second.lock.writeLock().lock();

            try
            {
                this.balance -= amount;
                target.balance += amount;
            }
            finally
            {
                second.lock.writeLock().unlock();
            }

        }
        finally
        {
            first.lock.writeLock().unlock();
        }
    }
}