package tc.oc.pgm.crafting;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableSet;
import org.bukkit.inventory.FurnaceRecipe;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.ShapelessRecipe;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import tc.oc.pgm.kits.ItemParser;
import tc.oc.pgm.map.MapModule;
import tc.oc.pgm.map.MapModuleContext;
import tc.oc.pgm.map.MapModuleFactory;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchModuleFactory;
import tc.oc.pgm.module.ModuleDescription;
import tc.oc.pgm.module.ModuleLoadException;
import tc.oc.pgm.utils.MaterialPattern;
import tc.oc.pgm.utils.XMLUtils;
import tc.oc.pgm.xml.InvalidXMLException;
@ModuleDescription(name = "Crafting")
public class CraftingModule implements MapModule, MatchModuleFactory<CraftingMatchModule> {
private final Set<Recipe> customRecipes;
private final Set<MaterialPattern> disabledRecipes;
public CraftingModule(Set<Recipe> customRecipes, Set<MaterialPattern> disabledRecipes) {
this.customRecipes = ImmutableSet.copyOf(customRecipes);
this.disabledRecipes = ImmutableSet.copyOf(disabledRecipes);
}
@Override
public CraftingMatchModule createMatchModule(Match match) throws ModuleLoadException {
return new CraftingMatchModule(match, customRecipes, disabledRecipes);
}
public static class Factory extends MapModuleFactory<CraftingModule> {
@Override
public @Nullable CraftingModule parse(MapModuleContext context, Logger logger, Document doc) throws InvalidXMLException {
Set<Recipe> customRecipes = new HashSet<>();
Set<MaterialPattern> disabledRecipes = new HashSet<>();
for(Element elCrafting : doc.getRootElement().getChildren("crafting")) {
for(Element elDisable : elCrafting.getChildren("disable")) {
disabledRecipes.add(XMLUtils.parseMaterialPattern(elDisable));
}
for(Element elRecipe : XMLUtils.getChildren(elCrafting, "shapeless", "shaped", "smelt")) {
Recipe recipe;
switch(elRecipe.getName()) {
case "shapeless":
recipe = parseShapelessRecipe(context, elRecipe);
break;
case "shaped":
recipe = parseShapedRecipe(context, elRecipe);
break;
case "smelt":
recipe = parseSmeltingRecipe(context, elRecipe);
break;
default: throw new IllegalStateException();
}
customRecipes.add(recipe);
if(XMLUtils.parseBoolean(elRecipe.getAttribute("override"), false)) {
// Disable specific material + data
disabledRecipes.add(new MaterialPattern(recipe.getResult().getData()));
} else if(XMLUtils.parseBoolean(elRecipe.getAttribute("override-all"), false)) {
// Disable all of this material
disabledRecipes.add(new MaterialPattern(recipe.getResult().getType()));
}
}
}
return customRecipes.isEmpty() && disabledRecipes.isEmpty() ? null : new CraftingModule(customRecipes, disabledRecipes);
}
private ItemStack parseRecipeResult(MapModuleContext context, Element elRecipe) throws InvalidXMLException {
return context.needModule(ItemParser.class)
.parseItem(XMLUtils.getRequiredUniqueChild(elRecipe, "result"), false)
.immutableCopy();
}
public Recipe parseShapelessRecipe(MapModuleContext context, Element elRecipe) throws InvalidXMLException {
ShapelessRecipe recipe = new ShapelessRecipe(parseRecipeResult(context, elRecipe));
for(Element elIngredient : XMLUtils.getChildren(elRecipe, "ingredient", "i")) {
MaterialPattern item = XMLUtils.parseMaterialPattern(elIngredient);
int count = XMLUtils.parseNumber(elIngredient.getAttribute("amount"), Integer.class, 1);
if(item.dataMatters()) {
recipe.addIngredient(count, item.getMaterialData());
} else {
recipe.addIngredient(count, item.getMaterial());
}
}
if(recipe.getIngredientList().isEmpty()) {
throw new InvalidXMLException("Crafting recipe must have at least one ingredient", elRecipe);
}
return recipe;
}
public Recipe parseShapedRecipe(MapModuleContext context, Element elRecipe) throws InvalidXMLException {
ShapedRecipe recipe = new ShapedRecipe(parseRecipeResult(context, elRecipe));
Element elShape = XMLUtils.getRequiredUniqueChild(elRecipe, "shape");
List<String> rows = new ArrayList<>(3);
for(Element elRow : elShape.getChildren("row")) {
String row = elRow.getTextNormalize();
if(rows.size() >= 3) {
throw new InvalidXMLException("Shape must have no more than 3 rows (" + row + ")", elShape);
}
if(rows.isEmpty()) {
if(row.length() > 3) {
throw new InvalidXMLException("Shape must have no more than 3 columns (" + row + ")", elShape);
}
} else if(row.length() != rows.get(0).length()) {
throw new InvalidXMLException("All rows must be the same width", elShape);
}
rows.add(row);
}
if(rows.isEmpty()) {
throw new InvalidXMLException("Shape must have at least one row", elShape);
}
recipe.shape(rows.toArray(new String[rows.size()]));
Set<Character> keys = recipe.getIngredientMap().keySet(); // All shape symbols are present and mapped to null at this point
for(Element elIngredient : elRecipe.getChildren("ingredient")) {
MaterialPattern item = XMLUtils.parseMaterialPattern(elIngredient);
Attribute attrSymbol = XMLUtils.getRequiredAttribute(elIngredient, "symbol");
String symbol = attrSymbol.getValue();
if(symbol.length() != 1) {
throw new InvalidXMLException("Ingredient key must be a single character from the recipe shape", attrSymbol);
}
char key = symbol.charAt(0);
if(!keys.contains(key)) {
throw new InvalidXMLException("Ingredient key '" + key + "' does not appear in the recipe shape", attrSymbol);
}
if(item.dataMatters()) {
recipe.setIngredient(key, item.getMaterialData());
} else {
recipe.setIngredient(key, item.getMaterial());
}
}
if(recipe.getIngredientMap().isEmpty()) {
throw new InvalidXMLException("Crafting recipe must have at least one ingredient", elRecipe);
}
return recipe;
}
public Recipe parseSmeltingRecipe(MapModuleContext context, Element elRecipe) throws InvalidXMLException {
MaterialPattern ingredient = XMLUtils.parseMaterialPattern(XMLUtils.getRequiredUniqueChild(elRecipe, "ingredient", "i"));
ItemStack result = parseRecipeResult(context, elRecipe);
if(ingredient.dataMatters()) {
return new FurnaceRecipe(result, ingredient.getMaterialData());
} else {
return new FurnaceRecipe(result, ingredient.getMaterial());
}
}
}
}