package net.wizardsoflua.lua.extension;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Objects.requireNonNull;

import java.util.ServiceConfigurationError;
import java.util.function.Consumer;

import net.sandius.rembulan.Table;
import net.wizardsoflua.lua.Converters;
import net.wizardsoflua.lua.extension.spi.ConverterExtension;
import net.wizardsoflua.lua.extension.spi.LuaExtension;

public class SpellExtensionLoader
    implements net.wizardsoflua.lua.extension.api.service.LuaExtensionLoader {
  private final ClassIndex extensions = new ClassIndex();
  private final Table env;
  private final ServiceInjector injector;
  private final Converters converters;

  public SpellExtensionLoader(Table env, ServiceInjector injector, Converters converters) {
    this.env = checkNotNull(env, "env == null!");
    this.injector = checkNotNull(injector, "injector == null!");
    this.converters = requireNonNull(converters, "converters == null!");
  }

  public ServiceInjector getInjector() {
    return injector;
  }

  public void installExtensions() {
    ServiceLoader.load(LuaExtension.class).forEach(this::getLuaExtension);
    ServiceLoader.load(ConverterExtension.class).forEach(this::getConverterExtension);
  }

  @Override
  public <E extends LuaExtension> E getLuaExtension(Class<E> extensionClass) {
    return getExtension(extensionClass, extension -> {
      injector.injectServicesInto(extension);
      extension.installInto(env);
    });
  }

  public <E extends ConverterExtension<?, ?>> E getConverterExtension(Class<E> extensionClass) {
    return getExtension(extensionClass, extension -> {
      injector.injectServicesInto(extension);
      converters.addConverterExtension(extension);
    });
  }

  private <E> E getExtension(Class<E> extensionClass, Consumer<E> initializer)
      throws ServiceConfigurationError {
    E extension = extensions.get(extensionClass);
    if (extension == null) {
      extension = newInstance(extensionClass);
      extensions.add(extension);
      initializer.accept(extension);
    }
    return extension;
  }

  private static <P> P newInstance(Class<P> cls) throws ServiceConfigurationError {
    try {
      return cls.newInstance();
    } catch (InstantiationException | IllegalAccessException ex) {
      String message = "Provider " + cls + " could not be instantiated";
      throw new ServiceConfigurationError(LuaExtension.class.getName() + ": " + message, ex);
    }
  }
}

@@ -195,44 +195,8 @@
 ror;
-%0Aimport java.util.function.Consumer;
 %0A%0Aim
@@ -558,16 +558,144 @@
 ndex();%0A
+  private final ClassIndex converterExtensions = new ClassIndex();%0A  private final ClassIndex luaExtensions = new ClassIndex();%0A
   privat
@@ -1187,83 +1187,8 @@
 ) %7B%0A
-    ServiceLoader.load(LuaExtension.class).forEach(this::getLuaExtension);%0A
     
@@ -1276,22 +1276,85 @@
 ;%0A  
-%7D%0A%0A  @Override
+  ServiceLoader.load(LuaExtension.class).forEach(this::getLuaExtension);%0A  %7D%0A
 %0A  p
@@ -1370,19 +1370,25 @@
 extends 
-Lua
+Converter
 Extensio
@@ -1392,18 +1392,30 @@
 sion
+%3C?, ?%3E
 %3E E get
-Lua
+Converter
 Exte
@@ -1447,34 +1447,47 @@
 lass) %7B%0A    
-return get
+E extension = converter
 Extension(ex
@@ -1475,32 +1475,37 @@
 nverterExtension
+s.get
 (extensionClass,
@@ -1495,34 +1495,43 @@
 t(extensionClass
-, 
+);%0A    if (
 extension -%3E %7B%0A 
@@ -1516,34 +1516,40 @@
   if (extension 
--%3E
+== null)
  %7B%0A      injecto
@@ -1545,35 +1545,32 @@
     
-injector.injectServicesInto
+extension = getExtension
 (ext
@@ -1567,32 +1567,37 @@
 ension(extension
+Class
 );%0A      extensi
@@ -1593,48 +1593,139 @@
     
-extension.installInto(env);%0A    %7D);%0A  %7D%0A
+converterExtensions.add(extension);%0A      converters.addConverterExtension(extension);%0A    %7D%0A    return extension;%0A  %7D%0A%0A  @Override
 %0A  p
@@ -1737,33 +1737,27 @@
  %3CE extends 
-Converter
+Lua
 Extension%3C?,
@@ -1753,38 +1753,26 @@
 xtension
-%3C?, ?%3E
 %3E E get
-Converter
+Lua
 Extensio
@@ -1808,18 +1808,25 @@
     
-return get
+E extension = lua
 Exte
@@ -1822,32 +1822,37 @@
 n = luaExtension
+s.get
 (extensionClass,
@@ -1850,18 +1850,27 @@
 ionClass
-, 
+);%0A    if (
 extensio
@@ -1875,10 +1875,16 @@
 ion 
--%3E
+== null)
  %7B%0A 
@@ -1892,35 +1892,32 @@
     
-injector.injectServicesInto
+extension = getExtension
 (ext
@@ -1914,32 +1914,37 @@
 ension(extension
+Class
 );%0A      convert
@@ -1940,59 +1940,98 @@
     
-converters.addConverterExtension(extension);%0A    %7D)
+luaExtensions.add(extension);%0A      extension.installInto(env);%0A    %7D%0A    return extension
 ;%0A  
@@ -2089,40 +2089,9 @@
 lass
-, Consumer%3CE%3E initializer)%0A     
+)
  thr
@@ -2293,24 +2293,33 @@
   in
-itializer.accept
+jector.injectServicesInto
 (ext

Fix initialization of hybrid extensions