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.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.ITermRegistry;
import org.metaborg.meta.lang.dynsem.interpreter.nodes.rules.RuleRegistry;

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 targetPackage;

    @Override
    public PolyglotEngine loadInterpreterForLanguage(ILanguageImpl langImpl)
        throws InterpreterLoadException {
        Properties dynSemProperties = dynSemProperties(langImpl);

        DynSemEntryPoint entryPoint = getEntryPoint(dynSemProperties);

        targetPackage = dynSemProperties.getProperty("target.package");
        RuleRegistry ruleRegistry = entryPoint.getRuleRegistry();
        ITermRegistry termRegistry = entryPoint.getTermRegistry();

        String mimeType = entryPoint.getMimeType();
        PolyglotEngine builtEngine =
            PolyglotEngine.newBuilder().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;
    }

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

    @Override
    public String getTargetPackage() {
        return targetPackage;
    }

    @SuppressWarnings("unchecked")
    private <T> Class<T> getGeneratedClass(Properties dynSemProperties, String className)
        throws InterpreterLoadException {
        String targetPackage = dynSemProperties.getProperty("target.package");
        String langName = dynSemProperties.getProperty("source.langname");
        try {
            return (Class<T>) ClassUtils.getClass(targetPackage + "." + langName + className);
        } catch (ClassNotFoundException e) {
            throw new InterpreterLoadException(e);
        }
    }

    /* Returns the dynsem.properties file parsed as a Properties object. */
    private Properties dynSemProperties(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) {
                // Keep looking.
            }
        }

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

        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\".");
        }
        return dynSemProperties;
    }
}

@@ -1266,16 +1266,45 @@
 oader %7B%0A
+    private String langName;%0A
     priv
@@ -1475,39 +1475,13 @@
     
-Properties dynSemProperties = d
+loadD
 ynSe
@@ -1560,99 +1560,11 @@
 int(
-dynSemProperties);%0A%0A        targetPackage = dynSemProperties.getProperty(%22target.package%22
 );
+%0A
 %0A   
@@ -2425,35 +2425,8 @@
 int(
-Properties dynSemProperties
 )%0A  
@@ -2591,34 +2591,16 @@
 edClass(
-dynSemProperties, 
 %22EntryPo
@@ -3057,37 +3057,8 @@
 ass(
-Properties dynSemProperties, 
 Stri
@@ -3117,162 +3117,8 @@
 n %7B%0A
-        String targetPackage = dynSemProperties.getProperty(%22target.package%22);%0A        String langName = dynSemProperties.getProperty(%22source.langname%22);%0A
     
@@ -3346,15 +3346,46 @@
  /* 
-Returns
+Loads the required configurations from
  the
@@ -3430,16 +3430,23 @@
 operties
+%0A     *
  object.
@@ -3461,28 +3461,26 @@
 private 
-Properties d
+void loadD
 ynSemPro
@@ -4419,32 +4419,33 @@
 ;%0A        %7D%0A
+%0A
         
 return dynSe
@@ -4436,31 +4436,233 @@
     
-return dynSemProperties
+langName = dynSemProperties.getProperty(%22source.langname%22);%0A        targetPackage = dynSemProperties.getProperty(%22project.javapackage%22,%0A                                                     langName + %22.interpreter.generated%22)
 ;%0A  

Update to new dynsem.properties format