package com.redhat.ceylon.compiler.java.runtime.model;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jboss.modules.ModuleClassLoader;

import com.redhat.ceylon.cmr.api.ArtifactResult;
import com.redhat.ceylon.cmr.api.JDKUtils;
import com.redhat.ceylon.cmr.impl.JULLogger;
import com.redhat.ceylon.compiler.loader.impl.reflect.CachedTOCJars;
import com.redhat.ceylon.compiler.loader.impl.reflect.ReflectionModelLoader;
import com.redhat.ceylon.compiler.loader.impl.reflect.mirror.ReflectionClass;
import com.redhat.ceylon.compiler.loader.impl.reflect.mirror.ReflectionUtils;
import com.redhat.ceylon.compiler.loader.mirror.ClassMirror;
import com.redhat.ceylon.compiler.loader.model.LazyModule;
import com.redhat.ceylon.compiler.typechecker.analyzer.ModuleManager;
import com.redhat.ceylon.compiler.typechecker.model.Module;
import com.redhat.ceylon.compiler.typechecker.model.Modules;
import com.redhat.ceylon.compiler.typechecker.model.Unit;

public class RuntimeModelLoader extends ReflectionModelLoader {

    private Map<Module,ClassLoader> classLoaders = new HashMap<Module,ClassLoader>();
    private Map<String, Module> moduleCache = new HashMap<String, Module>();
    private CachedTOCJars jars = new CachedTOCJars();

    public RuntimeModelLoader(ModuleManager moduleManager, Modules modules) {
        super(moduleManager, modules, new JULLogger());
    }

    @Override
    protected List<String> getPackageList(Module module, String packageName) {
        return jars.getPackageList(packageName);
    }

    @Override
    protected boolean packageExists(Module module, String packageName) {
        return jars.packageExists(packageName);
    }

    @Override
    protected Class<?> loadClass(Module module, String name) {
        ClassLoader classLoader = classLoaders.get(module);
        if(classLoader == null){
            if(JDKUtils.isJDKModule(module.getNameAsString())
                    || JDKUtils.isOracleJDKModule(module.getNameAsString())){
                // the JDK does not have class loaders so load it from the root class loader
                try {
                    return ClassLoader.getSystemClassLoader().loadClass(name);
                } catch (ClassNotFoundException e) {
                    return null;
                }
            }
            return null;
        }
        try{
            return classLoader.loadClass(name);
        }catch(ClassNotFoundException x){
            return null;
        }
    }

    @Override
    public void addModuleToClassPath(Module module, ArtifactResult artifact) {
        String cacheKey = cacheKeyByModule(module.getNameAsString(), module.getVersion());
        moduleCache.put(cacheKey, module);
        if(artifact == null)
            return;
        File file = artifact.artifact();
        if(file == null)
            return;
        jars.addJar(file);
        if(module instanceof LazyModule){
            ((LazyModule) module).loadPackageList(artifact);
        }
    }

    public void addModuleClassLoader(Module module, ClassLoader classLoader) {
        classLoaders.put(module, classLoader);
    }

    @Override
    protected Module findModuleForClassMirror(ClassMirror classMirror) {
        Class<?> klass = ((ReflectionClass)classMirror).klass;
        return findModuleForClass(klass);
    }

    public Module findModuleForClass(Class<?> klass){
        ClassLoader cl = klass.getClassLoader();
        if(cl instanceof ModuleClassLoader){
            ModuleClassLoader classLoader = (ModuleClassLoader)cl;
            org.jboss.modules.Module jbossModule = classLoader.getModule();
            String name = jbossModule.getIdentifier().getName();
            String version = jbossModule.getIdentifier().getSlot();
            String cacheKey = cacheKeyByModule(name, version);
            return moduleCache.get(cacheKey);
        }else{
            // revert to a single classloader version?
            // FIXME: perhaps we can have other one-classloader-to-one-module setups than jboss modules?
            String pkgName = ReflectionUtils.getPackageName(klass);
            // even on jboss modules, the jdk has no class loader, so jdk classes will have to be resolved
            // to the jdk modules by package
            String jdkModuleName = JDKUtils.getJDKModuleNameForPackage(pkgName);
            if(jdkModuleName != null)
                return findModule(jdkModuleName, JDK_MODULE_VERSION);
            return lookupModuleInternal(pkgName);
        }
    }
    
    private String cacheKeyByModule(String name, String version) {
        if(name.equals(Module.DEFAULT_MODULE_NAME))
            return name + "/"; // no version
        return name + "/" + version;
    }

    public Unit getUnit() {
        return typeFactory;
    }

}

@@ -937,16 +937,77 @@
 odules;%0A
+import com.redhat.ceylon.compiler.typechecker.model.Package;%0A
 import c
@@ -1473,32 +1473,907 @@
 gger());%0A    %7D%0A%0A
+    @Override%0A    public void loadStandardModules() %7B%0A        // set up the type factory and that's it: do not try to load the language module package before it's set up%0A        // by Metamodel.loadModule%0A        Module languageModule = findOrCreateModule(CEYLON_LANGUAGE, null);%0A        addModuleToClassPath(languageModule, null);%0A        Package languagePackage = findOrCreatePackage(languageModule, CEYLON_LANGUAGE);%0A        typeFactory.setPackage(languagePackage);%0A        %0A        // make sure the jdk modules are loaded because those are not initialised by jboss modules nor the IDE Launcher%0A        for(String jdkModule : JDKUtils.getJDKModuleNames())%0A            findOrCreateModule(jdkModule, JDK_MODULE_VERSION);%0A        for(String jdkOracleModule : JDKUtils.getOracleJDKModuleNames())%0A            findOrCreateModule(jdkOracleModule, JDK_MODULE_VERSION);%0A    %7D%0A    %0A
     @Override%0A  
@@ -5474,24 +5474,270 @@
 %0A    %7D%0A    %0A
+    @Override%0A    protected String cacheKeyByModule(Module module, String name) %7B%0A        if(module == null)%0A            throw new NullPointerException(%22No module found for %22+name);%0A        return super.cacheKeyByModule(module, name);%0A    %7D%0A    %0A
     private 

fix #300 by not loading the language module before it's set up