package net.wizardsoflua.lua.module.types;

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

import javax.annotation.Nullable;

import net.sandius.rembulan.ByteString;
import net.sandius.rembulan.Table;
import net.sandius.rembulan.runtime.LuaFunction;
import net.wizardsoflua.lua.classes.CustomLuaClass;
import net.wizardsoflua.lua.classes.JavaLuaClass;
import net.wizardsoflua.lua.classes.LuaClass;
import net.wizardsoflua.lua.classes.LuaClassLoader;
import net.wizardsoflua.lua.classes.ObjectClass;
import net.wizardsoflua.lua.classes.common.Delegator;

@Deprecated
public class Types {
  public static final String NIL_META = "nil";
  public static final String BOOLEAN_META = "boolean";
  public static final String FUNCTION_META = "function";
  public static final String NUMBER_META = "number";
  public static final String STRING_META = "string";
  public static final String TABLE_META = "table";

  private final Table env;
  private final LuaClassLoader classLoader;

  public Types(Table env, LuaClassLoader classLoader) {
    this.env = checkNotNull(env, "env == null!");
    this.classLoader = requireNonNull(classLoader, "classLoader == null!");
  }

  /**
   * Declares a new {@link LuaClass} with the specified name and the optional superclass meta table.
   *
   * @param luaClassName
   * @param superClassMetaTable
   */
  public void declareClass(String luaClassName, @Nullable Table superClassMetaTable) {
    requireNonNull(luaClassName, "luaClassName == null!");
    checkState(env.rawget(luaClassName) == null,
        "bad argument #%s: a global variable with name '%s' is already defined", 1, luaClassName);

    LuaClass superClass;
    if (superClassMetaTable != null) {
      superClass = classLoader.getLuaClassForClassTable(superClassMetaTable);
      checkArgument(superClass != null,
          "The table '%s' does not represent a LuaClass loaded by this LuaClassLoader",
          superClassMetaTable);
    } else {
      superClass = classLoader.getLuaClassOfType(ObjectClass.class);
    }
    classLoader.load(new CustomLuaClass(luaClassName, superClass));
  }

  /**
   * Returns {@code true} if the specified Lua object is an instance of the Lua class represented by
   * the specified class meta table.
   *
   * @param classMetaTable
   * @param luaObject
   * @return {@code true} if the specified Lua object is an instance of the Lua class
   */
  public boolean isInstanceOf(Table classMetaTable, Object luaObject) {
    if (luaObject == null) {
      return false;
    }
    if (!(luaObject instanceof Table)) {
      return false;
    }
    Table actualMetaTable = ((Table) luaObject).getMetatable();
    return actualMetaTable != null
        && (actualMetaTable == classMetaTable || isInstanceOf(classMetaTable, actualMetaTable));
  }

  /**
   * Returns the Lua type name of the given Lua object.
   *
   * @param luaObject
   * @return the Lua type name of the given Lua object
   */
  public @Nullable String getTypename(@Nullable Object luaObject) {
    if (luaObject == null) {
      return NIL_META;
    }
    if (luaObject instanceof Table) {
      Table table = (Table) luaObject;
      String classname = getClassname(table);
      if (classname != null) {
        return classname;
      }
    }
    return getTypename(luaObject.getClass());
  }

  public @Nullable String getTypename(Class<?> type) {
    if (Delegator.class.isAssignableFrom(type)) {
      @SuppressWarnings("unchecked")
      Class<? extends Delegator<?>> delegatorClass = (Class<? extends Delegator<?>>) type;
      type = Delegator.getDelegateClassOf(delegatorClass);
    }
    JavaLuaClass<?, ?> luaClass = classLoader.getLuaClassForJavaClass(type);
    if (luaClass != null) {
      return luaClass.getName();
    }
    if (Table.class.isAssignableFrom(type)) {
      return TABLE_META;
    }
    if (ByteString.class.isAssignableFrom(type) || String.class.isAssignableFrom(type)) {
      return STRING_META;
    }
    if (Number.class.isAssignableFrom(type)) {
      return NUMBER_META;
    }
    if (Boolean.class.isAssignableFrom(type)) {
      return BOOLEAN_META;
    }
    if (LuaFunction.class.isAssignableFrom(type)) {
      return FUNCTION_META;
    }
    return type.getName();
  }

  /**
   * Returns the name of the {@link LuaClass} of the specified {@link Table} or {@code null} if the
   * {@link Table} is not an instance of a {@link LuaClass}.
   *
   * @param table
   * @return the name of the {@link LuaClass} of the specified {@link Table} or {@code null}
   */
  public @Nullable String getClassname(Table table) {
    LuaClass luaClass = classLoader.getLuaClassOfInstance(table);
    if (luaClass != null) {
      return luaClass.getName();
    }
    return null;
  }
}

@@ -3606,186 +3606,27 @@
     
-@SuppressWarnings(%22unchecked%22)%0A      Class%3C? extends Delegator%3C?%3E%3E delegatorClass = (Class%3C? extends Delegator%3C?%3E%3E) type;%0A      type = Delegator.getDelegateClassOf(delegatorClass
+type = unproxy(type
 );%0A 
@@ -4243,32 +4243,266 @@
 getName();%0A  %7D%0A%0A
+  private %3CT%3E Class%3CT%3E unproxy(Class%3C?%3E type) %7B%0A    @SuppressWarnings(%22unchecked%22)%0A    Class%3C? extends Delegator%3CT%3E%3E delegatorClass = (Class%3C? extends Delegator%3CT%3E%3E) type;%0A    return Delegator.getDelegateClassOf(delegatorClass);%0A  %7D%0A%0A
   /**%0A   * Retur

Fix generics for JDK