//Rozdział 15.
//Polecenie

public class BankAccount
{
    private int balance;
    private int overdraftLimit = -500;
    public void Deposit(int amount)
    {
        balance += amount;
        WriteLine($"Wpłacono {amount} PLN, saldo wynosi teraz {balance}");
    }
    public void Withdraw(int amount)
    {
        if (balance - amount >= overdraftLimit)
        {
            balance -= amount;
            WriteLine($"Wypłacono {amount} PLN, saldo wynosi teraz {balance}");
        }
    }
    public override string ToString()
    {
        return $"{nameof(balance)}: {balance}";
    }
}

//Implementacja wzorca Polecenie

public interface ICommand
{
    void Call();
}

public class BankAccountCommand : ICommand
{
    private BankAccount account;
    public enum Action
    {
        Deposit, Withdraw
    }
    private Action action;
    private int amount;
    public BankAccountCommand
        (BankAccount account, Action action, int amount) { ... }
}

public void Call()
{
    switch (action)
    {
        case Action.Deposit:
            account.Deposit(amount);
            succeeded = true;
            break;
        case Action.Withdraw:
            succeeded = account.Withdraw(amount);
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }
}

var ba = new BankAccount();
var cmd = new BankAccountCommand(ba,
    BankAccountCommand.Action.Deposit, 100);
cmd.Call(); // Wpłacono 100 PLN, saldo wynosi teraz 100
WriteLine(ba); // saldo: 100

//Operacje cofania
public interface ICommand
{
    void Call();
    void Undo();
}

public void Undo()
{
    switch (action)
    {
        case Action.Deposit:
            account.Withdraw(amount);
            break;
        case Action.Withdraw:
            account.Deposit(amount);
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }
}

//public bool Withdraw(int amount)
{
    if (balance - amount >= overdraftLimit)
    {
        balance -= amount;
        Console.WriteLine($"Wypłacono {amount} PLN, saldo wynosi teraz
        {balance}");
        return true; // sukces
    }
    return false; // niepowodzenie
}

public class BankAccountCommand : ICommand
{
...
    private bool succeeded;
}

public void Undo()
{
    if (!succeeded) return;
    switch (action)
    {
        case Action.Deposit:
            account.Deposit(amount); //zakładamy, że zawsze się powiedzie
            succeeded = true;
            break;
        case Action.Withdraw:
            succeeded = account.Withdraw(amount);
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }
}

var ba = new BankAccount();
var cmdDeposit = new BankAccountCommand(ba,
    BankAccountCommand.Action.Deposit, 100);
var cmdWithdraw = new BankAccountCommand(ba,
    BankAccountCommand.Action.Withdraw, 1000);
cmdDeposit.Call();
cmdWithdraw.Call();
WriteLine(ba); // saldo: 100
cmdWithdraw.Undo();
cmdDeposit.Undo();
WriteLine(ba); // saldo: 0

//Polecenia złożone
abstract class CompositeBankAccountCommand :
List<BankAccountCommand>, ICommand
{
    public virtual void Call()
    {
        ForEach(cmd => cmd.Call());
    }
    public virtual void Undo()
    {
        foreach (var cmd in
            ((IEnumerable<BankAccountCommand>)this).Reverse())
        {
            cmd.Undo();
        }
    }
}

class MoneyTransferCommand : CompositeBankAccountCommand
{
    public MoneyTransferCommand(BankAccount from,
        BankAccount to, int amount)
    {
        AddRange(new []
        {
            new BankAccountCommand(from,
                BankAccountCommand.Action.Withdraw, amount),
            new BankAccountCommand(to,
                BankAccountCommand.Action.Deposit, amount)
        });
    }
}

public override void Call()
{
    bool ok = true;
    foreach (var cmd in this)
    {
        if (ok)
        {
            cmd.Call();
            ok = cmd.Success;
        }
        else
        {
            cmd.Success = false;
        }
    }
}

var from = new BankAccount();
from.Deposit(100);
var to = new BankAccount();

var mtc = new MoneyTransferCommand(from, to, 1000);
mtc.Call();
WriteLine(from); // saldo: 100
WriteLine(to); // saldo: 0

Polecenie funkcyjne
public class BankAccount
{
    public int Balance;
}

public void Deposit(BankAccount account, int amount)
{
    account.Balance += amount;
}

public void Withdraw(BankAccount account, int amount)
{
    if (account.Balance >= amount)
    account.Balance -= amount;
}

var ba = new BankAccount();
var commands = new List<Action>();

commands.Add(() => Deposit(ba, 100));
commands.Add(() => Withdraw(ba, 100));

commands.ForEach(c => c());

 
