package mb.nabl2.spoofax.primitives;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.metaborg.util.concurrent.IClosableLock;
import org.metaborg.util.concurrent.NullClosableLock;
import org.metaborg.util.log.ILogger;
import org.metaborg.util.log.LoggerUtils;
import org.spoofax.interpreter.core.IContext;
import org.spoofax.interpreter.core.InterpreterException;
import org.spoofax.interpreter.library.AbstractPrimitive;
import org.spoofax.interpreter.stratego.Strategy;
import org.spoofax.interpreter.terms.IStrategoTerm;
import org.spoofax.interpreter.terms.ITermFactory;

import mb.nabl2.config.NaBL2Config;
import mb.nabl2.config.NaBL2DebugConfig;
import mb.nabl2.constraints.IConstraint;
import mb.nabl2.solver.Fresh;
import mb.nabl2.solver.ISolution;
import mb.nabl2.spoofax.analysis.CustomSolution;
import mb.nabl2.spoofax.analysis.IScopeGraphContext;
import mb.nabl2.spoofax.analysis.IScopeGraphUnit;
import mb.nabl2.stratego.ConstraintTerms;
import mb.nabl2.stratego.StrategoTerms;
import mb.nabl2.terms.ITerm;

public abstract class ScopeGraphContextPrimitive extends AbstractPrimitive {
    private static final FakeScopeGraphContext FAKE_SCOPE_GRAPH_CONTEXT = new FakeScopeGraphContext();
    private static ILogger logger = LoggerUtils.logger(ScopeGraphContextPrimitive.class);

    public ScopeGraphContextPrimitive(String name, int svars, int tvars) {
        super(name, svars, tvars);
    }

    @Override
    public final boolean call(IContext env, Strategy[] svars, IStrategoTerm[] tvars) throws InterpreterException {
        final Object contextObj = env.contextObject();
        if (contextObj == null) {
            logger.warn("Context is null.");
            return false;
        }
        final IScopeGraphContext<?> context;
        if (!(contextObj instanceof IScopeGraphContext)) {
            context = FAKE_SCOPE_GRAPH_CONTEXT;
        } else {
            context = (IScopeGraphContext<?>) env.contextObject();
        }
        List<IStrategoTerm> termArgs = Arrays.asList(tvars);
        Optional<? extends IStrategoTerm> result;
        try (IClosableLock lock = context.guard()) {
            result = call(context, env.current(), termArgs, env.getFactory());
        }
        return result.map(t -> {
            env.setCurrent(t);
            return true;
        }).orElse(false);
    }

    public Optional<? extends IStrategoTerm> call(IScopeGraphContext<?> context, IStrategoTerm sterm,
            List<IStrategoTerm> sterms, ITermFactory factory) throws InterpreterException {
        StrategoTerms strategoTerms = new StrategoTerms(factory);
        ITerm term = ConstraintTerms.specialize(strategoTerms.fromStratego(sterm));
        List<ITerm> terms = sterms.stream().map(strategoTerms::fromStratego).map(ConstraintTerms::specialize)
                .collect(Collectors.toList());
        Optional<? extends ITerm> result = call(context, term, terms);
        return result.map(ConstraintTerms::explicate).map(strategoTerms::toStratego);
    }

    @SuppressWarnings("unused")
    public Optional<? extends ITerm> call(IScopeGraphContext<?> context, ITerm term, List<ITerm> terms)
            throws InterpreterException {
        throw new UnsupportedOperationException("Subclasses must override call method.");
    }

    private static final class FakeScopeGraphContext implements IScopeGraphContext<IScopeGraphUnit> {
        private static final NaBL2Config CONFIG = new NaBL2Config(false,
                new NaBL2DebugConfig(NaBL2DebugConfig.Flag.ALL));

        IScopeGraphUnit fakeUnit = new IScopeGraphUnit() {
            private static final long serialVersionUID = 1L;
            private Fresh fresh = new Fresh();

            @Override
            public String resource() {
                return null;
            }

            @Override
            public Set<IConstraint> constraints() {
                return Collections.unmodifiableSet(new HashSet<>());
            }

            @Override
            public Optional<ISolution> solution() {
                return Optional.empty();
            }

            @Override
            public Optional<CustomSolution> customSolution() {
                return Optional.empty();
            }

            @Override
            public Fresh fresh() {
                return fresh;
            }

            @Override
            public boolean isPrimary() {
                return false;
            }
        };
        Collection<IScopeGraphUnit> fakeUnits = Collections.unmodifiableList(Arrays.asList(fakeUnit));
        IClosableLock l = new NullClosableLock();

