package io.sphere.sdk.facets;
import io.sphere.sdk.categories.Category;
import io.sphere.sdk.categories.CategoryTree;
import io.sphere.sdk.models.Reference;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Function;
import static java.util.stream.Collectors.toList;
/**
* Mapper that transforms facet options with Category IDs into a hierarchical list of facet options, as defined by the given category list.
* The IDs are then replaced by the Category name, in a language according to the provided locales.
* Any facet option that is not represented in the list of categories or doesn't contain a name for the locales, is discarded.
*/
public class HierarchicalCategoryFacetOptionMapper implements FacetOptionMapper {
private final CategoryTree subcategoryTree;
private final List<Locale> locales;
private HierarchicalCategoryFacetOptionMapper(final CategoryTree subcategoryTree, final List<Locale> locales) {
this.subcategoryTree = subcategoryTree;
this.locales = locales;
}
@Override
public List<FacetOption> apply(final List<FacetOption> facetOptions) {
return getRootCategories().stream()
.map(root -> buildFacetOption(root, facetOptionFinder(facetOptions)))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toList());
}
public static HierarchicalCategoryFacetOptionMapper of(final List<Category> subcategories, final List<Locale> locales) {
final CategoryTree subcategoryTree = CategoryTree.of(subcategories);
return new HierarchicalCategoryFacetOptionMapper(subcategoryTree, locales);
}
private List<Category> getRootCategories() {
return subcategoryTree.getAllAsFlatList().stream().filter(category -> {
final Optional<Reference<Category>> parentRef = Optional.ofNullable(category.getParent());
return parentRef.map(parent -> !subcategoryTree.findById(parent.getId()).isPresent()).orElse(true);
}).collect(toList());
}
private Optional<FacetOption> buildFacetOption(final Category category, final Function<Category, Optional<FacetOption>> facetOptionFinder) {
Optional<FacetOption> facetOption = facetOptionFinder.apply(category);
final List<Category> children = subcategoryTree.findChildren(category);
if (!children.isEmpty()) {
facetOption = addChildrenToFacetOption(facetOption, category, children, facetOptionFinder);
}
return setNameToFacetOptionLabel(facetOption, category, locales);
}
private Optional<FacetOption> addChildrenToFacetOption(final Optional<FacetOption> facetOption,
final Category category, final List<Category> children,
final Function<Category, Optional<FacetOption>> facetOptionFinder) {
boolean selected = facetOption.map(FacetOption::isSelected).orElse(false);
long count = facetOption.map(FacetOption::getCount).orElse(0L);
List<FacetOption> childrenFacetOption = new ArrayList<>();
for (final Category child : children) {
final Optional<FacetOption> childFacetOption = buildFacetOption(child, facetOptionFinder);
if (childFacetOption.isPresent()) {
selected |= childFacetOption.get().isSelected();
count += childFacetOption.get().getCount();
childrenFacetOption.add(childFacetOption.get());
}
}
return updateFacetOption(facetOption, category, selected, count, childrenFacetOption);
}
private Optional<FacetOption> updateFacetOption(final Optional<FacetOption> facetOption, final Category category,
final boolean selected, final long count, final List<FacetOption> childrenFacetOption) {
FacetOption updatedFacetOption = null;
if (facetOption.isPresent()) {
updatedFacetOption = facetOption.get()
.withCount(count)
.withSelected(selected)
.withChildren(childrenFacetOption);
} else if (!childrenFacetOption.isEmpty()) {
updatedFacetOption = FacetOption.of(category.getId(), count, selected).withChildren(childrenFacetOption);
}
return Optional.ofNullable(updatedFacetOption);
}
private Optional<FacetOption> setNameToFacetOptionLabel(final Optional<FacetOption> facetOptionOptional,
final Category category, final List<Locale> locales) {
return facetOptionOptional.flatMap(facetOption ->
category.getName().find(locales)
.map(facetOption::withLabel));
}
private Function<Category, Optional<FacetOption>> facetOptionFinder(final List<FacetOption> facetOptions) {
return category -> facetOptions.stream()
.filter(facetOption -> facetOption.getValue().equals(category.getId()))
.findFirst();
}
}