package tc.oc.pgm.regions;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import com.google.common.collect.Range;
import org.bukkit.util.ImVector;
import org.bukkit.util.Vector;
import org.jdom2.Attribute;
import org.jdom2.Element;
import tc.oc.pgm.features.FeatureDefinitionParser;
import tc.oc.pgm.features.MagicMethodFeatureParser;
import tc.oc.pgm.utils.MethodParser;
import tc.oc.pgm.utils.XMLUtils;
import tc.oc.pgm.xml.InvalidXMLException;
import tc.oc.pgm.xml.Node;
import static tc.oc.commons.core.exception.LambdaExceptionUtils.rethrowFunction;
import static tc.oc.commons.core.exception.LambdaExceptionUtils.rethrowSupplier;
public class RegionDefinitionParser extends MagicMethodFeatureParser<Region> implements FeatureDefinitionParser<Region> {
@Inject private RegionParser regionParser;
private Region parseHalves(Element el, double dir) throws InvalidXMLException {
final List<HalfspaceRegion> halves = new ArrayList<>();
XMLUtils.parseNumber(el, "x", Double.class).infinity(true).optional().ifPresent(x ->
halves.add(new HalfspaceRegion(new Vector(x, 0, 0), new Vector(dir, 0, 0)))
);
XMLUtils.parseNumber(el, "y", Double.class).infinity(true).optional().ifPresent(y ->
halves.add(new HalfspaceRegion(new Vector(0, y, 0), new Vector(0, dir, 0)))
);
XMLUtils.parseNumber(el, "z", Double.class).infinity(true).optional().ifPresent(z ->
halves.add(new HalfspaceRegion(new Vector(0, 0, z), new Vector(0, 0, dir)))
);
if(halves.isEmpty()) {
throw new InvalidXMLException("Expected at least one of x, y, or z attributes", el);
}
return new Intersection(halves);
}
@MethodParser
public HalfspaceRegion half(Element el) throws InvalidXMLException {
Vector normal = XMLUtils.parseVector(XMLUtils.getRequiredAttribute(el, "normal"));
if(normal.lengthSquared() == 0) {
throw new InvalidXMLException("normal must have a non-zero length", el);
}
Vector origin = XMLUtils.parseVector(el.getAttribute("origin"), new Vector());
return new HalfspaceRegion(origin, normal);
}
@MethodParser
public Region below(Element el) throws InvalidXMLException {
return parseHalves(el, -1);
}
@MethodParser
public Region above(Element el) throws InvalidXMLException {
return parseHalves(el, 1);
}
@MethodParser
public PointRegion point(Element el) throws InvalidXMLException {
return new PointRegion(XMLUtils.parseVector(new Node(el)));
}
@MethodParser
public CuboidRegion cuboid(Element el) throws InvalidXMLException {
Vector min = XMLUtils.parseVector(el.getAttribute("min"));
Vector max = XMLUtils.parseVector(el.getAttribute("max"));
Vector size = XMLUtils.parseVector(el.getAttribute("size"));
if(min != null && max != null && size == null) {
return new CuboidRegion(min, max);
} else if(min != null && max == null && size != null) {
return new CuboidRegion(min, min.plus(size));
} else if(min == null && max != null && size != null) {
return new CuboidRegion(max.clone().subtract(size), max);
} else {
throw new InvalidXMLException("cuboid must specify exactly two of 'min', 'max', and 'size' attributes", el);
}
}
@MethodParser
public CylindricalRegion cylinder(Element el) throws InvalidXMLException {
Vector base = XMLUtils.parseVector(el.getAttribute("base"));
if(base == null) {
throw new InvalidXMLException("Cylindrical region must specify valid base vector.", el);
}
try {
final double radius = XMLUtils.parseNumber(Node.fromRequiredAttr(el, "radius"), Double.class, true);
final double top = Node.tryAttr(el, "top")
.map(rethrowFunction(node -> XMLUtils.parseNumber(node, Double.class, true)))
.orElseGet(rethrowSupplier(() -> base.getY() + XMLUtils.parseNumber(Node.fromRequiredAttr(el, "height"), Double.class, true)));
return new CylindricalRegion(base, radius, top);
} catch (NumberFormatException e) {
throw new InvalidXMLException("Cylindrical region must specify valid radius and height.", el);
}
}
@MethodParser
public Region rectangle(Element el) throws InvalidXMLException {
Vector min = XMLUtils.parse2DVector(Node.fromRequiredAttr(el, "min"));
Vector max = XMLUtils.parse2DVector(Node.fromRequiredAttr(el, "max"));
return new CuboidRegion(ImVector.of(min.getX(), Double.NEGATIVE_INFINITY, min.getZ()),
ImVector.of(max.getX(), Double.POSITIVE_INFINITY, max.getZ()));
}
@MethodParser
public BlockRegion block(Element el) throws InvalidXMLException {
// TODO: remove "location" backwards compatibility with next major map proto bump
Vector loc = XMLUtils.parseVector(Node.fromAttr(el, "location"));
if(loc == null) {
loc = XMLUtils.parseVector(new Node(el));
if(loc == null) {
throw new InvalidXMLException("Block region must have valid location vector.", el);
}
}
return new BlockRegion(loc);
}
@MethodParser
public Region union(Element el) throws InvalidXMLException {
return new Union(regionParser.parseChildList(el));
}
@MethodParser
public Region intersect(Element el) throws InvalidXMLException {
return new Intersection(regionParser.parseChildList(el, Range.atLeast(1)));
}
@MethodParser
public Region complement(Element el) throws InvalidXMLException {
final List<Region> regions = regionParser.parseChildList(el, Range.atLeast(2));
return new Complement(regions.get(0), Union.of(regions.subList(1, regions.size())));
}
@MethodParser
public Region negative(Element el) throws InvalidXMLException {
return new NegativeRegion(regionParser.parseReferenceAndChildUnion(el));
}
@MethodParser
public Region circle(Element el) throws InvalidXMLException {
Vector center = XMLUtils.parse2DVector(Node.fromRequiredAttr(el, "center"));
double radius = XMLUtils.parseNumber(Node.fromRequiredAttr(el, "radius"), Double.class, true);
return new CylindricalRegion(ImVector.of(center.getX(),
Double.NEGATIVE_INFINITY,
center.getZ()),
radius,
Double.POSITIVE_INFINITY);
}
@MethodParser
public SphereRegion sphere(Element el) throws InvalidXMLException {
return new SphereRegion(XMLUtils.parseVector(Node.fromRequiredAttr(el, "center", "origin")),
XMLUtils.parseNumber(Node.fromRequiredAttr(el, "radius"), Double.class, true));
}
@MethodParser
public TranslatedRegion translate(Element el) throws InvalidXMLException {
Attribute offsetAttribute = el.getAttribute("offset");
if(offsetAttribute == null) {
throw new InvalidXMLException("Translate region must have an offset", el);
}
Vector offset = XMLUtils.parseVector(offsetAttribute);
return new TranslatedRegion(regionParser.parseReferenceAndChildUnion(el), offset);
}
@MethodParser
public MirroredRegion mirror(Element el) throws InvalidXMLException {
Vector normal = XMLUtils.parseVector(XMLUtils.getRequiredAttribute(el, "normal"));
if(normal.lengthSquared() == 0) {
throw new InvalidXMLException("normal must have a non-zero length", el);
}
Vector origin = XMLUtils.parseVector(el.getAttribute("origin"), new Vector());
return new MirroredRegion(regionParser.parseReferenceAndChildUnion(el), origin, normal);
}
@MethodParser
public EverywhereRegion everywhere(Element el) throws InvalidXMLException {
return new EverywhereRegion();
}
@MethodParser({"empty", "nowhere"})
public EmptyRegion empty(Element el) throws InvalidXMLException {
return new EmptyRegion();
}
}