package org.molgenis.data.mapper.algorithmgenerator.generator;
import org.apache.commons.lang3.StringUtils;
import org.molgenis.data.DataService;
import org.molgenis.data.mapper.algorithmgenerator.bean.AmountWrapper;
import org.molgenis.data.mapper.algorithmgenerator.bean.Category;
import org.molgenis.data.meta.model.Attribute;
import org.molgenis.data.meta.model.EntityType;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class OneToManyCategoryAlgorithmGenerator extends AbstractCategoryAlgorithmGenerator
{
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("##.#",
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
private final OneToOneCategoryAlgorithmGenerator oneToOneCategoryAlgorithmGenerator;
public OneToManyCategoryAlgorithmGenerator(DataService dataService)
{
super(dataService);
oneToOneCategoryAlgorithmGenerator = new OneToOneCategoryAlgorithmGenerator(dataService);
}
@Override
public boolean isSuitable(Attribute targetAttribute, List<Attribute> sourceAttributes)
{
return isXrefOrCategorialDataType(targetAttribute) && (sourceAttributes.stream()
.allMatch(this::isXrefOrCategorialDataType)) && sourceAttributes.size() > 1;
}
@Override
public String generate(Attribute targetAttribute, List<Attribute> sourceAttributes,
EntityType targetEntityType, EntityType sourceEntityType)
{
// if the target attribute and all the source attributes contain frequency related categories
StringBuilder stringBuilder = new StringBuilder();
if (suitableForGeneratingWeightedMap(targetAttribute, sourceAttributes))
{
stringBuilder.append(createAlgorithmNullCheckIfStatement(sourceAttributes))
.append(createAlgorithmElseBlock(targetAttribute, sourceAttributes));
}
else
{
for (Attribute sourceAttribute : sourceAttributes)
{
stringBuilder.append(oneToOneCategoryAlgorithmGenerator
.generate(targetAttribute, Arrays.asList(sourceAttribute), targetEntityType, sourceEntityType));
}
}
return stringBuilder.toString();
}
String createAlgorithmElseBlock(Attribute targetAttribute, List<Attribute> sourceAttributes)
{
StringBuilder stringBuilder = new StringBuilder();
if (sourceAttributes.size() > 0)
{
stringBuilder.append("else{\n").append("\tSUM_WEIGHT = new newValue(0);\n");
for (Attribute sourceAttribute : sourceAttributes)
{
String generateWeightedMap = generateWeightedMap(sourceAttribute);
if (StringUtils.isNotEmpty(generateWeightedMap))
{
stringBuilder.append("\tSUM_WEIGHT.plus(").append(generateWeightedMap).append(");\n");
}
}
stringBuilder.append("\tSUM_WEIGHT").append(groupCategoryValues(targetAttribute)).append("\n}");
}
return stringBuilder.toString();
}
String createAlgorithmNullCheckIfStatement(List<Attribute> sourceAttributes)
{
StringBuilder stringBuilder = new StringBuilder();
if (sourceAttributes.size() > 0)
{
stringBuilder.append("var SUM_WEIGHT;\n").append("if(");
sourceAttributes.stream().forEach(attribute -> stringBuilder.append("$('").append(attribute.getName())
.append("').isNull().value() && "));
stringBuilder.delete(stringBuilder.length() - 4, stringBuilder.length());
stringBuilder.append("){\n").append("\tSUM_WEIGHT = new newValue();\n").append("\tSUM_WEIGHT.value();\n}");
}
return stringBuilder.toString();
}
boolean suitableForGeneratingWeightedMap(Attribute targetAttribute,
List<Attribute> sourceAttributes)
{
boolean isTargetSuitable = oneToOneCategoryAlgorithmGenerator
.isFrequencyCategory(convertToCategory(targetAttribute));
boolean areSourcesSuitable = sourceAttributes.stream().map(this::convertToCategory)
.allMatch(oneToOneCategoryAlgorithmGenerator::isFrequencyCategory);
return isTargetSuitable && areSourcesSuitable;
}
public String generateWeightedMap(Attribute attribute)
{
StringBuilder stringBuilder = new StringBuilder();
for (Category sourceCategory : convertToCategory(attribute))
{
AmountWrapper amountWrapper = sourceCategory.getAmountWrapper();
if (amountWrapper != null)
{
if (stringBuilder.length() == 0)
{
stringBuilder.append("$('").append(attribute.getName()).append("').map({");
}
double estimatedValue = amountWrapper.getAmount().getEstimatedValue();
stringBuilder.append("\"").append(sourceCategory.getCode()).append("\":")
.append(DECIMAL_FORMAT.format(estimatedValue)).append(",");
}
}
if (stringBuilder.length() > 0)
{
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
stringBuilder.append("}, null, null).value()");
}
return stringBuilder.toString();
}
public String groupCategoryValues(Attribute attribute)
{
StringBuilder stringBuilder = new StringBuilder();
List<Category> sortedCategories = convertToCategory(attribute).stream()
.filter(category -> category.getAmountWrapper() != null).collect(Collectors.toList());
Collections.sort(sortedCategories, new Comparator<Category>()
{
public int compare(Category o1, Category o2)
{
return Double.compare(o1.getAmountWrapper().getAmount().getEstimatedValue(),
o2.getAmountWrapper().getAmount().getEstimatedValue());
}
});
List<Integer> sortedRangValues = getRangedValues(sortedCategories);
Map<String, Category> categoryRangeBoundMap = createCategoryRangeBoundMap(sortedCategories);
if (categoryRangeBoundMap.size() > 0)
{
if (stringBuilder.length() == 0)
{
stringBuilder.append(".group([");
}
sortedRangValues.stream().forEach(value -> stringBuilder.append(value).append(','));
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
stringBuilder.append("]).map({");
for (Entry<String, Category> entry : categoryRangeBoundMap.entrySet())
{
stringBuilder.append("\"").append(entry.getKey()).append("\":\"").append(entry.getValue().getCode())
.append("\",");
}
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
stringBuilder.append("}, null, null).value();");
}
return stringBuilder.toString();
}
private List<Integer> getRangedValues(List<Category> sortedCategories)
{
List<Integer> sortedRangValues = new ArrayList<>();
for (Category targetCategory : sortedCategories)
{
int minValue = parseAmountMinimumValue(targetCategory);
int maxValue = parseAmountMaximumValue(targetCategory);
if (!sortedRangValues.contains(minValue))
{
sortedRangValues.add(minValue);
}
if (!sortedRangValues.contains(maxValue))
{
sortedRangValues.add(maxValue);
}
}
return sortedRangValues;
}
private Map<String, Category> createCategoryRangeBoundMap(List<Category> sortedCategories)
{
Map<String, Category> categoryRangeBoundMap = new LinkedHashMap<>();
for (int categoryIndex = 0; categoryIndex < sortedCategories.size(); categoryIndex++)
{
Category category = sortedCategories.get(categoryIndex);
int minValue = parseAmountMinimumValue(category);
int maxValue = parseAmountMaximumValue(category);
if (categoryIndex == 0)
{
categoryRangeBoundMap.put("-" + minValue, category);
}
// The category contains ranged values
if (minValue != maxValue)
{
categoryRangeBoundMap.put(minValue + "-" + maxValue, category);
}
else
{
if (categoryIndex < sortedCategories.size() - 1)
{
Category nextCategory = sortedCategories.get(categoryIndex + 1);
int upperBound = parseAmountMinimumValue(nextCategory);
if (upperBound != maxValue)
{
categoryRangeBoundMap.put(maxValue + "-" + upperBound, category);
}
}
if (categoryIndex > 0)
{
Category previousCategory = sortedCategories.get(categoryIndex - 1);
int lowerBound = parseAmountMaximumValue(previousCategory);
if (lowerBound != minValue)
{
categoryRangeBoundMap.put(lowerBound + "-" + minValue, category);
}
}
}
if (categoryIndex == sortedCategories.size() - 1)
{
categoryRangeBoundMap.put(maxValue + "+", category);
}
}
return categoryRangeBoundMap;
}
int parseAmountMinimumValue(Category category)
{
return (int) Double
.parseDouble(DECIMAL_FORMAT.format(category.getAmountWrapper().getAmount().getMinimumValue()));
}
int parseAmountMaximumValue(Category category)
{
return (int) Double
.parseDouble(DECIMAL_FORMAT.format(category.getAmountWrapper().getAmount().getMaximumValue()));
}
}