<commit_before>package org.metaborg.meta.nabl2.scopegraph.esop;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.metaborg.meta.nabl2.regexp.IRegExp;
import org.metaborg.meta.nabl2.regexp.IRegExpMatcher;
import org.metaborg.meta.nabl2.regexp.RegExpMatcher;
import org.metaborg.meta.nabl2.relations.IRelation;
import org.metaborg.meta.nabl2.relations.RelationDescription;
import org.metaborg.meta.nabl2.relations.terms.Relation;
import org.metaborg.meta.nabl2.scopegraph.ILabel;
import org.metaborg.meta.nabl2.scopegraph.INameResolution;
import org.metaborg.meta.nabl2.scopegraph.IOccurrence;
import org.metaborg.meta.nabl2.scopegraph.IPath;
import org.metaborg.meta.nabl2.scopegraph.IResolutionParameters;
import org.metaborg.meta.nabl2.scopegraph.IScope;
import org.metaborg.meta.nabl2.scopegraph.terms.Paths;
import org.metaborg.meta.nabl2.util.Optionals;
import org.metaborg.meta.nabl2.util.functions.Function1;
import org.metaborg.meta.nabl2.util.functions.PartialFunction0;
import org.metaborg.util.iterators.Iterables2;
import org.pcollections.HashTreePSet;
import org.pcollections.PSet;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class EsopNameResolution<S extends IScope, L extends ILabel, O extends IOccurrence>
        implements INameResolution<S,L,O>, Serializable {

    private static final long serialVersionUID = 42L;

    private static final boolean useStaging = true;

    private final EsopScopeGraph<S,L,O> scopeGraph;
    private final Set<L> labels;
    private final IRegExp<L> wf;
    private final IRegExp<L> noWf;
    private final IRelation<L> order;
    private final IRelation<L> noOrder;

    transient private Map<O,Iterable<IPath<S,L,O>>> resolveCache;
    transient private Map<IRelation<L>,EnvL<S,L,O>> stagedEnv_L;

    public EsopNameResolution(EsopScopeGraph<S,L,O> scopeGraph, IResolutionParameters<L> params) {
        this.scopeGraph = scopeGraph;
        this.labels = Sets.newHashSet(params.getLabels());
        this.wf = params.getPathWf();
        this.order = params.getSpecificityOrder();
        assert order.getDescription().equals(
                RelationDescription.STRICT_PARTIAL_ORDER) : "Label specificity order must be a strict partial order";
        this.noWf = wf.getBuilder().complement(wf.getBuilder().emptySet());
        this.noOrder = new Relation<>(RelationDescription.STRICT_PARTIAL_ORDER);
        initTransients();
    }

    private void initTransients() {
        this.resolveCache = Maps.newHashMap();
        this.stagedEnv_L = Maps.newHashMap();
    }

    @Override public Iterable<S> getAllScopes() {
        return scopeGraph.getAllScopes();
    }

    @Override public Iterable<O> getAllRefs() {
        return scopeGraph.getAllRefs();
    }

    @Override public Iterable<IPath<S,L,O>> resolve(O ref) {
        return tryResolve(ref).orElseGet(() -> Iterables2.empty());
    }

    @Override public Iterable<IPath<S,L,O>> visible(S scope) {
        return tryVisible(scope).orElseGet(() -> Iterables2.empty());
    }

    @Override public Iterable<IPath<S,L,O>> reachable(S scope) {
        return tryReachable(scope).orElseGet(() -> Iterables2.empty());
    }

    public Optional<Iterable<IPath<S,L,O>>> tryResolve(O ref) {
        if(resolveCache.containsKey(ref)) {
            return Optional.of(resolveCache.get(ref));
        } else {
            Optional<Iterable<IPath<S,L,O>>> paths = resolve(HashTreePSet.empty(), ref);
            paths.ifPresent(ds -> resolveCache.put(ref, ds));
            return paths;
        }
    }

    public Optional<Iterable<IPath<S,L,O>>> tryVisible(S scope) {
        EsopEnv<S,L,O> env = env(HashTreePSet.empty(), HashTreePSet.empty(), order, RegExpMatcher.create(wf), scope);
        return env.isComplete() ? Optional.of(env.getAll()) : Optional.empty();
    }

    public Optional<Iterable<IPath<S,L,O>>> tryReachable(S scope) {
        EsopEnv<S,L,O> env =
                env(HashTreePSet.empty(), HashTreePSet.empty(), noOrder, RegExpMatcher.create(noWf), scope);
        return env.isComplete() ? Optional.of(env.getAll()) : Optional.empty();
    }

    private Optional<Iterable<IPath<S,L,O>>> resolve(PSet<O> seenI, O ref) {
        return scopeGraph.getRefScope(ref).map(scope -> {
            EsopEnv<S,L,O> e = env(seenI.plus(ref), HashTreePSet.empty(), order, RegExpMatcher.create(wf), scope);
            Iterable<IPath<S,L,O>> paths = e.get(ref);
            if(e.isComplete()) {
                return Optional.of(paths);
            } else {
                return Iterables.isEmpty(paths) ? Optional.<Iterable<IPath<S,L,O>>>empty() : Optional.of(paths);
            }
        }).orElseGet(() -> Optional.of(Iterables2.empty()));
    }

    private EsopEnv<S,L,O> env(PSet<O> seenImports, PSet<S> seenScopes, IRelation<L> lt, IRegExpMatcher<L> re,
            S scope) {
        if(seenScopes.contains(scope) || re.isEmpty()) {
            return EsopEnv.empty(true);
        }
        return env_L(labels, seenImports, seenScopes, lt, re, scope);
    }

    private EsopEnv<S,L,O> env_l(PSet<O> seenImports, PSet<S> seenScopes, IRelation<L> lt, IRegExpMatcher<L> re, L l,
            S scope) {
        return l.getName().equals("D") ? env_D(seenImports, seenScopes, lt, re, l, scope)
                : env_nonD(seenImports, seenScopes, lt, re, l, scope);
    }

    private EsopEnv<S,L,O> env_D(PSet<O> seenImports, PSet<S> seenScopes, IRelation<L> lt, IRegExpMatcher<L> re, L l,<commit_msg>Respect path well-formedness when calculating reachable declarations.<commit_after>package org.metaborg.meta.nabl2.scopegraph.esop;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.metaborg.meta.nabl2.regexp.IRegExp;
import org.metaborg.meta.nabl2.regexp.IRegExpMatcher;
import org.metaborg.meta.nabl2.regexp.RegExpMatcher;
import org.metaborg.meta.nabl2.relations.IRelation;
import org.metaborg.meta.nabl2.relations.RelationDescription;
import org.metaborg.meta.nabl2.relations.terms.Relation;
import org.metaborg.meta.nabl2.scopegraph.ILabel;
import org.metaborg.meta.nabl2.scopegraph.INameResolution;
import org.metaborg.meta.nabl2.scopegraph.IOccurrence;
import org.metaborg.meta.nabl2.scopegraph.IPath;
import org.metaborg.meta.nabl2.scopegraph.IResolutionParameters;
import org.metaborg.meta.nabl2.scopegraph.IScope;
import org.metaborg.meta.nabl2.scopegraph.terms.Paths;
import org.metaborg.meta.nabl2.util.Optionals;
import org.metaborg.meta.nabl2.util.functions.Function1;
import org.metaborg.meta.nabl2.util.functions.PartialFunction0;
import org.metaborg.util.iterators.Iterables2;
import org.pcollections.HashTreePSet;
import org.pcollections.PSet;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class EsopNameResolution<S extends IScope, L extends ILabel, O extends IOccurrence>
        implements INameResolution<S,L,O>, Serializable {

    private static final long serialVersionUID = 42L;

    private static final boolean useStaging = true;

    private final EsopScopeGraph<S,L,O> scopeGraph;
    private final Set<L> labels;
    private final IRegExp<L> wf;
    private final IRelation<L> order;
    private final IRelation<L> noOrder;

    transient private Map<O,Iterable<IPath<S,L,O>>> resolveCache;
    transient private Map<IRelation<L>,EnvL<S,L,O>> stagedEnv_L;

    public EsopNameResolution(EsopScopeGraph<S,L,O> scopeGraph, IResolutionParameters<L> params) {
        this.scopeGraph = scopeGraph;
        this.labels = Sets.newHashSet(params.getLabels());
        this.wf = params.getPathWf();
        this.order = params.getSpecificityOrder();
        assert order.getDescription().equals(
                RelationDescription.STRICT_PARTIAL_ORDER) : "Label specificity order must be a strict partial order";
        this.noOrder = new Relation<>(RelationDescription.STRICT_PARTIAL_ORDER);
        initTransients();
    }

    private void initTransients() {
        this.resolveCache = Maps.newHashMap();
        this.stagedEnv_L = Maps.newHashMap();
    }

    @Override public Iterable<S> getAllScopes() {
        return scopeGraph.getAllScopes();
    }

    @Override public Iterable<O> getAllRefs() {
        return scopeGraph.getAllRefs();
    }

    @Override public Iterable<IPath<S,L,O>> resolve(O ref) {
        return tryResolve(ref).orElseGet(() -> Iterables2.empty());
    }

    @Override public Iterable<IPath<S,L,O>> visible(S scope) {
        return tryVisible(scope).orElseGet(() -> Iterables2.empty());
    }

    @Override public Iterable<IPath<S,L,O>> reachable(S scope) {
        return tryReachable(scope).orElseGet(() -> Iterables2.empty());
    }

    public Optional<Iterable<IPath<S,L,O>>> tryResolve(O ref) {
        if(resolveCache.containsKey(ref)) {
            return Optional.of(resolveCache.get(ref));
        } else {
            Optional<Iterable<IPath<S,L,O>>> paths = resolve(HashTreePSet.empty(), ref);
            paths.ifPresent(ds -> resolveCache.put(ref, ds));
            return paths;
        }
    }

    public Optional<Iterable<IPath<S,L,O>>> tryVisible(S scope) {
        EsopEnv<S,L,O> env = env(HashTreePSet.empty(), HashTreePSet.empty(), order, RegExpMatcher.create(wf), scope);
        return env.isComplete() ? Optional.of(env.getAll()) : Optional.empty();
    }

    public Optional<Iterable<IPath<S,L,O>>> tryReachable(S scope) {
        EsopEnv<S,L,O> env =
                env(HashTreePSet.empty(), HashTreePSet.empty(), noOrder, RegExpMatcher.create(wf), scope);
        return env.isComplete() ? Optional.of(env.getAll()) : Optional.empty();
    }

    private Optional<Iterable<IPath<S,L,O>>> resolve(PSet<O> seenI, O ref) {
        return scopeGraph.getRefScope(ref).map(scope -> {
            EsopEnv<S,L,O> e = env(seenI.plus(ref), HashTreePSet.empty(), order, RegExpMatcher.create(wf), scope);
            Iterable<IPath<S,L,O>> paths = e.get(ref);
            if(e.isComplete()) {
                return Optional.of(paths);
            } else {
                return Iterables.isEmpty(paths) ? Optional.<Iterable<IPath<S,L,O>>>empty() : Optional.of(paths);
            }
        }).orElseGet(() -> Optional.of(Iterables2.empty()));
    }

    private EsopEnv<S,L,O> env(PSet<O> seenImports, PSet<S> seenScopes, IRelation<L> lt, IRegExpMatcher<L> re,
            S scope) {
        if(seenScopes.contains(scope) || re.isEmpty()) {
            return EsopEnv.empty(true);
        }
        return env_L(labels, seenImports, seenScopes, lt, re, scope);
    }

    private EsopEnv<S,L,O> env_l(PSet<O> seenImports, PSet<S> seenScopes, IRelation<L> lt, IRegExpMatcher<L> re, L l,
            S scope) {
        return l.getName().equals("D") ? env_D(seenImports, seenScopes, lt, re, l, scope)
                : env_nonD(seenImports, seenScopes, lt, re, l, scope);
    }

    private EsopEnv<S,L,O> env_D(PSet<O> seenImports, PSet<S> seenScopes, IRelation<L> lt, IRegExpMatcher<L> re, L l,
{'fraction_non_alphanumeric': 0.13212004549829381, 'fraction_numerical': 0.004549829381398198, 'mean_word_length': 3.6672111065741118, 'pattern_counts': {'":': 0, '<': 130, '<?xml version=': 0, '>': 142, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6993179', 'n_tokens_mistral': 3929, 'n_tokens_neox': 3838, 'n_words': 667}