package org.openlca.app.navigation.actions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openlca.app.cloud.index.Diff;
import org.openlca.app.cloud.index.DiffIndex;
import org.openlca.app.cloud.index.DiffType;
import org.openlca.app.cloud.ui.diff.DiffResult;
import org.openlca.core.database.CategorizedEntityDao;
import org.openlca.core.database.Daos;
import org.openlca.core.database.IDatabase;
import org.openlca.core.database.references.ExchangeReferenceSearch;
import org.openlca.core.database.references.FlowPropertyFactorReferenceSearch;
import org.openlca.core.database.references.IReferenceSearch;
import org.openlca.core.database.references.IReferenceSearch.Reference;
import org.openlca.core.database.usage.IUseSearch;
import org.openlca.core.model.AbstractEntity;
import org.openlca.core.model.Exchange;
import org.openlca.core.model.FlowPropertyFactor;
import org.openlca.core.model.ModelType;
import org.openlca.core.model.descriptors.CategorizedDescriptor;
class ReferenceSearcher {
private Set<Long> allChanged = new HashSet<>();
private Map<Long, String> idToRefId = new HashMap<>();
private Set<DiffResult> results = new HashSet<>();
private IDatabase database;
private DiffIndex index;
private Set<Long> alreadySearchedReferences = new HashSet<>();
private Set<Long> alreadySearchedUsages = new HashSet<>();
ReferenceSearcher(IDatabase database, DiffIndex index) {
this.database = database;
this.index = index;
}
List<DiffResult> run(List<DiffResult> toCheck) {
allChanged.clear();
for (DiffResult result : toCheck)
allChanged.add(result.local.localId);
if (allChanged.size() == index.getChanged().size())
return Collections.emptyList();
Map<ModelType, Set<Long>> typeToIds = prepareFromResults(toCheck);
search(typeToIds);
return new ArrayList<>(results);
}
private void search(Map<ModelType, Set<Long>> toCheck) {
Set<CategorizedDescriptor> allFound = new HashSet<>();
for (ModelType type : toCheck.keySet()) {
Set<Long> values = toCheck.get(type);
Set<CategorizedDescriptor> refs = search(type, values);
allFound.addAll(refs);
List<Diff> diffs = getChanged(refs);
for (Diff diff : diffs) {
DiffResult diffResult = new DiffResult(diff);
diffResult.ignoreRemote = true;
allChanged.add(diffResult.local.localId);
results.add(diffResult);
}
}
if (allFound.isEmpty() || allChanged.size() == index.getChanged().size())
return;
Map<ModelType, Set<Long>> next = prepareFromDescriptors(allFound);
search(next);
}
private Set<CategorizedDescriptor> search(ModelType type, Set<Long> toCheck) {
Set<CategorizedDescriptor> results = searchReferences(type, toCheck);
results.addAll(searchUsage(type, toCheck));
return results;
}
private Set<CategorizedDescriptor> searchReferences(ModelType type, Set<Long> toCheck) {
Set<CategorizedDescriptor> results = new HashSet<>();
for (Long id : new HashSet<>(toCheck)) {
if (alreadySearchedReferences.contains(id)) {
toCheck.remove(id);
continue;
}
alreadySearchedReferences.add(id);
}
if (toCheck.isEmpty())
return Collections.emptySet();
IReferenceSearch<?> refSearch = IReferenceSearch.FACTORY.createFor(type, database, true);
results.addAll(loadDescriptors(refSearch.findReferences(toCheck)));
return results;
}
private Set<CategorizedDescriptor> searchUsage(ModelType type, Set<Long> toCheck) {
for (Long id : new HashSet<>(toCheck)) {
if (alreadySearchedUsages.contains(id)) {
toCheck.remove(id);
continue;
}
alreadySearchedUsages.add(id);
Diff diff = index.get(idToRefId.get(id));
if (diff == null || diff.type != DiffType.CHANGED)
continue;
toCheck.remove(id);
}
Set<CategorizedDescriptor> results = new HashSet<>();
if (toCheck.isEmpty())
return results;
IUseSearch<?> useSearch = IUseSearch.FACTORY.createFor(type, database);
List<CategorizedDescriptor> usedIn = useSearch.findUses(toCheck);
for (CategorizedDescriptor descriptor : usedIn) {
Diff diff = index.get(descriptor.getRefId());
if (diff == null || diff.type == DiffType.NO_DIFF || diff.type == DiffType.NEW)
continue;
results.add(descriptor);
}
return results;
}
private Set<CategorizedDescriptor> loadDescriptors(List<Reference> references) {
Map<Class<? extends AbstractEntity>, Set<Long>> map = new HashMap<>();
for (Reference reference : references) {
Set<Long> set = map.get(reference.getType());
if (set == null)
map.put(reference.getType(), set = new HashSet<>());
set.add(reference.id);
}
Set<CategorizedDescriptor> descriptors = new HashSet<>();
List<Reference> newRefs = new ArrayList<>();
for (Class<? extends AbstractEntity> clazz : map.keySet()) {
ModelType type = ModelType.forModelClass(clazz);
if (type != null && type.isCategorized()) {
CategorizedEntityDao<?, ?> dao = Daos.createCategorizedDao(database, type);
descriptors.addAll(dao.getDescriptors(map.get(clazz)));
} else if (clazz == FlowPropertyFactor.class) {
newRefs.addAll(new FlowPropertyFactorReferenceSearch(database).findReferences(map.get(clazz)));
} else if (clazz == Exchange.class) {
newRefs.addAll(new ExchangeReferenceSearch(database).findReferences(map.get(clazz)));
}
}
if (!newRefs.isEmpty())
descriptors.addAll(loadDescriptors(newRefs));
for (CategorizedDescriptor descriptor : descriptors)
idToRefId.put(descriptor.getId(), descriptor.getRefId());
return descriptors;
}
private List<Diff> getChanged(Set<CategorizedDescriptor> refs) {
List<Diff> relevant = new ArrayList<>();
for (CategorizedDescriptor d : refs) {
if (allChanged.contains(d.getId()))
continue;
Diff diff = index.get(d.getRefId());
if (diff == null || !diff.hasChanged())
continue;
relevant.add(diff);
}
return relevant;
}
private Map<ModelType, Set<Long>> prepareFromResults(List<DiffResult> toCheck) {
Map<ModelType, Set<Long>> typeToIds = new HashMap<>();
for (DiffResult result : toCheck) {
ModelType type = result.local.getDataset().type;
addId(typeToIds, type, result.local.localId);
idToRefId.put(result.local.localId, result.local.getDataset().refId);
}
return typeToIds;
}
private Map<ModelType, Set<Long>> prepareFromDescriptors(Set<CategorizedDescriptor> toCheck) {
Map<ModelType, Set<Long>> typeToIds = new HashMap<>();
for (CategorizedDescriptor descriptor : toCheck) {
addId(typeToIds, descriptor.getModelType(), descriptor.getId());
idToRefId.put(descriptor.getId(), descriptor.getRefId());
}
return typeToIds;
}
private void addId(Map<ModelType, Set<Long>> map, ModelType type, long id) {
Set<Long> ids = map.get(type);
if (ids == null)
map.put(type, ids = new HashSet<>());
ids.add(id);
}
}