package com.abmash.REMOVE.core.element.distance;
import com.abmash.REMOVE.core.htmlquery.condition.ElementCondition.ElementType;
import com.abmash.api.HtmlElement;
import com.abmash.core.element.Location;
import com.abmash.core.element.Size;
import java.util.ArrayList;
import java.util.Comparator;
public class ElementDistanceComparator implements Comparator<HtmlElement> {
public enum DistanceType {
TOPLEFT, TOP, TOPRIGHT,
LEFT, CENTER, RIGHT,
BOTTOMLEFT, BOTTOM, BOTTOMRIGHT,
DEFAULT
}
public enum CalculationType {
AVERAGE, MIN,
}
private DistanceType distanceType;
private CalculationType calculationType;
public ElementDistanceComparator(DistanceType distanceType, CalculationType calculationType) {
this.distanceType = distanceType;
this.calculationType = calculationType;
}
public int compare(HtmlElement firstElement, HtmlElement secondElement) {
// get weighted average distances
ArrayList<ElementWeightedAverageDistance> firstWeightedAverageDistances = getWeightedAverageDistances(firstElement);
ArrayList<ElementWeightedAverageDistance> secondWeightedAverageDistances = getWeightedAverageDistances(secondElement);
// TODO give weight on different reference elements?
Double firstDistance;
Double secondDistance;
if(calculationType == CalculationType.MIN) {
// get minimum distance for all reference elements
firstDistance = getMinimumDistanceForAllReferenceElements(firstWeightedAverageDistances);
secondDistance = getMinimumDistanceForAllReferenceElements(secondWeightedAverageDistances);
} else {
// get average distance for all reference elements
firstDistance = getAverageDistanceForAllReferenceElements(firstWeightedAverageDistances);
secondDistance = getAverageDistanceForAllReferenceElements(secondWeightedAverageDistances);
}
if(firstDistance <= secondDistance) return -1;
if(firstDistance > secondDistance) return 1;
return 0;
}
private ArrayList<ElementWeightedAverageDistance> getWeightedAverageDistances(HtmlElement element) {
// hash map for the weighted distances of each target element, identified by their internal id
ArrayList<ElementWeightedAverageDistance> weightedAverageDistances = new ArrayList<ElementWeightedAverageDistance>();
for (HtmlElement referenceElement: element.getReferenceElements()) {
// calculate distance and weight
Double distance = getDistance(element, referenceElement);
Double weight = getWeight(referenceElement);
// add distance and weight
ElementWeightedAverageDistance elementWeightedAverageDistance = new ElementWeightedAverageDistance(element);
elementWeightedAverageDistance.addWeightedDistance(distance, weight);
weightedAverageDistances.add(elementWeightedAverageDistance);
}
return weightedAverageDistances;
}
private Double getDistance(HtmlElement element, HtmlElement referenceElement) {
Double distance;
Location location = element.getLocation();
Location referenceLocation = referenceElement.getLocation();
Size size = element.getSize();
Size referenceSize = referenceElement.getSize();
ElementDistance elementDistance = new ElementDistance(referenceLocation, location, referenceSize, size);
switch (distanceType) {
case TOPLEFT:
distance = elementDistance.getDistanceTopLeft();
break;
case TOP:
distance = elementDistance.getDistanceTop();
break;
case TOPRIGHT:
distance = elementDistance.getDistanceTopRight();
break;
case LEFT:
distance = elementDistance.getDistanceLeft();
break;
case CENTER:
distance = elementDistance.getDistanceCenter();
break;
case RIGHT:
distance = elementDistance.getDistanceRight();
break;
case BOTTOMLEFT:
distance = elementDistance.getDistanceBottomLeft();
break;
case BOTTOM:
distance = elementDistance.getDistanceBottom();
break;
case BOTTOMRIGHT:
distance = elementDistance.getDistanceBottomRight();
break;
case DEFAULT:
default:
distance = elementDistance.getDistance();
for (ElementType elementType: referenceElement.getTypes()) {
switch (elementType) {
case TYPABLE:
distance = elementDistance.getDistanceTopLeft();
break;
}
// TODO break loop after first match?
}
break;
}
// System.out.println(distance + " for [" + element + "]");
return distance;
}
private Double getWeight(HtmlElement element) {
Double weight = 1.0;
for (ElementType elementType: element.getTypes()) {
switch (elementType) {
case TYPABLE:
case CHOOSABLE:
case DATEPICKER:
if (element.getTagName().equals("label")) {
weight *= 3;
}
// TODO more weight on exact matches
case IMAGE:
// TODO font-weight 400 as constant. research if 400 is default everywhere
String fontWeight;
if ((fontWeight = element.getCssValue("font-weight")) != null) {
weight *= Double.parseDouble(fontWeight) / 400;
}
if (element.getTagName().equals("strong")) {
weight *= 2.0;
}
if (element.getTagName().equals("li")) {
weight *= 1.5;
}
break;
default:
break;
}
}
return weight;
}
private Double getAverageDistanceForAllReferenceElements(ArrayList<ElementWeightedAverageDistance> weightedAverageDistances) {
Double distance = 0.0;
for (ElementWeightedAverageDistance weightedAverageDistance: weightedAverageDistances) {
distance += weightedAverageDistance.calculateWeightedAverageDistance();
}
return distance / weightedAverageDistances.size();
}
private Double getMinimumDistanceForAllReferenceElements(ArrayList<ElementWeightedAverageDistance> weightedAverageDistances) {
Double distance = Double.MAX_VALUE;
for (ElementWeightedAverageDistance weightedAverageDistance: weightedAverageDistances) {
distance = Math.min(distance, weightedAverageDistance.calculateWeightedAverageDistance());
}
return distance;
}
}