341 lines
10 KiB
C#
341 lines
10 KiB
C#
namespace CoffeeVendingMachines;
|
|
|
|
public class CoffeeVendingMachine
|
|
{
|
|
private static readonly CoinType[] withdrawalOrder =
|
|
[
|
|
CoinType.Euro02,
|
|
CoinType.Euro01,
|
|
CoinType.Cent50,
|
|
CoinType.Cent20,
|
|
CoinType.Cent10,
|
|
CoinType.Cent05
|
|
];
|
|
private readonly CoinDepot[] _currentChangeCoins;
|
|
private readonly CoinDepot[] _currentInputCoins;
|
|
private readonly Product[] _products;
|
|
|
|
public string Location { get; }
|
|
public int TotalChangeAmountInMachine => SumDepotValue(_currentChangeCoins);
|
|
public int TotalMoneyCurrentlyInput => SumDepotValue(_currentInputCoins);
|
|
public Product[] AvailableProducts => GetAvailableProducts();
|
|
|
|
/// <summary>
|
|
/// Creates a new coffee vending machine.
|
|
/// </summary>
|
|
/// <param name="coinDepots"></param>
|
|
/// <param name="products"></param>
|
|
/// <param name="location"></param>
|
|
public CoffeeVendingMachine(CoinDepot[] coinDepots, Product[] products, string location)
|
|
{
|
|
Location = location;
|
|
_currentChangeCoins = coinDepots;
|
|
_products = products;
|
|
_currentInputCoins = CreateCoinDepots(6);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new coffee vending machine with default products.
|
|
/// </summary>
|
|
/// <param name="location"></param>
|
|
public CoffeeVendingMachine(string location) : this(CreateCoinDepots(6, true), CreateDefaultProducts(), location)
|
|
{
|
|
// no need to implement anything here
|
|
}
|
|
|
|
public CoinDepot[] Cancel()
|
|
{
|
|
int count = 0;
|
|
foreach (var inputCoin in _currentInputCoins)
|
|
{
|
|
if (inputCoin.Count > 0)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
CoinDepot[] newInputCoins = new CoinDepot[count];
|
|
|
|
int j = 0;
|
|
for (int i = 0; i < _currentInputCoins.Length; i++)
|
|
{
|
|
if (_currentInputCoins[i].Count > 0)
|
|
{
|
|
newInputCoins[j] = new CoinDepot(_currentInputCoins[i]);
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return newInputCoins;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts the given coin into the machine.
|
|
/// </summary>
|
|
/// <param name="coin"></param>
|
|
/// <returns>True if the coin was inserted, false otherwise.</returns>
|
|
public bool InsertCoin(CoinType coin)
|
|
{
|
|
foreach (var inputDepot in _currentInputCoins)
|
|
{
|
|
if (inputDepot.Coin == coin)
|
|
{
|
|
inputDepot.Add();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Withdraws the change from the machine.
|
|
/// </summary>
|
|
/// <param name="productName"></param>
|
|
/// <param name="change"></param>
|
|
/// <param name="remainder"></param>
|
|
/// <returns>True if the change was withdrawn, false otherwise.</returns>
|
|
public bool SelectProduct(string productName, out CoinDepot[]? change, out int? remainder)
|
|
{
|
|
if(!TryFindProduct(productName, out Product? product) || product == null || !product.InStock || TotalMoneyCurrentlyInput < product.Price)
|
|
{
|
|
change = null;
|
|
remainder = null;
|
|
return false;
|
|
}
|
|
|
|
int returnAmount = TotalMoneyCurrentlyInput - product.Price;
|
|
AddInputToChange();
|
|
product.AddSale();
|
|
int? remainingAmount = PrepareChangeCoins(returnAmount, out CoinDepot[] changeCoins);
|
|
|
|
change = changeCoins;
|
|
remainder = remainingAmount;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Withdraws the input coins from the machine.
|
|
/// </summary>
|
|
/// <returns>The withdrawn coins.</returns>
|
|
public override string ToString()
|
|
{
|
|
string coinString = "";
|
|
string productString = "";
|
|
foreach (var coin in CoinType.GetValues(typeof(CoinType)))
|
|
{
|
|
CoinDepot? depot = GetDepotByType(_currentChangeCoins, (CoinType)coin);
|
|
if (depot != null)
|
|
{
|
|
coinString += $"|{depot.ToString()}".PadRight(40) + $"|{Environment.NewLine}";
|
|
}
|
|
}
|
|
foreach (var product in _products)
|
|
{
|
|
productString += $"|{product.ToString()}".PadRight(40) + $"|{Environment.NewLine}";
|
|
}
|
|
return $"|=======================================|{Environment.NewLine}" +
|
|
$"|Machine located at: {Location}".PadRight(40) + $"|{Environment.NewLine}" +
|
|
$"|=======================================|{Environment.NewLine}" +
|
|
coinString +
|
|
$"|=======================================|{Environment.NewLine}" +
|
|
productString +
|
|
"|=======================================|";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the input coins to the change coins.
|
|
/// </summary>
|
|
private void AddInputToChange()
|
|
{
|
|
for (int i = 0; i < _currentInputCoins.Length; i++)
|
|
{
|
|
while (_currentInputCoins[i].Count > 0)
|
|
{
|
|
_currentChangeCoins[i].Add();
|
|
_currentInputCoins[i].Withdraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the coin depots.
|
|
/// </summary>
|
|
/// <param name="count"></param>
|
|
/// <returns>The created coin depots.</returns>
|
|
private static CoinDepot[] CreateCoinDepots(int count, bool fill = false)
|
|
{
|
|
CoinDepot[] coinDepots = new CoinDepot[count];
|
|
CoinType[] coinTypes = (CoinType[])Enum.GetValues(typeof(CoinType));
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
coinDepots[i] = new CoinDepot(coinTypes[i]);
|
|
}
|
|
|
|
if (fill)
|
|
{
|
|
for (int i = 0; i < coinDepots.Length; i++)
|
|
{
|
|
while(coinDepots[i].Count < 3) // magic number
|
|
{
|
|
coinDepots[i].Add();
|
|
}
|
|
}
|
|
}
|
|
|
|
return coinDepots;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the default products.
|
|
/// </summary>
|
|
/// <returns>The default products.</returns>
|
|
private static Product[] CreateDefaultProducts()
|
|
{
|
|
Product[] products = new Product[3]; // magic number
|
|
products[0] = new Product("Cappuccino", 085, 10);
|
|
products[1] = new Product("Mocca", 100, 10);
|
|
products[2] = new Product("Cacao", 060, 10);
|
|
|
|
return products;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the depot by the given coin type.
|
|
/// </summary>
|
|
/// <param name="coinDepots"></param>
|
|
/// <param name="coinType"></param>
|
|
/// <returns>The depot with the given coin type or null if not found.</returns>
|
|
private static CoinDepot? GetDepotByType(CoinDepot[] coinDepots, CoinType coinType)
|
|
{
|
|
foreach (var coinDepot in coinDepots)
|
|
{
|
|
if (coinDepot.Coin == coinType)
|
|
{
|
|
return coinDepot;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepares the change coins for the given amount.
|
|
/// </summary>
|
|
/// <param name="amount"></param>
|
|
/// <param name="changeCoins"></param>
|
|
/// <returns>The remaining amount that could not be changed.</returns>
|
|
private int? PrepareChangeCoins(int amount, out CoinDepot[] changeCoins)
|
|
{
|
|
changeCoins = CreateCoinDepots(_currentChangeCoins.Length);
|
|
|
|
foreach (var coinType in withdrawalOrder)
|
|
{
|
|
CoinDepot? depot = GetDepotByType(_currentChangeCoins, coinType);
|
|
if (depot == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
while (amount >= (int)coinType && depot.Count > 0)
|
|
{
|
|
amount -= (int)coinType;
|
|
depot.Withdraw();
|
|
CoinDepot? changeDepot = GetDepotByType(changeCoins, coinType);
|
|
changeDepot?.Add();
|
|
}
|
|
}
|
|
|
|
changeCoins = RemoveUnusedCoins(changeCoins);
|
|
return amount > 0 ? amount : null;
|
|
}
|
|
|
|
private CoinDepot[] RemoveUnusedCoins(CoinDepot[] coins)
|
|
{
|
|
int count = 0;
|
|
foreach (var coin in coins)
|
|
{
|
|
if (coin.Count > 0)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
CoinDepot[] usedCoins = new CoinDepot[count];
|
|
int j = 0;
|
|
for (int i = 0; i < coins.Length; i++)
|
|
{
|
|
if (coins[i].Count > 0)
|
|
{
|
|
usedCoins[j] = coins[i];
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return usedCoins;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sums the value of all coins in the coin depots.
|
|
/// </summary>
|
|
/// <param name="coinDepots"></param>
|
|
/// <returns>The sum of all coins in the coin depots.</returns>
|
|
private static int SumDepotValue(CoinDepot[] coinDepots)
|
|
{
|
|
int sum = 0;
|
|
foreach (var coinDepot in coinDepots)
|
|
{
|
|
sum += coinDepot.Count * (int)coinDepot.Coin;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to find a product by its name.
|
|
/// </summary>
|
|
/// <param name="productName"></param>
|
|
/// <param name="product"></param>
|
|
/// <returns>True if the product was found, false otherwise.</returns>
|
|
private bool TryFindProduct(string productName, out Product? product)
|
|
{
|
|
foreach (var _product in _products)
|
|
{
|
|
if(_product.Name == productName)
|
|
{
|
|
product = _product;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
product = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the available products.
|
|
/// </summary>
|
|
/// <returns>The available products.</returns>
|
|
private Product[] GetAvailableProducts()
|
|
{
|
|
int count = 0;
|
|
foreach (var product in _products)
|
|
{
|
|
if (product.InStock)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
Product[] availableProducts = new Product[count];
|
|
int j = 0;
|
|
for (int i = 0; i < _products.Length; i++)
|
|
{
|
|
if (_products[i].InStock)
|
|
{
|
|
availableProducts[j] = _products[i];
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return availableProducts;
|
|
}
|
|
}
|