/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.internal.providers.bankitems;
import com.jcwhatever.nucleus.collections.wrap.IteratorWrapper;
import com.jcwhatever.nucleus.providers.bankitems.IBankItem;
import com.jcwhatever.nucleus.providers.bankitems.InsufficientItemsException;
import com.jcwhatever.nucleus.storage.IDataNode;
import com.jcwhatever.nucleus.utils.CollectionUtils;
import com.jcwhatever.nucleus.utils.PreCon;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
/**
* Nucleus implementation of {@link IBankItem}.
*/
class BankItem implements IBankItem {
private final UUID _id;
private final BankItem _root;
private final ItemStack _item;
private final PageStartIndex _start = PageStartIndex.ONE;
private final IDataNode _dataNode;
private final Object _sync;
private int _itemsPerPage = 6 * 9;
private volatile int _amount;
/**
* Constructor.
*
* @param id The unique ID of the bank item.
* @param matchingStack An item stack that is represented by the {@link BankItem}.
* @param amount The quantity available.
* @param dataNode The items data node.
*/
public BankItem(UUID id, ItemStack matchingStack, int amount, IDataNode dataNode) {
this(id, null, matchingStack, amount, dataNode);
}
private BankItem(UUID id, @Nullable BankItem root, ItemStack matchingStack, int amount, IDataNode dataNode) {
PreCon.notNull(matchingStack);
_id = id;
_root = root != null ? root : this;
_sync = isRootItem() ? new Object() : null;
_item = matchingStack;
_amount = amount;
_dataNode = dataNode;
}
@Override
public UUID getId() {
return _id;
}
@Override
public boolean isRootItem() {
return _root == this;
}
@Override
public IBankItem getRootItem() {
return _root;
}
@Override
public Material getType() {
return _item.getType();
}
@Override
public int getAmount() {
return _amount;
}
@Override
public int getRootAmount() {
return _root._amount;
}
@Override
public int getMaxStackSize() {
return getType().getMaxStackSize();
}
@Override
public int getTotalStacks() {
return (int)Math.ceil((double)_amount / getMaxStackSize());
}
/**
* Deposit the specified amount of items into the account.
*
* @param amount The amount to deposit. Must be a positive number.
*
* @return True if successful.
*/
public boolean deposit(int amount) {
PreCon.positiveNumber(amount);
if (!isRootItem())
throw new RuntimeException("Only the root BankItem can accept deposits.");
if (amount == 0)
return true;
synchronized (_root._sync) {
if (isRootItem()) {
_amount += amount;
} else {
int result = _amount + amount;
if (result > getMaxStackSize())
return false;
_amount += amount;
_root._amount += amount;
}
_dataNode.set("amount", _root._amount);
_dataNode.save();
return true;
}
}
/**
* Withdraw/Remove the specified amount of items.
*
* @param amount The number of items. Must be a positive number.
*
* @throws InsufficientItemsException if there are not enough items.
*/
public void withdraw(int amount) throws InsufficientItemsException {
PreCon.positiveNumber(amount);
if (!isRootItem())
throw new RuntimeException("Only the root BankItem can be withdrawn from.");
if (amount == 0)
return;
if (amount > _amount)
throw new InsufficientItemsException();
synchronized (_root._sync) {
if (isRootItem()) {
_amount -= amount;
} else {
_amount -= amount;
_root._amount -= amount;
}
_dataNode.set("amount", _root._amount);
}
}
@Override
public ItemStack toItemStack(int amount) {
ItemStack itemStack = _item.clone();
itemStack.setAmount(amount);
return itemStack;
}
@Override
public PageStartIndex getPageStartIndex() {
return _start;
}
@Override
public int size() {
return getTotalStacks();
}
@Override
public int getTotalPages() {
return (int)Math.ceil(getTotalStacks() / (double)_itemsPerPage);
}
@Override
public int getItemsPerPage() {
return _itemsPerPage;
}
@Override
public void setItemsPerPage(int itemsPerPage) {
_itemsPerPage = itemsPerPage;
}
@Override
public List<IBankItem> getPage(int page) {
if (_amount == 0)
return CollectionUtils.unmodifiableList();
synchronized (_root._sync) {
int start = getStartIndex(page);
int end = getEndIndex(page);
int totalStacks = end - start;
if (totalStacks == 0)
return CollectionUtils.unmodifiableList();
List<IBankItem> result = new ArrayList<>(totalStacks);
int lastStackSize = end == getTotalStacks()
? getLastStackSize()
: getMaxStackSize();
for (int i = 0; i < totalStacks; i++) {
int size = i < totalStacks - 1 || lastStackSize == 0
? getMaxStackSize()
: lastStackSize;
result.add(new BankItem(_id, _root, _item, size, _dataNode));
}
return result;
}
}
@Override
public List<IBankItem> getItems() {
if (_amount == 0)
return CollectionUtils.unmodifiableList();
synchronized (_root._sync) {
int totalStacks = getTotalStacks();
int lastStackSize = getLastStackSize();
if (totalStacks == 0)
return CollectionUtils.unmodifiableList();
List<IBankItem> result = new ArrayList<>(totalStacks);
for (int i = 0; i < totalStacks; i++) {
int size = i < totalStacks - 1 || lastStackSize == 0
? getMaxStackSize()
: lastStackSize;
result.add(new BankItem(_id, _root, _item, size, _dataNode));
}
return result;
}
}
@Override
public Iterator<IBankItem> iterator(final int page) {
return new IteratorWrapper<IBankItem>() {
Iterator<IBankItem> iterator = getPage(page).iterator();
@Override
protected Iterator<IBankItem> iterator() {
return iterator;
}
@Override
public boolean onRemove(IBankItem remove) {
throw new UnsupportedOperationException();
}
};
}
@Override
public Iterator<IBankItem> iterator() {
return new IteratorWrapper<IBankItem>() {
Iterator<IBankItem> iterator = getItems().iterator();
@Override
protected Iterator<IBankItem> iterator() {
return iterator;
}
@Override
public boolean onRemove(IBankItem remove) {
throw new UnsupportedOperationException();
}
};
}
/**
* Get the index of the first item in the specified page.
*
* @param page The page number..
*/
protected int getStartIndex(int page) {
return (page - _start.getStartIndex()) * _itemsPerPage;
}
/**
* Get the index of the last item in the specified page.
*
* @param page The page number.
*/
protected int getEndIndex(int page) {
return Math.min(getTotalStacks(), getStartIndex(page) + _itemsPerPage);
}
/**
* Get the size of the last stack. Use when determining how many
* items are needed for the final stack when dividing the items
* into stacks with each being filled to the maximum stack size
* capacity.
*/
protected int getLastStackSize() {
if (_amount == 0)
return 0;
return _amount % getMaxStackSize();
}
}