package org.metaborg.meta.lang.dynsem.interpreter;

import java.io.IOException;
import java.util.concurrent.Callable;

import org.apache.commons.vfs2.FileObject;
import org.metaborg.core.MetaborgException;
import org.metaborg.core.action.EndNamedGoal;
import org.metaborg.core.action.ITransformGoal;
import org.metaborg.core.context.IContext;
import org.metaborg.core.language.ILanguageImpl;
import org.metaborg.core.project.IProject;
import org.metaborg.meta.lang.dynsem.interpreter.nabl2.NaBL2Context;
import org.metaborg.meta.lang.dynsem.interpreter.nodes.rules.RuleResult;
import org.metaborg.spoofax.core.Spoofax;
import org.metaborg.spoofax.core.context.scopegraph.ISpoofaxScopeGraphContext;
import org.metaborg.spoofax.core.shell.CLIUtils;
import org.metaborg.spoofax.core.unit.ISpoofaxAnalyzeUnit;
import org.metaborg.spoofax.core.unit.ISpoofaxInputUnit;
import org.metaborg.spoofax.core.unit.ISpoofaxParseUnit;
import org.metaborg.util.concurrent.IClosableLock;
import org.spoofax.interpreter.terms.IStrategoTerm;

import com.google.common.collect.ImmutableMap;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;

public class DynSemRunner {
	private final Spoofax S;
	private final CLIUtils cli;
	private final ILanguageImpl language;
	private final DynSemVM vm;

	@TruffleBoundary
	public DynSemRunner(Spoofax S, String languageName, DynSemVM vm) throws MetaborgException {
		this.S = S;
		this.cli = new CLIUtils(S);
		cli.loadLanguagesFromPath();
		this.language = cli.getLanguage(languageName);
		this.vm = vm;
	}

	public Object run(FileObject file) throws MetaborgException {
		final RunConfig runCfg = prepareForEvaluation(file);
		try {
			Callable<RuleResult> runner = vm.getCallable(runCfg.program, runCfg.props);
			RuleResult result = runner.call();
			return result.result;
		} catch (Exception e) {
			throw new MetaborgException("Evaluation failed.", e);
		}
	}

	@TruffleBoundary
	private RunConfig prepareForEvaluation(FileObject file) throws MetaborgException {
		CompilerAsserts.neverPartOfCompilation();
		IStrategoTerm program;
		ImmutableMap<String, Object> props;
		try {
			IProject project = cli.getProject(file);

			String text = S.sourceTextService.text(file);
			ISpoofaxInputUnit input = S.unitService.inputUnit(file, text, language, null);
			ISpoofaxParseUnit parsed = S.syntaxService.parse(input);
			if (!parsed.valid()) {
				throw new MetaborgException("Parsing failed.");
			}
			cli.printMessages(vm.getContext().getErr(), parsed.messages());
			if (!parsed.success()) {
				throw new MetaborgException("Parsing returned errors.");
			}

			IContext context = S.contextService.get(file, project, language);
			if (S.analysisService.available(language)) {
				ISpoofaxAnalyzeUnit analyzed;
				try (IClosableLock lock = context.write()) {
					analyzed = S.analysisService.analyze(parsed, context).result();
				}
				if (!analyzed.valid()) {
					throw new MetaborgException("Analysis failed.");
				}
				cli.printMessages(vm.getContext().getErr(), analyzed.messages());
				if (!analyzed.success()) {
					throw new MetaborgException("Analysis returned errors.");
				}
				ImmutableMap.Builder<String, Object> propBuilder = ImmutableMap.builder();
				if (context instanceof ISpoofaxScopeGraphContext) {
					ISpoofaxScopeGraphContext<?> scopeGraphContext = (ISpoofaxScopeGraphContext<?>) context;
					scopeGraphContext.unit(file.getName().getURI()).solution().ifPresent(solution -> {
						propBuilder.put(NaBL2Context.class.getName(),
								new NaBL2Context(solution.findAndLock(), S.termFactoryService.getGeneric()));
					});
				}
				props = propBuilder.build();
				if (analyzed.hasAst()) {
					ITransformGoal mkoccgoal = new EndNamedGoal("Make Occurrences");
					if (S.transformService.available(context, mkoccgoal)) {
						program = S.transformService.transform(analyzed, context, mkoccgoal).iterator().next().ast();
					} else {
						program = analyzed.ast();
					}
				} else {
					program = parsed.ast();
				}
			} else {
				program = parsed.ast();
				props = ImmutableMap.of();
			}
		} catch (IOException e) {
			throw new MetaborgException("Analysis failed.", e);
		}
		return new RunConfig(program, props);
	}

	private class RunConfig {
		protected final IStrategoTerm program;
		protected final ImmutableMap<String, Object> props;

		public RunConfig(IStrategoTerm program, ImmutableMap<String, Object> props) {
			this.program = program;
			this.props = props;
		}
	}

}
@@ -657,37 +657,30 @@
 ext.
-s
 co
-pegraph.ISpoofaxScopeGraph
+nstraint.IConstraint
 Cont
@@ -1171,16 +1171,98 @@
 ndary;%0A%0A
+import mb.nabl2.spoofax.analysis.IResult;%0Aimport mb.nabl2.stratego.StrategoBlob;%0A%0A
 public c
@@ -3361,119 +3361,92 @@
 of I
-SpoofaxScopeGraphContext) %7B%0A%09%09%09%09%09ISpoofaxScopeGraphContext%3C?%3E scopeGraphContext = (ISpoofaxScopeGraph
+ConstraintContext) %7B%0A%09%09%09%09%09IConstraintContext constraintContext = (IConstraint
 Context
-%3C?%3E
 ) co
@@ -3461,65 +3461,175 @@
 %09%09%09%09
-scopeGraphContext.unit(file.getName().getURI()).solution(
+if (constraintContext.hasAnalysis(file)) %7B%0A%09%09%09%09%09%09IStrategoTerm analysisTerm = constraintContext.getAnalysis(file);%0A%09%09%09%09%09%09StrategoBlob.match(analysisTerm, IResult.class
 ).if
@@ -3640,22 +3640,16 @@
 ent(
-solution
+r
  -%3E %7B%0A
+%09
 %09%09%09%09
@@ -3704,16 +3704,17 @@
 %09%09%09%09%09%09%09%09
+%09
 new NaBL
@@ -3726,16 +3726,18 @@
 ext(
+r.
 solution
 .fin
@@ -3736,20 +3736,8 @@
 tion
-.findAndLock
 (), 
@@ -3782,12 +3782,20 @@
 %09%09%09%09
+%09
 %7D);%0A
+%09%09%09%09%09%7D%0A
 %09%09%09%09

Update runner for new constraint context.