        @Override
        public IClosableLock guard() {
            return l;
        }

        @Override
        public IScopeGraphUnit unit(String resource) {
            return fakeUnit;
        }

        @Override
        public Collection<IScopeGraphUnit> units() {
            return fakeUnits;
        }

        @Override
        public NaBL2Config config() {
            return CONFIG;
        }
    }

}
@@ -60,93 +60,8 @@
 ys;%0A
-import java.util.Collection;%0Aimport java.util.Collections;%0Aimport java.util.HashSet;%0A
 impo
@@ -110,30 +110,8 @@
 al;%0A
-import java.util.Set;%0A
 impo
@@ -198,62 +198,8 @@
 ck;%0A
-import org.metaborg.util.concurrent.NullClosableLock;%0A
 impo
@@ -594,239 +594,8 @@
 y;%0A%0A
-import mb.nabl2.config.NaBL2Config;%0Aimport mb.nabl2.config.NaBL2DebugConfig;%0Aimport mb.nabl2.constraints.IConstraint;%0Aimport mb.nabl2.solver.Fresh;%0Aimport mb.nabl2.solver.ISolution;%0Aimport mb.nabl2.spoofax.analysis.CustomSolution;%0A
 impo
@@ -647,58 +647,8 @@
 xt;%0A
-import mb.nabl2.spoofax.analysis.IScopeGraphUnit;%0A
 impo
@@ -836,111 +836,8 @@
 e %7B%0A
-    private static final FakeScopeGraphContext FAKE_SCOPE_GRAPH_CONTEXT = new FakeScopeGraphContext();%0A
     
@@ -1343,53 +1343,8 @@
   %7D%0A
-        final IScopeGraphContext%3C?%3E context;%0A
     
@@ -1349,17 +1349,16 @@
       if
- 
 (!(conte
@@ -1413,42 +1413,87 @@
     
-context = FAKE_SCOPE_GRAPH_CONTEXT
+throw new InterpreterException(%22Context does not implement IScopeGraphContext%22)
 ;%0A  
@@ -1503,15 +1503,8 @@
    %7D
- else %7B
 %0A   
@@ -1508,19 +1508,43 @@
         
-   
+final IScopeGraphContext%3C?%3E
  context
@@ -1591,26 +1591,16 @@
 ject();%0A
-        %7D%0A
         
@@ -2909,1739 +2909,5 @@
   %7D%0A
-%0A    private static final class FakeScopeGraphContext implements IScopeGraphContext%3CIScopeGraphUnit%3E %7B%0A        private static final NaBL2Config CONFIG = new NaBL2Config(false,%0A                new NaBL2DebugConfig(NaBL2DebugConfig.Flag.ALL));%0A%0A        IScopeGraphUnit fakeUnit = new IScopeGraphUnit() %7B%0A            private static final long serialVersionUID = 1L;%0A            private Fresh fresh = new Fresh();%0A%0A            @Override%0A            public String resource() %7B%0A                return null;%0A            %7D%0A%0A            @Override%0A            public Set%3CIConstraint%3E constraints() %7B%0A                return Collections.unmodifiableSet(new HashSet%3C%3E());%0A            %7D%0A%0A            @Override%0A            public Optional%3CISolution%3E solution() %7B%0A                return Optional.empty();%0A            %7D%0A%0A            @Override%0A            public Optional%3CCustomSolution%3E customSolution() %7B%0A                return Optional.empty();%0A            %7D%0A%0A            @Override%0A            public Fresh fresh() %7B%0A                return fresh;%0A            %7D%0A%0A            @Override%0A            public boolean isPrimary() %7B%0A                return false;%0A            %7D%0A        %7D;%0A        Collection%3CIScopeGraphUnit%3E fakeUnits = Collections.unmodifiableList(Arrays.asList(fakeUnit));%0A        IClosableLock l = new NullClosableLock();%0A%0A        @Override%0A        public IClosableLock guard() %7B%0A            return l;%0A        %7D%0A%0A        @Override%0A        public IScopeGraphUnit unit(String resource) %7B%0A            return fakeUnit;%0A        %7D%0A%0A        @Override%0A        public Collection%3CIScopeGraphUnit%3E units() %7B%0A            return fakeUnits;%0A        %7D%0A%0A        @Override%0A        public NaBL2Config config() %7B%0A            return CONFIG;%0A        %7D%0A    %7D%0A%0A
 %7D

Revert "Use fake scopegraph context when the context is not set up for NaBL2"