package org.metaborg.spoofax.shell.core;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;

import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.metaborg.core.language.ILanguageImpl;
import org.metaborg.meta.lang.dynsem.interpreter.DynSemEntryPoint;
import org.metaborg.meta.lang.dynsem.interpreter.DynSemLanguage;
import org.metaborg.meta.lang.dynsem.interpreter.IDynSemLanguageParser;
import org.metaborg.meta.lang.dynsem.interpreter.ITermRegistry;
import org.metaborg.meta.lang.dynsem.interpreter.nodes.rules.RuleRegistry;
import org.metaborg.meta.lang.dynsem.interpreter.terms.ITerm;
import org.metaborg.meta.lang.dynsem.interpreter.terms.ITermTransformer;
import org.metaborg.spoofax.shell.util.StrategoUtil;
import org.spoofax.interpreter.terms.IStrategoTerm;

import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.vm.PolyglotEngine;

/**
 * Loads an interpreter that is present in the class path. This {@link IInterpreterLoader} uses
 * reflection to load the generated {@link DynSemEntryPoint} and {@link DynSemLanguage} subclasses.
 * It instantiates a {@link PolyglotEngine} with the {@link NonParser} that is provided, by using
 * the supported {@link DynSemLanguage#PARSER configuration parameter}.
 */
public class ClassPathInterpreterLoader implements IInterpreterLoader {
    private String langName;
    private String targetPackage;
    private ITermTransformer transformer;

    @Override
    public PolyglotEngine loadInterpreterForLanguage(ILanguageImpl langImpl)
        throws InterpreterLoadException {
        loadDynSemProperties(langImpl);

        DynSemEntryPoint entryPoint = getEntryPoint();

        transformer = entryPoint.getTransformer();
        IDynSemLanguageParser parser = entryPoint.getParser();
        RuleRegistry ruleRegistry = entryPoint.getRuleRegistry();
        ITermRegistry termRegistry = entryPoint.getTermRegistry();

        String mimeType = entryPoint.getMimeType();
        PolyglotEngine builtEngine =
            PolyglotEngine.newBuilder().config(mimeType, DynSemLanguage.PARSER, parser)
                .config(mimeType, DynSemLanguage.RULE_REGISTRY, ruleRegistry)
                .config(mimeType, DynSemLanguage.TERM_REGISTRY, termRegistry).build();
        try {
            InputStreamReader specTermReader =
                new InputStreamReader(entryPoint.getSpecificationTerm(), "UTF-8");
            builtEngine.eval(Source.fromReader(specTermReader, "Evaluate to interpreter.")
                .withMimeType(mimeType));
        } catch (IOException e) {
            throw new InterpreterLoadException(e);
        }
        return builtEngine;
    }

    @SuppressWarnings("unchecked")
    @Override
    public ITerm getProgramTerm(IStrategoTerm input) throws InterpreterLoadException {
        try {
            String termSort = StrategoUtil.getSortForTerm(input);
            // Get the abstract class for the sort of the term.
            Class<? extends ITerm> generatedTermClass = (Class<? extends ITerm>) ClassUtils
                .getClass(targetPackage + ".terms.I" + termSort + "Term");
            return (ITerm) MethodUtils.invokeStaticMethod(generatedTermClass, "create", input);
        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
                 | InvocationTargetException cause) {
            throw new InterpreterLoadException("Error constructing program term from input.",
                                               cause);
        }
    }

    @Override
    public ITermTransformer getTransformer() {
        return transformer;
    }

    private DynSemEntryPoint getEntryPoint() throws InterpreterLoadException {
        try {
            Class<DynSemEntryPoint> entryPointClass =
                this.<DynSemEntryPoint> getGeneratedClass("EntryPoint");
            return ConstructorUtils.invokeConstructor(entryPointClass);
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
                 | InvocationTargetException e) {
            throw new InterpreterLoadException(e);
        }
    }

    @SuppressWarnings("unchecked")
    private <T> Class<T> getGeneratedClass(String className) throws InterpreterLoadException {
        try {
            return (Class<T>) ClassUtils.getClass(targetPackage + "." + langName + className);
        } catch (ClassNotFoundException e) {
            throw new InterpreterLoadException(e);
        }
    }

    /* Loads the required configurations from the dynsem.properties file parsed as a Properties
     * object. */
    private void loadDynSemProperties(ILanguageImpl langImpl) throws InterpreterLoadException {
        FileObject dynSemPropertiesFile = findDynSemPropertiesFileForLanguage(langImpl);

        Properties dynSemProperties = new Properties();
        InputStream in;
        try {
            in = dynSemPropertiesFile.getContent().getInputStream();
            dynSemProperties.load(in);
        } catch (Exception e) {
            throw new InterpreterLoadException("Error when trying to load \"dynsem.properties\".");
        }

        langName = dynSemProperties.getProperty("source.langname");
        String groupId = dynSemProperties.getProperty("project.groupid");
        String artifactId = dynSemProperties.getProperty("project.artifactid");
        targetPackage = dynSemProperties.getProperty("project.javapackage",
                                                     groupId + '.' + artifactId + ".generated");
    }

    private FileObject findDynSemPropertiesFileForLanguage(ILanguageImpl langImpl)
        throws InterpreterLoadException {
        FileObject dynSemPropertiesFile = null;
        for (FileObject fo : langImpl.locations()) {
            try {
                dynSemPropertiesFile = fo.getChild("dynsem.properties");
                if (dynSemPropertiesFile != null) {
                    break;
                }
            } catch (FileSystemException e) {
                continue;
            }
        }

        if (dynSemPropertiesFile == null) {
            throw new InterpreterLoadException("Missing \"dynsem.properties\" file");
        }
        return dynSemPropertiesFile;
    }
}

@@ -5119,32 +5119,37 @@
 ties();%0A        
+try (
 InputStream in;%0A
@@ -5148,38 +5148,8 @@
 eam 
-in;%0A        try %7B%0A            
 in =
@@ -5199,17 +5199,19 @@
 Stream()
-;
+) %7B
 %0A       
@@ -5242,16 +5242,16 @@
 ad(in);%0A
-
         
@@ -5259,16 +5259,18 @@
  catch (
+IO
 Exceptio

Use try-with-resources to close input stream