package net.wizardsoflua.lua.module.blocks;

import com.google.auto.service.AutoService;

import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityShulkerBox;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.sandius.rembulan.Table;
import net.sandius.rembulan.TableFactory;
import net.sandius.rembulan.runtime.ExecutionContext;
import net.sandius.rembulan.runtime.ResolvedControlThrowable;
import net.wizardsoflua.block.ImmutableWolBlock;
import net.wizardsoflua.lua.extension.api.inject.Inject;
import net.wizardsoflua.lua.extension.api.service.Converter;
import net.wizardsoflua.lua.extension.spi.LuaExtension;
import net.wizardsoflua.lua.extension.util.AbstractLuaModule;
import net.wizardsoflua.lua.function.NamedFunction1;

@AutoService(LuaExtension.class)
public class BlocksModule extends AbstractLuaModule {
  @Inject
  private TableFactory tableFactory;
  @Inject
  private Converter converter;

  public BlocksModule() {
    add(new GetFunction());
  }

  @Override
  public String getName() {
    return "Blocks";
  }

  @Override
  public Table getTable() {
    return tableFactory.newTable();
  }

  private ImmutableWolBlock get(String name) {
    Block block = getBlockByName(name);

    IBlockState blockState = block.getDefaultState();

    NBTTagCompound nbt = null;
    if (blockState.getBlock().hasTileEntity(blockState)) {
      World world = null; // This is safe in vanilla MC 1.11.2 as the world value is not used.
      // TODO alternatively pass the 'default' world
      TileEntity tileEntity = blockState.getBlock().createTileEntity(world, blockState);
      nbt = new NBTTagCompound();
      tileEntity.writeToNBT(nbt);
      patch(tileEntity, nbt);
    }
    return new ImmutableWolBlock(blockState, nbt);
  }

  private Block getBlockByName(String blockName) {
    ResourceLocation resourceLocation = new ResourceLocation(blockName);
    if (!Block.REGISTRY.containsKey(resourceLocation)) {
      throw new IllegalArgumentException(
          String.format("Can't find block with name '%s'", blockName));
    }
    Block block = Block.REGISTRY.getObject(resourceLocation);
    return block;
  }

  // This is a work around for the TileEntityShulkerBox, since it doesn't write the Items tag
  // if the items are empty.
  private void patch(TileEntity tileEntity, NBTTagCompound origData) {
    if (tileEntity instanceof TileEntityShulkerBox) {
      if (origData.getTag("Items") == null) {
        origData.setTag("Items", new NBTTagList());
      }
    }
  }

  private class GetFunction extends NamedFunction1 {
    @Override
    public String getName() {
      return "get";
    }

    @Override
    public void invoke(ExecutionContext context, Object arg1) throws ResolvedControlThrowable {
      String name = converter.toJava(String.class, arg1, 1, "name", getName());
      ImmutableWolBlock result = get(name);
      Object luaResult = converter.toLua(result);
      context.getReturnBuffer().setTo(luaResult);
    }
  }
}

@@ -467,153 +467,153 @@
 net.
-sandius.rembulan.TableFactory;%0Aimport net.sandius.rembulan.runtime.ExecutionContext;%0Aimport net.sandius.rembulan.runtime.ResolvedControlThrowable
+wizardsoflua.annotation.GenerateLuaDoc;%0Aimport net.wizardsoflua.annotation.GenerateLuaModuleTable;%0Aimport net.wizardsoflua.annotation.LuaFunction
 ;%0Aim
@@ -880,80 +880,137 @@
 til.
-AbstractLuaModule;%0Aimport net.wizardsoflua.lua.function.NamedFunction1;%0A
+LuaTableExtension;%0A%0A@GenerateLuaModuleTable%0A@GenerateLuaDoc(name = BlocksModule.NAME, subtitle = %22The Building Blocks Directory%22)
 %0A@Au
@@ -1069,182 +1069,125 @@
 ule 
-extends AbstractLuaModule %7B%0A  @Inject%0A  private TableFactory tableFactory;%0A  @Inject%0A  private Converter converter;%0A%0A  public BlocksModule() %7B%0A    add(new GetFunction());%0A  %7D
+implements LuaTableExtension %7B%0A  public static final String NAME = %22Blocks%22;%0A  @Inject%0A  private Converter converter;
 %0A%0A  
@@ -1235,24 +1235,20 @@
  return 
-%22Blocks%22
+NAME
 ;%0A  %7D%0A%0A 
@@ -1301,47 +1301,78 @@
 urn 
-tableFactory.newTable();%0A  %7D%0A%0A  private
+new BlocksModuleTable%3C%3E(this, converter);%0A  %7D%0A%0A  @LuaFunction%0A  public
  Imm
@@ -2741,476 +2741,7 @@
 %0A  %7D
-%0A%0A  private class GetFunction extends NamedFunction1 %7B%0A    @Override%0A    public String getName() %7B%0A      return %22get%22;%0A    %7D%0A%0A    @Override%0A    public void invoke(ExecutionContext context, Object arg1) throws ResolvedControlThrowable %7B%0A      String name = converter.toJava(String.class, arg1, 1, %22name%22, getName());%0A      ImmutableWolBlock result = get(name);%0A      Object luaResult = converter.toLua(result);%0A      context.getReturnBuffer().setTo(luaResult);%0A    %7D%0A  %7D
 %0A%7D%0A

Rewrite BlocksModule