public class WikiRegistry{

    private static List<Map.Entry<String, ItemStack>> itemAndBlockPageEntries = new ArrayList<Map.Entry<String, ItemStack>>();
    private static Map<Class<? extends Entity>, String> entityPageEntries = new HashMap<Class<? extends Entity>, String>();
    public static List<IRecipeIntegrator> recipeIntegrators = new ArrayList<IRecipeIntegrator>();
    public static List<ITextInterpreter> textInterpreters = new ArrayList<ITextInterpreter>();
    private static IWikiHooks wikiHooks = new WikiHooks();

    public static void registerWikiTab(IWikiTab tab){
        if(tab instanceof ServerWikiTab) {
            for(IWikiTab t : GuiWiki.wikiTabs) {
                if(t instanceof ServerWikiTab) {
                    GuiWiki.wikiTabs.remove(t);
                    break;
                }
            }
            GuiWiki.wikiTabs.add(0, tab);
        } else {
            GuiWiki.wikiTabs.add(tab);
        }
    }

    public static void registerBlockAndItemPageEntry(ItemStack stack){
        if(stack.isEmpty() || stack.getItem() == null) throw new IllegalArgumentException("Can't register null items");
        registerBlockAndItemPageEntry(stack, stack.getUnlocalizedName().replace("tile.", "block/").replace("item.", "item/"));
    }

    public static void registerBlockAndItemPageEntry(Block block, String page){
        registerBlockAndItemPageEntry(new ItemStack(block, 1, OreDictionary.WILDCARD_VALUE), page);
    }

    public static void registerBlockAndItemPageEntry(Item item, String page){
        registerBlockAndItemPageEntry(new ItemStack(item, 1, OreDictionary.WILDCARD_VALUE), page);
    }

    public static void registerBlockAndItemPageEntry(ItemStack stack, String page){
        itemAndBlockPageEntries.add(new AbstractMap.SimpleEntry(page, stack));
    }

    public static void registerEntityPageEntry(Class<? extends Entity> entityClass){
        //        registerEntityPageEntry(entityClass, "entity/" + EntityList.CLASS_TO_NAME.get(entityClass));
        registerEntityPageEntry(entityClass, "entity/" + EntityList.getKey(entityClass).getResourcePath());
    }

    public static void registerEntityPageEntry(Class<? extends Entity> entityClass, String page){
        entityPageEntries.put(entityClass, page);
    }

    public static void registerRecipeIntegrator(IRecipeIntegrator recipeIntegrator){
        recipeIntegrators.add(recipeIntegrator);
    }

    public static void registerTextInterpreter(ITextInterpreter textInterpreter){
        textInterpreters.add(textInterpreter);
    }

    public static String getPageForItemStack(ItemStack stack){
        for(Map.Entry<String, ItemStack> entry : itemAndBlockPageEntries) {
            if(entry.getValue().isItemEqual(stack) && ItemStack.areItemStackTagsEqual(entry.getValue(), stack)) return entry.getKey();
        }
        for(Map.Entry<String, ItemStack> entry : itemAndBlockPageEntries) {
            if(OreDictionary.itemMatches(entry.getValue(), stack, false)) return entry.getKey();
        }
        return null;
    }

    public static String getPageForEntityClass(Class<? extends Entity> entityClass){
        String page = entityPageEntries.get(entityClass);
        if(page != null) {
            return page;
        } else {
            ResourceLocation entityName = EntityList.getKey(entityClass);
            return entityName == null ? null : "entity/" + entityName.getResourcePath();
        }
    }

    public static List<ItemStack> getItemAndBlockPageEntries(){
        //        List<ItemStack> entries = new ArrayList<ItemStack>();
        NonNullList<ItemStack> entries = NonNullList.create();
        for(Map.Entry<String, ItemStack> entry : itemAndBlockPageEntries) {
            if(entry.getValue().getItemDamage() == OreDictionary.WILDCARD_VALUE) {
                entry.getValue().getItem().getSubItems(CreativeTabs.SEARCH, entries);
            } else {
                entries.add(entry.getValue());
            }
        }
        return entries;
    }

    public static List<Class<? extends Entity>> getEntityPageEntries(){
        List<Class<? extends Entity>> entries = new ArrayList<Class<? extends Entity>>();
        for(Class<? extends Entity> entityClass : entityPageEntries.keySet()) {
            entries.add(entityClass);
        }
        return entries;
    }

    public static IWikiHooks getWikiHooks(){
        return wikiHooks;
    }
} public class GuiWiki extends GuiContainer{
    private static String currentFile = ""; //path (ResourceLocation) of the current wikipage
    private static List<String> fileInfo = new ArrayList<String>(); //The raw info directly retrieved from the .txt file.

    public static List<IWikiTab> wikiTabs = new ArrayList<IWikiTab>();//A list of all the tabs registered.
    private static IWikiTab currentTab;
    private static int currentTabPage = 0;
    private static String currentModIdPage = "igwmod";

    private static List<IPageLink> visibleWikiPages = new ArrayList<IPageLink>();
    private static int matchingWikiPages;

    private static final List<LocatedStack> locatedStacks = new ArrayList<LocatedStack>();
    private static final List<LocatedString> locatedStrings = new ArrayList<LocatedString>();
    private static final List<IWidget> locatedTextures = new ArrayList<IWidget>();

    private static final ResourceLocation scrollbarTexture = new ResourceLocation("textures/gui/container/creative_inventory/tabs.png");

    private static float currentPageLinkScroll;
    private static float currentPageScroll;
    private static int currentPageTranslation;

    /** True if the scrollbar is being dragged */
    private boolean isScrollingPageLink;
    private boolean isScrollingPage;
    private boolean wasClicking;
    private int lastMouseX;
    private int oldGuiScale;

    private GuiScreen prevGui;
    private GuiButton previousButton, nextButton;

    private static GuiTextField searchField;

    private static final int PAGE_LINK_SCROLL_X = 80;
    private static final int PAGE_LINK_SCROLL_HEIGHT = 214;
    private static final int PAGE_LINK_SCROLL_Y = 14;

    private static final int PAGE_SCROLL_X = 240;
    private static final int PAGE_SCROLL_HEIGHT = 230;
    private static final int PAGE_SCROLL_Y = 4;

    public static final double TEXT_SCALE = 0.5D;

    public static final int MAX_TEXT_Y = 453;
    public static final int MIN_TEXT_Y = 10;

    public GuiWiki(){
        super(new ContainerBlockWiki());
        allowUserInput = true;
        ySize = 238;
        xSize = 256;
        if(currentTab == null) currentTab = wikiTabs.get(0);
    }

    @Override
    public void initGui(){
        if(mc.gameSettings.guiScale != 0) {
            oldGuiScale = mc.gameSettings.guiScale;
            mc.gameSettings.guiScale = 0;
            mc.displayGuiScreen(this);
        } else {
            super.initGui();
            buttonList.clear();
            Keyboard.enableRepeatEvents(true);

            String lastSearch = "";
            if(searchField != null) lastSearch = searchField.getText();
            searchField = new GuiTextField(0, fontRenderer, guiLeft + 40, guiTop + currentTab.getSearchBarAndScrollStartY(), 53, fontRenderer.FONT_HEIGHT);
            searchField.setMaxStringLength(15);
            searchField.setEnableBackgroundDrawing(true);
            searchField.setVisible(true);
            searchField.setFocused(false);
            searchField.setCanLoseFocus(true);
            searchField.setText(lastSearch);
            updateSearch();

            previousButton = new GuiButton(0, guiLeft + 40, guiTop + 4, 25, 10, "<--");
            nextButton = new GuiButton(1, guiLeft + 68, guiTop + 4, 25, 10, "-->");
            previousButton.enabled = BrowseHistory.canGoPrevious();
            nextButton.enabled = BrowseHistory.canGoNext();
            buttonList.add(previousButton);
            buttonList.add(nextButton);
        }
    }

    public FontRenderer getFontRenderer(){
        return fontRenderer;
    }

    @Override
    public int getGuiLeft(){
        return guiLeft;
    }

    @Override
    public int getGuiTop(){
        return guiTop;
    }

    public void setPreviousScreen(GuiScreen gui){
        prevGui = gui;
    }

    @Override
    public void actionPerformed(GuiButton button){
        BrowseHistory history;
        if(button.id == 0) {
            history = BrowseHistory.previous();
        } else {
            history = BrowseHistory.next();
        }
        currentFile = history.link;
        currentTab = history.tab;
        currentTabPage = getPageNumberForTab(currentTab);
        currentTab.onPageChange(this, currentFile, history.meta);
        updateWikiPage(history.meta);
        updateSearch();
        initGui();//update the textfield location.
        currentPageScroll = history.scroll;
        updatePageScrolling();
    }

    /**
     * Called when the screen is unloaded. Used to disable keyboard repeat events
     */
    @Override
    public void onGuiClosed(){
        super.onGuiClosed();
        Keyboard.enableRepeatEvents(false);
        mc.gameSettings.guiScale = oldGuiScale;
    }

    @Override
    protected void handleMouseClick(Slot slot, int slotId, int mouseButton, ClickType type){
        if(slot != null && slot.getHasStack()) {
            setCurrentFile(slot.getStack());
        }
    }

    @Override
    protected void mouseClicked(int x, int y, int button) throws IOException{
        super.mouseClicked(x, y, button);
        searchField.mouseClicked(x, y, button);
        if(searchField.isFocused() && button == 1) {
            searchField.setText("");
            currentPageLinkScroll = 0;
            updateSearch();
        }

        List<IWikiTab> visibleTabs = getVisibleTabs();
        for(int i = 0; i < visibleTabs.size(); i++) {
            if(x <= 33 + guiLeft && x >= 1 + guiLeft && y >= 8 + guiTop + i * 35 && y <= 43 + guiTop + i * 35) {
                currentTab = visibleTabs.get(i);
                currentPageLinkScroll = 0;
                updateSearch();
                initGui();//update the textfield location.
                break;
            }
        }

        for(IPageLink link : visibleWikiPages) {
            if(link.onMouseClick(this, -guiLeft + x, -guiTop + y)) return;
        }
        for(LocatedString link : locatedStrings) {
            if(link.onMouseClick(this, -guiLeft + x, -guiTop + y)) return;
        }

        if(hasMultipleTabPages() && x < 33 + guiLeft && x >= 1 + guiLeft && y >= 214 + guiTop && y <= 236 + guiTop) {
            if(button == 0) {
                if(++currentTabPage >= getTotalTabPages()) currentTabPage = 0;
            } else if(button == 1) {
                if(--currentTabPage < 0) currentTabPage = getTotalTabPages() - 1;
            }
        }
    }

    public void setCurrentFile(World world, BlockPos pos){
        BlockWikiEvent wikiEvent = new BlockWikiEvent(world, pos);
        if(wikiEvent.drawnStack.getItem() == null) return;
        wikiEvent.pageOpened = WikiRegistry.getPageForItemStack(wikiEvent.drawnStack);
        if(wikiEvent.pageOpened == null) wikiEvent.pageOpened = wikiEvent.drawnStack.getUnlocalizedName().replace("tile.", "block/").replace("item.", "item/");
        MinecraftForge.EVENT_BUS.post(wikiEvent);
        setCurrentFile(wikiEvent.pageOpened, wikiEvent.drawnStack);
    }

    public void setCurrentFile(Entity entity){
        EntityWikiEvent wikiEvent = new EntityWikiEvent(entity);
        wikiEvent.pageOpened = WikiRegistry.getPageForEntityClass(entity.getClass());
        MinecraftForge.EVENT_BUS.post(wikiEvent);
        setCurrentFile(wikiEvent.pageOpened, entity);
    }

    public void setCurrentFile(ItemStack stack){
        String defaultName = WikiRegistry.getPageForItemStack(stack);
        if(defaultName == null) defaultName = stack.getUnlocalizedName().replace("tile.", "block/").replace("item.", "item/");
        ItemWikiEvent wikiEvent = new ItemWikiEvent(stack, defaultName);
        MinecraftForge.EVENT_BUS.post(wikiEvent);
        if(stack != null) {
            stack = stack.copy();
            //            stack.stackSize = 1;
            stack.setCount(1);
        }

        setCurrentFile(wikiEvent.pageOpened, stack);
    }

    public void setCurrentFile(String file, Object... metadata){
        if(file == null) return;

        BrowseHistory.updateHistory(currentPageScroll);
        if(metadata.length == 0) {
            ItemStack displayedStack = WikiUtils.getStackFromName(file);
            if(!displayedStack.isEmpty()) metadata = new Object[]{displayedStack};
        }
        currentFile = file;
        IWikiTab tab = getTabForPage(currentFile);
        if(tab != null) currentTab = tab;
        currentTabPage = getPageNumberForTab(currentTab);
        currentTab.onPageChange(this, file, metadata);
        updateWikiPage(metadata);
        updateSearch();
        BrowseHistory.addHistory(file, currentTab, metadata);
        initGui();//update the textfield location.
    }

    /**
     * Fired when a key is typed. This is the equivalent of KeyListener.keyTyped(KeyEvent e).
     */
    @Override
    protected void keyTyped(char par1, int keyCode) throws IOException{
        if(searchField.textboxKeyTyped(par1, keyCode)) {
            currentPageLinkScroll = 0;
            updateSearch();
        } else {
            if(keyCode == ClientProxy.openInterfaceKey.getKeyCode() || keyCode == 1) {
                FMLClientHandler.instance().showGuiScreen(prevGui);
            } else {
                super.keyTyped(par1, keyCode);
            }
        }
    }

    private void updateSearch(){
        List<IPageLink> pages = currentTab.getPages(null);//request all pages.

        if(pages != null) {
            List<Integer> matchingIndexes = new ArrayList<Integer>();
            for(int i = 0; i < pages.size(); i++) {
                if(searchField.getText().toLowerCase().equals("")) {
                    matchingIndexes.add(i);
                } else if(pages.get(i).getName().toLowerCase().contains(searchField.getText().toLowerCase()) && !(pages.get(i) instanceof LocatedSectionString)) {
                    matchingIndexes.add(i);
                }
            }
            matchingWikiPages = matchingIndexes.size();
            int firstListedPageIndex = (int)(getScrollStates() * currentPageLinkScroll + 0.5F) * currentTab.pagesPerScroll();
            int[] indexes = new int[Math.min(Math.min(matchingIndexes.size() - firstListedPageIndex, matchingIndexes.size()), currentTab.pagesPerTab())];
            for(int i = 0; i < indexes.length; i++) {
                indexes[i] = matchingIndexes.get(firstListedPageIndex + i);
            }
            visibleWikiPages = currentTab.getPages(indexes);
        } else {
            visibleWikiPages = new ArrayList<IPageLink>();
            matchingWikiPages = 0;
        }
        ((ContainerBlockWiki)inventorySlots).updateStacks(locatedStacks, visibleWikiPages);
    }

    private void updatePageScrolling(){
        int translation = -(int)(currentPageScroll * getMaxPageTranslation() / 2 + 0.5F) * 2 - currentPageTranslation;
        currentPageTranslation += translation;
        for(LocatedStack stack : locatedStacks) {
            stack.setY(stack.getY() + translation / 2);
        }
        for(LocatedString string : locatedStrings) {
            string.setY(string.getY() + translation);
        }
        for(IWidget image : locatedTextures) {
            image.setY(image.getY() + translation);
        }
        ((ContainerBlockWiki)inventorySlots).updateStacks(locatedStacks, visibleWikiPages);
    }

    private int getMaxPageTranslation(){
        int maxTranslation = -100000;
        for(IWidget texture : locatedTextures) {
            maxTranslation = Math.max(maxTranslation, texture.getY() + texture.getHeight());
        }
        for(LocatedString string : locatedStrings) {
            maxTranslation = Math.max(maxTranslation, string.getY() + fontRenderer.FONT_HEIGHT);
        }
        return Math.max(maxTranslation - currentPageTranslation - MAX_TEXT_Y, 0);
    }

    private boolean needsPageLinkScrollBars(){
        return matchingWikiPages > currentTab.pagesPerTab();
    }

    private boolean needsPageScrollBars(){
        return getMaxPageTranslation() > 0;
    }

    @Override
    public void handleMouseInput() throws IOException{
        super.handleMouseInput();
        int i = Mouse.getEventDWheel();

        if(i != 0) {
            if(i > 0) {
                i = 1;
            }

            if(i < 0) {
                i = -1;
            }

            if(lastMouseX < PAGE_LINK_SCROLL_X + guiLeft + 14) {
                if(needsPageLinkScrollBars()) {
                    int j = getScrollStates();

                    currentPageLinkScroll = (float)(currentPageLinkScroll - (double)i / (double)j);

                    if(currentPageLinkScroll < 0.0F) {
                        currentPageLinkScroll = 0.0F;
                    }

                    if(currentPageLinkScroll > 1.0F) {
                        currentPageLinkScroll = 1.0F;
                    }
                    updateSearch();
                }
            } else {
                if(needsPageScrollBars()) {
                    int maxTranslation = getMaxPageTranslation();
                    currentPageScroll -= (float)i / maxTranslation * 40;
                    if(currentPageScroll > 1F) currentPageScroll = 1F;
                    else if(currentPageScroll < 0F) currentPageScroll = 0F;
                    updatePageScrolling();
                }
            }
        }
    }

    private int getScrollStates(){
        return (1 + matchingWikiPages - currentTab.pagesPerTab()) / currentTab.pagesPerScroll();
    }

    /**
     * Draws the screen and all the components in it.
     */
    @Override
    public void drawScreen(int mouseX, int mouseY, float partialTicks){
        drawDefaultBackground();
        lastMouseX = mouseX;
        boolean leftClicking = Mouse.isButtonDown(0);
        int pageLinkScrollX1 = guiLeft + PAGE_LINK_SCROLL_X;
        int pageLinkScrollY1 = guiTop + PAGE_LINK_SCROLL_Y + currentTab.getSearchBarAndScrollStartY();
        int pageLinkScrollX2 = pageLinkScrollX1 + 14;
        int pageLinkScrollY2 = pageLinkScrollY1 + PAGE_LINK_SCROLL_HEIGHT - currentTab.getSearchBarAndScrollStartY();

        int pageScrollX1 = guiLeft + PAGE_SCROLL_X;
        int pageScrollY1 = guiTop + PAGE_SCROLL_Y;
        int pageScrollX2 = pageScrollX1 + 14;
        int pageScrollY2 = pageScrollY1 + PAGE_SCROLL_HEIGHT;

        if(!wasClicking && leftClicking) {
            if(mouseX >= pageLinkScrollX1 && mouseY >= pageLinkScrollY1 && mouseX < pageLinkScrollX2 && mouseY < pageLinkScrollY2) {
                isScrollingPageLink = needsPageLinkScrollBars();
            } else if(mouseX >= pageScrollX1 && mouseY >= pageScrollY1 && mouseX < pageScrollX2 && mouseY < pageScrollY2) {
                isScrollingPage = needsPageScrollBars();
            }
        }

        if(!leftClicking) {
            isScrollingPageLink = false;
            isScrollingPage = false;
        }

        wasClicking = leftClicking;

        if(isScrollingPageLink) {
            currentPageLinkScroll = (mouseY - pageLinkScrollY1 - 7.5F) / (pageLinkScrollY2 - pageLinkScrollY1 - 15.0F);

            if(currentPageLinkScroll < 0.0F) {
                currentPageLinkScroll = 0.0F;
            }

            if(currentPageLinkScroll > 1.0F) {
                currentPageLinkScroll = 1.0F;
            }
            updateSearch();
        } else if(isScrollingPage) {
            currentPageScroll = (mouseY - pageScrollY1 - 7.5F) / (pageScrollY2 - pageScrollY1 - 15.0F);

            if(currentPageScroll < 0.0F) {
                currentPageScroll = 0.0F;
            }

            if(currentPageScroll > 1.0F) {
                currentPageScroll = 1.0F;
            }
            updatePageScrolling();
        }

        super.drawScreen(mouseX, mouseY, partialTicks);
        //  GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
        //GL11.glDisable(GL11.GL_LIGHTING);
    }

    /**
     * Draw the background layer for the GuiContainer (everything behind the items)
     */
    @Override
    protected void drawGuiContainerBackgroundLayer(float par1, int mouseX, int mouseY){
        GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
        // RenderHelper.enableGUIStandardItemLighting();

        //Draw the wiki tabs.
        mc.getTextureManager().bindTexture(Textures.GUI_WIKI);
        drawTexturedModalRect(guiLeft + 33, guiTop, 33, 0, xSize - 33, ySize);
        List<IWikiTab> visibleTabs = getVisibleTabs();
        for(int i = 0; i < visibleTabs.size(); i++) {
            drawTexturedModalRect(guiLeft, guiTop + 4 + i * 35, 0, currentTab == visibleTabs.get(i) ? 0 : 35, 33, 35);
        }

        //Draw the change tabpage tab.
        if(hasMultipleTabPages()) {
            drawTexturedModalRect(guiLeft, guiTop + 214, 0, 70, 33, 22);
        }

        //draw the pagelink scrollbar
        if(needsPageLinkScrollBars()) {
            drawTexturedModalRect(guiLeft + PAGE_LINK_SCROLL_X - 1, guiTop + PAGE_LINK_SCROLL_Y + currentTab.getSearchBarAndScrollStartY() - 1, PAGE_SCROLL_X - 1, PAGE_SCROLL_Y - 1, 14, PAGE_LINK_SCROLL_HEIGHT - currentTab.getSearchBarAndScrollStartY() - 1);
            drawTexturedModalRect(guiLeft + PAGE_LINK_SCROLL_X - 1, guiTop + PAGE_LINK_SCROLL_Y + PAGE_LINK_SCROLL_HEIGHT - 2, PAGE_SCROLL_X - 1, PAGE_SCROLL_Y + PAGE_SCROLL_HEIGHT - 2, 14, 1);
        } else {
            drawVerticalLine(guiLeft + PAGE_LINK_SCROLL_X + 13, guiTop + PAGE_LINK_SCROLL_Y + currentTab.getSearchBarAndScrollStartY(), PAGE_LINK_SCROLL_HEIGHT + 20, 0xFF888888);
        }

        //Draw the text field.
        searchField.drawTextBox();

        drawWikiPage(mouseX, mouseY);

    }

    @Override
    protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY){
        super.drawGuiContainerForegroundLayer(mouseX, mouseY);

        GL11.glColor4d(1, 1, 1, 1);
        //Draw the scroll bar widgets.

        mc.getTextureManager().bindTexture(scrollbarTexture);
        if(needsPageLinkScrollBars()) {
            drawTexturedModalRect(PAGE_LINK_SCROLL_X, PAGE_LINK_SCROLL_Y + currentTab.getSearchBarAndScrollStartY() + (int)((PAGE_LINK_SCROLL_HEIGHT - currentTab.getSearchBarAndScrollStartY() - 17) * currentPageLinkScroll), 232 + (needsPageLinkScrollBars() ? 0 : 12), 0, 12, 15);
        }
        drawTexturedModalRect(PAGE_SCROLL_X, PAGE_SCROLL_Y + (int)((PAGE_SCROLL_Y + PAGE_SCROLL_HEIGHT - PAGE_SCROLL_Y - 17) * currentPageScroll), 232 + (needsPageScrollBars() ? 0 : 12), 0, 12, 15);

        GL11.glEnable(GL11.GL_LIGHTING);

        //draw the tab page browse text if necessary
        if(hasMultipleTabPages()) {
            fontRenderer.drawString(currentTabPage + 1 + "/" + getTotalTabPages(), 10, 221, 0xFF000000);
        }

        //Draw the wiki page stacks.
        for(LocatedStack locatedStack : locatedStacks) {
            locatedStack.renderBackground(this, mouseX, mouseY);
        }

        //draw the wikipage
        currentTab.renderForeground(this, mouseX, mouseY);

        GL11.glPushMatrix();
        GL11.glTranslated(guiLeft, guiTop, 0);
        for(LocatedString locatedString : locatedStrings) {
            if(locatedString.getY() > MIN_TEXT_Y && locatedString.getReservedSpace().height + locatedString.getY() <= MAX_TEXT_Y) {
                locatedString.renderForeground(this, mouseX, mouseY);
            }
        }
        GL11.glPopMatrix();

        // Draw the wiki page images.
        GL11.glColor4d(1, 1, 1, 1);
        GL11.glPushMatrix();
        GL11.glScaled(TEXT_SCALE, TEXT_SCALE, 1);
        for(IWidget texture : locatedTextures) {
            texture.renderForeground(this, mouseX, mouseY);
        }

        GL11.glPopMatrix();

        //Draw wiki tab images.
        List<IReservedSpace> reservedSpaces = currentTab.getReservedSpaces();
        if(reservedSpaces != null) {
            for(IReservedSpace space : reservedSpaces) {
                if(space instanceof LocatedTexture) {
                    ((LocatedTexture)space).renderForeground(this, mouseX, mouseY);
                }
            }
        }

        //render the wiki links
        for(IPageLink link : visibleWikiPages) {
            link.renderForeground(this, mouseX, mouseY);
        }

        drawTooltips(mouseX, mouseY);

        //GL11.glDisable(GL12.GL_RESCALE_NORMAL);
        //  RenderHelper.disableStandardItemLighting();
        //   GL11.glDisable(GL11.GL_LIGHTING);
        //GL11.glDisable(GL11.GL_DEPTH_TEST);
    }

    private void drawTooltips(int x, int y){
        GlStateManager.enableLighting();

        List<IWikiTab> visibleTabs = getVisibleTabs();
        for(int i = 0; i < visibleTabs.size(); i++) {
            if(x <= 33 + guiLeft && x >= 1 + guiLeft && y >= 4 + guiTop + i * 35 && y <= 39 + guiTop + i * 35) {
                drawHoveringText(I18n.format(visibleTabs.get(i).getName()), x - guiLeft, y - guiTop);
            }
        }
        if(hasMultipleTabPages() && x < 33 + guiLeft && x >= 1 + guiLeft && y >= 214 + guiTop && y <= 236 + guiTop) {
            drawHoveringText(Arrays.asList(new String[]{I18n.format("igwmod.tooltip.tabPageBrowse.next"), I18n.format("igwmod.tooltip.tabPageBrowse.previous")}), x - guiLeft, y - guiTop);
        }
        /*
        if(curSection == EnumWikiSection.ENTITIES) {
            for(int i = 0; i < shownEntityList.size(); i++) {
                if(x >= guiLeft + 41 && x <= guiLeft + 76 && y >= guiTop + 75 + i * 36 && y <= guiTop + 110 + i * 36) {
                    drawCreativeTabHoveringText(shownEntityList.get(i).getEntityName(), x - guiLeft, y - guiTop);
                }
            }
        }*/
    }

    private void updateWikiPage(Object... metadata){
        Object o = metadata.length > 0 ? metadata[0] : null;
        ItemStack pageStack = o instanceof ItemStack ? (ItemStack)o : null;
        Entity pageEntity = o instanceof Entity ? (Entity)o : null;
        PageChangeEvent pageChangeEvent = new PageChangeEvent(currentFile, pageStack, pageEntity);
        MinecraftForge.EVENT_BUS.post(pageChangeEvent);
        currentFile = pageChangeEvent.currentFile;
        fileInfo = pageChangeEvent.pageText;
        if(fileInfo == null) {
            String modid = currentModIdPage;
            if(currentFile.contains(":")) {
                String[] splitted = currentFile.split(":", 2);
                modid = splitted[0];
                currentFile = splitted[1];
            } else {
                if(pageStack != null) {
                    modid = WikiUtils.getOwningModId(pageStack);
                } else if(pageEntity != null) {
                    modid = Util.getModIdForEntity(pageEntity.getClass());
                } else if(o instanceof String) {
                    modid = (String)o;
                } else {
                    ItemStack tabItem = currentTab.renderTabIcon(this);
                    if(tabItem != null && tabItem.getItem() != null) {
                        modid = WikiUtils.getOwningModId(tabItem);
                    }
                    if(ConfigHandler.debugMode && !currentFile.startsWith("server")) {
                        IGWLog.info("Tracked down the mod owner of the page \"" + currentFile + "\" by getting the mod owner of the tab ItemStack. This is not recommended. Please prefix page links with <modid>:, so for example: pneumaticcraft:menu/baseConcepts");
                    }
                }
            }

            currentModIdPage = modid;
            fileInfo = InfoSupplier.getInfo(modid, currentFile, false);
        }
        List<IReservedSpace> reservedSpaces = currentTab.getReservedSpaces();
        if(reservedSpaces == null) reservedSpaces = new ArrayList<IReservedSpace>();
        reservedSpaces.add(new ReservedSpace(new Rectangle(0, 0, 200, Integer.MAX_VALUE)));
        InfoSupplier.analyseInfo(fontRenderer, fileInfo, reservedSpaces, locatedStrings, locatedStacks, locatedTextures);
        ((ContainerBlockWiki)inventorySlots).updateStacks(locatedStacks, visibleWikiPages);
        currentPageTranslation = 0;
        currentPageScroll = 0;
    }

    private void drawWikiPage(int mouseX, int mouseY){
        currentTab.renderBackground(this, mouseX, mouseY);
        GL11.glPushMatrix();
        GL11.glTranslated(guiLeft, guiTop, 0);

        // Draw the wiki page images.
        GL11.glColor4d(1, 1, 1, 1);
        GL11.glPushMatrix();
        GL11.glScaled(TEXT_SCALE, TEXT_SCALE, 1);
        for(IWidget texture : locatedTextures) {
            texture.renderBackground(this, mouseX, mouseY);
        }

        GL11.glPopMatrix();

        for(LocatedString locatedString : locatedStrings) {
            if(locatedString.getY() > MIN_TEXT_Y && locatedString.getReservedSpace().height + locatedString.getY() <= MAX_TEXT_Y) {
                locatedString.renderBackground(this, mouseX, mouseY);
            }
        }
        GL11.glColor4d(1, 1, 1, 1);

        //Draw wiki tab images.
        List<IReservedSpace> reservedSpaces = currentTab.getReservedSpaces();
        if(reservedSpaces != null) {
            for(IReservedSpace space : reservedSpaces) {
                if(space instanceof LocatedTexture) {
                    ((LocatedTexture)space).renderBackground(this, mouseX, mouseY);
                }
            }
        }

        GL11.glPushMatrix();
        GL11.glTranslated(0, 4, 0);
        List<IWikiTab> visibleTabs = getVisibleTabs();

        RenderHelper.enableGUIStandardItemLighting();
        for(IWikiTab tab : visibleTabs) {
            ItemStack drawingStack = tab.renderTabIcon(this);
            if(drawingStack != null) {
                if(drawingStack.getItem() instanceof ItemBlock) {
                    renderRotatingBlockIntoGUI(this, drawingStack, 11, 23, 1.5F);
                } else {
                    boolean oldSetting = mc.gameSettings.fancyGraphics;
                    mc.gameSettings.fancyGraphics = true;
                    renderRotatingBlockIntoGUI(this, drawingStack, 12, 20, 1.2F);
                    mc.gameSettings.fancyGraphics = oldSetting;
                }
            }
            GL11.glTranslated(0, 35, 0);
        }
        GL11.glPopMatrix();

        //render the wiki links
        for(IPageLink link : visibleWikiPages) {
            link.renderBackground(this, mouseX, mouseY);
        }
        GL11.glPopMatrix();

    }

    private List<IWikiTab> getVisibleTabs(){
        List<IWikiTab> tabs = new ArrayList<IWikiTab>();
        for(int i = currentTabPage * 6; i < currentTabPage * 6 + 6 && i < wikiTabs.size(); i++) {
            tabs.add(wikiTabs.get(i));
        }
        return tabs;
    }

    private IWikiTab getTabForPage(String page){
        if(page == null) return null; //When there isn't a valid page just stay on the same page.
        if(currentTab != null) {//give the current tab the highest priority.
            List<IPageLink> links = currentTab.getPages(null);
            if(links != null) {
                for(IPageLink link : links) {
                    if(page.equals(link.getLinkAddress())) return currentTab;
                }
            }
        }
        for(int i = wikiTabs.size() - 1; i >= 0; i--) {
            IWikiTab tab = wikiTabs.get(i);
            List<IPageLink> links = tab.getPages(null);
            if(links != null) {
                for(IPageLink link : links) {
                    if(page.equals(link.getLinkAddress())) return tab;
                }
            }
        }
        return null;
    }

    private int getPageNumberForTab(IWikiTab tab){
        int index = wikiTabs.indexOf(tab);
        if(index == -1) {
            return 0;
        } else {
            return index / 6;
        }
    }

    private boolean hasMultipleTabPages(){
        return wikiTabs.size() > 6;
    }

    private int getTotalTabPages(){
        return wikiTabs.size() / 6 + 1;
    }

    /**
     * This method was copied from Equivalent Exchange 3's RenderUtils.java class, https://github.com/pahimar/Equivalent-Exchange-3/blob/master/src/main/java/com/pahimar/ee3/client/renderer/RenderUtils.java
     * @param fontRenderer
     * @param stack
     * @param x
     * @param y
     * @param zLevel
     * @param scale
     */
    /*
    public void renderRotatingBlockIntoGUI(GuiWiki gui, ItemStack stack, int x, int y, float scale){

     RenderBlocks renderBlocks = new RenderBlocks();

     Block block = Block.blocksList[stack.itemID];
     FMLClientHandler.instance().getClient().renderEngine.bindTexture(TextureMap.locationBlocksTexture);
     GL11.glPushMatrix();
     GL11.glTranslatef(x - 2, y + 3, -3.0F + gui.zLevel);
     GL11.glScalef(10.0F, 10.0F, 10.0F);
     GL11.glTranslatef(1.0F, 0.5F, 1.0F);
     GL11.glScalef(1.0F * scale, 1.0F * scale, -1.0F);
     GL11.glRotatef(210.0F, 1.0F, 0.0F, 0.0F);
     GL11.glRotatef(-TickHandler.ticksExisted, 0.0F, 1.0F, 0.0F);

     int var10 = Item.itemsList[stack.itemID].getColorFromItemStack(stack, 0);
     float var16 = (var10 >> 16 & 255) / 255.0F;
     float var12 = (var10 >> 8 & 255) / 255.0F;
     float var13 = (var10 & 255) / 255.0F;

     GL11.glColor4f(var16, var12, var13, 1.0F);

     GL11.glRotatef(-90.0F, 0.0F, 1.0F, 0.0F);
     renderBlocks.useInventoryTint = true;
     renderBlocks.renderBlockAsItem(block, stack.getItemDamage(), 1.0F);
     renderBlocks.useInventoryTint = true;
     GL11.glPopMatrix();
    }*/

    private static RenderEntityItem renderItem;
    private static EntityItem entityItem;

    public void renderRotatingBlockIntoGUI(GuiWiki gui, ItemStack stack, int x, int y, float scale){
        if(entityItem == null) {
            entityItem = new EntityItem(gui.mc.world);
            renderItem = new RenderEntityItem(Minecraft.getMinecraft().getRenderManager(), Minecraft.getMinecraft().getRenderItem()){
                @Override
                public boolean shouldBob(){
                    return false;
                }
            };
        }
        entityItem.setItem(stack);

        GlStateManager.pushMatrix();
        GlStateManager.translate(x + 1, y + 13, 20);
        GlStateManager.scale(40 * scale, 40 * scale, -40 * scale);
        GlStateManager.rotate(180, 1, 0, 0);
        GlStateManager.rotate(30, 1, 0, 0);
        GlStateManager.translate(0.1, 0.1, gui.zLevel);
        GlStateManager.rotate(-TickHandler.ticksExisted, 0, 1, 0);
        renderItem.doRender(entityItem, 0.0, 0.0, 0, 0, 0);
        GlStateManager.popMatrix();
        /* RenderBlocks renderBlocks = new RenderBlocks();

         Block block = Block.blocksList[stack.itemID];
         FMLClientHandler.instance().getClient().renderEngine.bindTexture(TextureMap.locationBlocksTexture);
         GL11.glPushMatrix();
         GL11.glTranslatef(x - 2, y + 3, -3.0F + gui.zLevel);
         GL11.glScalef(10.0F, 10.0F, 10.0F);
         GL11.glTranslatef(1.0F, 0.5F, 1.0F);
         GL11.glScalef(1.0F * scale, 1.0F * scale, -1.0F);
         GL11.glRotatef(210.0F, 1.0F, 0.0F, 0.0F);
         GL11.glRotatef(-TickHandler.ticksExisted, 0.0F, 1.0F, 0.0F);

         int var10 = Item.itemsList[stack.itemID].getColorFromItemStack(stack, 0);
         float var16 = (var10 >> 16 & 255) / 255.0F;
         float var12 = (var10 >> 8 & 255) / 255.0F;
         float var13 = (var10 & 255) / 255.0F;

         GL11.glColor4f(var16, var12, var13, 1.0F);

         GL11.glRotatef(-90.0F, 0.0F, 1.0F, 0.0F);
         renderBlocks.useInventoryTint = true;
         renderBlocks.renderBlockAsItem(block, stack.getItemDamage(), 1.0F);
         renderBlocks.useInventoryTint = true;
         GL11.glPopMatrix();*/
    }
} public class BlockAndItemWikiTab implements IWikiTab{
    private static ItemStack drawingStack = ItemStack.EMPTY;

    @Override
    public String getName(){
        return "igwmod.wikitab.blocksAndItems.name";
    }

    @Override
    public ItemStack renderTabIcon(GuiWiki gui){
        return new ItemStack(Blocks.GRASS);
    }

    @Override
    public List<IReservedSpace> getReservedSpaces(){
        List<IReservedSpace> reservedSpaces = new ArrayList<IReservedSpace>();
        reservedSpaces.add(new LocatedTexture(Textures.GUI_ITEMS_AND_BLOCKS, 40, 74, 36, 144));
        return reservedSpaces;
    }

    @Override
    public List<IPageLink> getPages(int[] indexes){
        List<ItemStack> itemStacks = WikiRegistry.getItemAndBlockPageEntries();
        List<IPageLink> pages = new ArrayList<IPageLink>();
        if(indexes == null) {
            for(int i = 0; i < itemStacks.size(); i++) {
                pages.add(new LocatedStack(itemStacks.get(i), 41 + i % 2 * 18, 75 + i / 2 * 18));
            }
        } else {
            for(int i = 0; i < indexes.length; i++) {
                pages.add(new LocatedStack(itemStacks.get(indexes[i]), 41 + i % 2 * 18, 75 + i / 2 * 18));
            }
        }
        return pages;
    }

    @Override
    public int pagesPerTab(){
        return 16;
    }

    @Override
    public int pagesPerScroll(){
        return 2;
    }

    @Override
    public int getSearchBarAndScrollStartY(){
        return 61;
    }

    @Override
    public void renderForeground(GuiWiki gui, int mouseX, int mouseY){
        if(!drawingStack.isEmpty()) {
            GlStateManager.enableLighting();
            RenderHelper.enableGUIStandardItemLighting();
            if(drawingStack.getItem() instanceof ItemBlock) {
                gui.renderRotatingBlockIntoGUI(gui, drawingStack, 55, 58, 2.8F);
            } else {
                GL11.glPushMatrix();
                GL11.glTranslated(49, 20, 0);
                GL11.glScaled(2.2, 2.2, 2.2);
                Minecraft.getMinecraft().getRenderItem().renderItemAndEffectIntoGUI(drawingStack, 0, 0);
                GL11.glPopMatrix();
            }
        }
    }

    @Override
    public void renderBackground(GuiWiki gui, int mouseX, int mouseY){}

    @Override
    public void onMouseClick(GuiWiki gui, int mouseX, int mouseY, int mouseKey){}

    @Override
    public void onPageChange(GuiWiki gui, String pageName, Object... metadata){
        if(metadata.length > 0 && metadata[0] instanceof ItemStack) {
            drawingStack = (ItemStack)metadata[0];
        }
    }

} public class EntityWikiTab implements IWikiTab{
    private static Entity curEntity;
    private static Entity tabEntity;

    public EntityWikiTab(){

    }

    @Override
    public String getName(){
        return "igwmod.wikitab.entities.name";
    }

    @Override
    public ItemStack renderTabIcon(GuiWiki gui){
        if(tabEntity == null) {
            EntityPlayer player = gui.mc.player;
            tabEntity = new EntityCreeper(player.world);
        }
        drawEntity(tabEntity, 18, 28, 0.6F, 0);
        return null;
    }

    @Override
    public List<IReservedSpace> getReservedSpaces(){
        List<IReservedSpace> reservedSpaces = new ArrayList<IReservedSpace>();
        reservedSpaces.add(new LocatedTexture(Textures.GUI_ENTITIES, 40, 74, 36, 153));
        return reservedSpaces;
    }

    @Override
    public List<IPageLink> getPages(int[] indexes){
        List<Class<? extends Entity>> allEntries = WikiRegistry.getEntityPageEntries();
        List<IPageLink> pages = new ArrayList<IPageLink>();
        if(indexes == null) {
            for(int i = 0; i < allEntries.size(); i++) {
                pages.add(new LocatedEntity(allEntries.get(i), 41, 77 + i * 36));
            }
        } else {
            for(int i = 0; i < indexes.length; i++) {
                pages.add(new LocatedEntity(allEntries.get(indexes[i]), 41, 77 + i * 36));
            }
        }
        return pages;
    }

    @Override
    public int pagesPerTab(){
        return 4;
    }

    @Override
    public int pagesPerScroll(){
        return 1;
    }

    @Override
    public int getSearchBarAndScrollStartY(){
        return 61;
    }

    @Override
    public void renderForeground(GuiWiki gui, int mouseX, int mouseY){}

    @Override
    public void renderBackground(GuiWiki gui, int mouseX, int mouseY){
        GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
        // RenderHelper.enableStandardItemLighting();
        if(curEntity != null) drawEntity(curEntity, gui.getGuiLeft() + 65, gui.getGuiTop() + 49, 0.7F, 0);
        //  GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
    }

    @Override
    public void onMouseClick(GuiWiki gui, int mouseX, int mouseY, int mouseKey){}

    /* public static void drawEntity(Entity entity, int x, int y, float size, float partialTicks){
         GL11.glDisable(GL11.GL_LIGHTING);
         GL11.glEnable(GL11.GL_DEPTH_TEST);
         short short1 = 240;
         short short2 = 240;
         OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, short1 / 1.0F, short2 / 1.0F);

         GL11.glPushMatrix();
         GL11.glTranslated(x, y, 10);
         float maxHitboxComponent = Math.max(1, Math.max(entity.width, entity.height));
         GL11.glScaled(40 * size / maxHitboxComponent, -40 * size / maxHitboxComponent, -40 * size / maxHitboxComponent);
         //GL11.glRotated(20, 1, 0, 1);
         GL11.glRotatef(-30.0F, 1.0F, 0.0F, 0.0F);
         GL11.glRotated(TickHandler.ticksExisted + partialTicks, 0, 1, 0);
         Minecraft.getMinecraft().getRenderManager().renderEntityWithPosYaw(entity, 0D, 0D, 0.0D, 0, partialTicks);
         GL11.glPopMatrix();
         GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
         GL11.glDisable(GL11.GL_LIGHTING);
         GL11.glDisable(GL11.GL_DEPTH_TEST);
     }*/

    public static void drawEntity(Entity entity, int x, int y, float size, float partialTicks){
        //GL11.glEnable(GL11.GL_LIGHTING);
        //x, y, scale, yaw, pitch
        GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
        float maxHitboxComponent = Math.max(1, Math.max(entity.width, entity.height));
        int scale = (int)(40 * size / maxHitboxComponent);

        GlStateManager.enableColorMaterial();
        GlStateManager.pushMatrix();
        GL11.glTranslatef(x, y, 50.0F);
        GL11.glScalef(-scale, scale, scale);
        GL11.glRotatef(180.0F, 0.0F, 0.0F, 1.0F);

        GL11.glRotatef(30, 1, 0, 0);

        GL11.glRotatef(135.0F, 0.0F, 1.0F, 0.0F);
        RenderHelper.enableStandardItemLighting();
        GL11.glRotatef(-135.0F, 0.0F, 1.0F, 0.0F);

        GL11.glRotatef(-TickHandler.ticksExisted, 0, 1, 0);
        /* GL11.glRotatef(-((float)Math.atan((double)(par4 / 40.0F))) * 20.0F, 1.0F, 0.0F, 0.0F);
         entity.renderYawOffset = (float)Math.atan((double)(par3 / 40.0F)) * 20.0F;
         entity.rotationYaw = (float)Math.atan((double)(par3 / 40.0F)) * 40.0F;
         entity.rotationPitch = -((float)Math.atan((double)(par4 / 40.0F))) * 20.0F;
         entity.rotationYawHead = entity.rotationYaw;
         entity.prevRotationYawHead = entity.rotationYaw;*/
        GL11.glTranslatef(0.0F, (float)entity.getYOffset(), 0.0F);
        RenderManager rendermanager = Minecraft.getMinecraft().getRenderManager();
        rendermanager.setRenderShadow(false);
        Minecraft.getMinecraft().getRenderManager().playerViewY = 180.0F;
        Minecraft.getMinecraft().getRenderManager().renderEntity(entity, 0.0D, 0.0D, 0.0D, 0.0F, 1.0F, false);
        rendermanager.setRenderShadow(true);
        /* entity.renderYawOffset = f2;
         entity.rotationYaw = f3;
         entity.rotationPitch = f4;
         entity.prevRotationYawHead = f5;
         entity.rotationYawHead = f6;*/
        GlStateManager.popMatrix();
        RenderHelper.disableStandardItemLighting();
        GlStateManager.disableRescaleNormal();
        GlStateManager.setActiveTexture(OpenGlHelper.lightmapTexUnit);
        GlStateManager.disableTexture2D();
        GlStateManager.setActiveTexture(OpenGlHelper.defaultTexUnit);

        // GL11.glDisable(GL11.GL_LIGHTING);
    }

    @Override
    public void onPageChange(GuiWiki gui, String pageName, Object... metadata){
        if(metadata.length > 0 && metadata[0] instanceof Entity) {
            curEntity = Util.getEntityForClass(((Entity)metadata[0]).getClass());
        }
    }

} public class IGWWikiTab extends BaseWikiTab{

    public IGWWikiTab(){
        pageEntries.add("intro");
        pageEntries.add("forServers");
        if(ConfigHandler.debugMode) {
            pageEntries.add("devIntro");
            pageEntries.add("devItemAndBlock");
            pageEntries.add("devPageCommands");
            pageEntries.add("devForModders");
        }
    }

    @Override
    public String getName(){
        return "igwmod.wikitab.igwmod.name";
    }

    @Override
    public ItemStack renderTabIcon(GuiWiki gui){
        return new ItemStack(Blocks.LOG);
    }

    @Override
    protected String getPageName(String pageEntry){
        return I18n.format("igwtab.entry." + pageEntry);
    }

    @Override
    protected String getPageLocation(String pageEntry){
        return "igwmod:igwtab/" + pageEntry;
    }
} public class Constants{
    public static final String MOD_ID = "igwmod";
    private static final String MASSIVE = "@MASSIVE@";
    private static final String MAJOR = "@MAJOR@";
    private static final String MINOR = "@MINOR@";
    private static final String BUILD = "@BUILD_NUMBER@";
    private static final String MC_VERSION = "@MC_VERSION@";
    //  public static final String WIKI_PAGE_LOCATION = "https://github.com/MineMaarten/IGW-mod/archive/master.zip";// "http://www.minemaarten.com/wp-content/uploads/2013/12/WikiPages.zip";
    //  public static final String ZIP_NAME = "igw";
    //   public static final int CONNECTION_TIMEOUT = 3000;
    //    public static final int READ_TIMEOUT = 5000;
    //  public static final String INTERNET_UPDATE_CONFIG_KEY = "Update pages via internet";
    //  public static final String USE_OFFLINE_WIKIPAGES_KEY = "Use offline wikipages";
    public static final int DEFAULT_KEYBIND_OPEN_GUI = Keyboard.KEY_I;

    public static final int TEXTURE_MAP_ID = 15595;

    public static String fullVersionString(){

        return String.format("%s-%s.%s.%s-%s", MC_VERSION, MASSIVE, MAJOR, MINOR, BUILD);
    }
} public class IntegratorComment implements IRecipeIntegrator{

    @Override
    public String getCommandKey(){
        return "comment";
    }

    @Override
    public void onCommandInvoke(String[] arguments, List<IReservedSpace> reservedSpaces, List<LocatedString> locatedStrings, List<LocatedStack> locatedStacks, List<IWidget> locatedTextures) throws IllegalArgumentException{
        //Just ignore it, which is what a comment is doing ;).
    }

} public class IntegratorCraftingRecipe implements IRecipeIntegrator{

    public static Map<String, IRecipe> autoMappedRecipes = new HashMap<String, IRecipe>();
    public static final int STACKS_X_OFFSET = 1;
    public static final int STACKS_Y_OFFSET = 1;
    private static final int RESULT_STACK_X_OFFSET = 95;
    private static final int RESULT_STACK_Y_OFFSET = STACKS_Y_OFFSET + 18;

    @Override
    public String getCommandKey(){
        return "crafting";
    }

    @Override
    public void onCommandInvoke(String[] arguments, List<IReservedSpace> reservedSpaces, List<LocatedString> locatedStrings, List<LocatedStack> locatedStacks, List<IWidget> locatedTextures) throws IllegalArgumentException{
        if(arguments.length < 3) throw new IllegalArgumentException("Code needs at least 3 arguments!");
        int x;
        try {
            x = Integer.parseInt(arguments[0]);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("The first parameter (the x coordinate) contains an invalid number. Check for spaces or invalid characters!");
        }
        int y;
        try {
            y = Integer.parseInt(arguments[1]);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("The second parameter (the y coordinate) contains an invalid number. Check for spaces or invalid characters!");
        }
        locatedTextures.add(new LocatedTexture(TextureSupplier.getTexture(Paths.MOD_ID_WITH_COLON + "textures/GuiCrafting.png"), x, y, (int)(116 / GuiWiki.TEXT_SCALE), (int)(54 / GuiWiki.TEXT_SCALE)));

        if(arguments[2].startsWith("key=")) {
            if(arguments.length != 3) throw new IllegalArgumentException("An RecipeRetrievalEvent crafting code can only have 3 parameters: x, y and the key!");
            addAutomaticCraftingRecipe(arguments[2], locatedStacks, locatedTextures, locatedStrings, (int)(x * GuiWiki.TEXT_SCALE), (int)(y * GuiWiki.TEXT_SCALE));
        } else {
            addManualCraftingRecipe(arguments, locatedStacks, locatedTextures, (int)(x * GuiWiki.TEXT_SCALE), (int)(y * GuiWiki.TEXT_SCALE));
        }
    }

    /**
     * Check RecipeRetrievalEvent to see what this method does.
     * @param y 
     * @param x 
     */
    private void addAutomaticCraftingRecipe(String code, List<LocatedStack> locatedStacks, List<IWidget> locatedTextures, List<LocatedString> locatedStrings, int x, int y) throws IllegalArgumentException{
        String key = code.substring(4);
        CraftingRetrievalEvent recipeEvent = new CraftingRetrievalEvent(key);
        IRecipe autoMappedRecipe = autoMappedRecipes.get(key);
        if(autoMappedRecipe != null) {
            recipeEvent.recipe = autoMappedRecipe;
        } else {
            MinecraftForge.EVENT_BUS.post(recipeEvent);
        }
        if(recipeEvent.recipe instanceof ShapedRecipes) {
            ShapedRecipes recipe = (ShapedRecipes)recipeEvent.recipe;
            for(int i = 0; i < recipe.recipeHeight; i++) {
                for(int j = 0; j < recipe.recipeWidth; j++) {
                    Ingredient ingredientStack = recipe.recipeItems.get(i * recipe.recipeWidth + j);
                    if(ingredientStack.getMatchingStacks().length > 0) {
                        locatedStacks.add(new LocatedStack(ingredientStack.getMatchingStacks()[0], x + STACKS_X_OFFSET + j * 18, y + STACKS_Y_OFFSET + i * 18));
                    }
                }
            }
            locatedStacks.add(new LocatedStack(recipe.getRecipeOutput(), x + RESULT_STACK_X_OFFSET, y + RESULT_STACK_Y_OFFSET));
            locatedStrings.add(new LocatedString(I18n.format("igwmod.gui.crafting.shaped"), x * 2 + 120, y * 2 + 10, 0xFF000000, false));
        } else if(recipeEvent.recipe instanceof ShapedOreRecipe) {
            ShapedOreRecipe recipe = (ShapedOreRecipe)recipeEvent.recipe;
            int recipeHeight = 0;
            int recipeWidth = 0;
            try {
                recipeHeight = ReflectionHelper.findField(ShapedOreRecipe.class, "height").getInt(recipe);
                recipeWidth = ReflectionHelper.findField(ShapedOreRecipe.class, "width").getInt(recipe);
            } catch(Exception e) {
                IGWLog.error("Something went wrong while trying to get the width and height fields from ShapedOreRecipe!");
                e.printStackTrace();
            }
            for(int i = 0; i < recipeHeight; i++) {
                for(int j = 0; j < recipeWidth; j++) {
                    Ingredient ingredient = recipe.getIngredients().get(i * recipeWidth + j);
                    if(ingredient.getMatchingStacks().length > 0) {
                        ItemStack ingredientStack = ingredient.getMatchingStacks()[0];
                        //ingredient instanceof ItemStack ? (ItemStack)ingredient : ((List<ItemStack>)ingredient).get(0);
                        if(!ingredientStack.isEmpty()) {
                            locatedStacks.add(new LocatedStack(ingredientStack, x + STACKS_X_OFFSET + j * 18, y + STACKS_Y_OFFSET + i * 18));
                        }
                    }
                }
            }
            locatedStacks.add(new LocatedStack(recipe.getRecipeOutput(), x + RESULT_STACK_X_OFFSET, y + RESULT_STACK_Y_OFFSET));
            locatedStrings.add(new LocatedString(I18n.format("igwmod.gui.crafting.shaped"), x * 2 + 120, y * 2 + 10, 0xFF000000, false));
        } else if(recipeEvent.recipe instanceof ShapelessRecipes) {
            ShapelessRecipes recipe = (ShapelessRecipes)recipeEvent.recipe;
            for(int i = 0; i < 3; i++) {
                for(int j = 0; j < 3; j++) {
                    if(i * 3 + j < recipe.recipeItems.size()) {
                        Ingredient ingredient = recipe.recipeItems.get(i * 3 + j);
                        ItemStack ingredientStack = ingredient.getMatchingStacks()[0];
                        if(!ingredientStack.isEmpty()) {
                            locatedStacks.add(new LocatedStack(ingredientStack, x + STACKS_X_OFFSET + j * 18, y + STACKS_Y_OFFSET + i * 18));
                        }
                    }
                }
            }
            locatedStacks.add(new LocatedStack(recipe.getRecipeOutput(), x + RESULT_STACK_X_OFFSET, y + RESULT_STACK_Y_OFFSET));
            locatedStrings.add(new LocatedString(I18n.format("igwmod.gui.crafting.shapeless"), x * 2 + 120, y * 2 + 10, 0xFF000000, false));
        } else if(recipeEvent.recipe instanceof ShapelessOreRecipe) {
            ShapelessOreRecipe recipe = (ShapelessOreRecipe)recipeEvent.recipe;
            for(int i = 0; i < 3; i++) {
                for(int j = 0; j < 3; j++) {
                    if(i * 3 + j < recipe.getIngredients().size()) {
                        Ingredient ingredient = recipe.getIngredients().get(i * 3 + j);
                        if(ingredient != null) {
                            ItemStack ingredientStack = ingredient.getMatchingStacks()[0];
                            //ingredient instanceof ItemStack ? (ItemStack)ingredient : ((List<ItemStack>)ingredient).get(0);
                            if(!ingredientStack.isEmpty()) {
                                locatedStacks.add(new LocatedStack(ingredientStack, x + STACKS_X_OFFSET + j * 18, y + STACKS_Y_OFFSET + i * 18));
                            }
                        }
                    }
                }
            }
            locatedStacks.add(new LocatedStack(recipe.getRecipeOutput(), x + RESULT_STACK_X_OFFSET, y + RESULT_STACK_Y_OFFSET));
            locatedStrings.add(new LocatedString(I18n.format("igwmod.gui.crafting.shapeless"), x * 2 + 120, y * 2 + 10, 0xFF000000, false));
        } else if(recipeEvent.recipe == null) {
            throw new IllegalArgumentException("RecipeRetrievalEvent: For the given key, no subscriber returned a recipe! key = " + key);
        } else {
            throw new IllegalArgumentException("RecipeRetrievalEvent: Don't pass anything other than ShapedRecipes, ShapedOreRecipes, ShapelessRecipes or ShapelessOreRecipe! key = " + key);
        }
    }

    private void addManualCraftingRecipe(String[] codeParts, List<LocatedStack> locatedStacks, List<IWidget> locatedTextures, int x, int y) throws IllegalArgumentException{
        String[] ingredients = new String[codeParts.length - 3];
        System.arraycopy(codeParts, 3, ingredients, 0, codeParts.length - 3);
        // ingredients[codeParts.length - 2] = lastTwoArguments[0];
        String result = codeParts[2];
        Map<String, ItemStack> ingredientMap = new HashMap<>();
        for(int i = 3; i < ingredients.length; i++) {
            String[] ingredient = ingredients[i].split("=");
            ingredientMap.put(ingredient[0], WikiUtils.getStackFromName(ingredient[1]));
        }
        for(int i = 0; i < 3; i++) {
            for(int j = 0; j < 3; j++) {
                ItemStack ingredientStack = ingredientMap.getOrDefault(ingredients[i].substring(j, j + 1), ItemStack.EMPTY);
                if(!ingredientStack.isEmpty()) {
                    locatedStacks.add(new LocatedStack(ingredientStack, x + STACKS_X_OFFSET + j * 18, y + STACKS_Y_OFFSET + i * 18));
                }
            }
        }
        ItemStack resultStack = WikiUtils.getStackFromName(result);
        if(!resultStack.isEmpty()) {
            locatedStacks.add(new LocatedStack(resultStack, x + RESULT_STACK_X_OFFSET, y + RESULT_STACK_Y_OFFSET));
        }
    }

} public class IntegratorFurnace implements IRecipeIntegrator{

    public static Map<String, ItemStack> autoMappedFurnaceRecipes = new HashMap<String, ItemStack>();

    @Override
    public String getCommandKey(){
        return "furnace";
    }

    @Override
    public void onCommandInvoke(String[] arguments, List<IReservedSpace> reservedSpaces, List<LocatedString> locatedStrings, List<LocatedStack> locatedStacks, List<IWidget> locatedTextures) throws IllegalArgumentException{
        if(arguments.length != 3 && arguments.length != 4) throw new IllegalArgumentException("Code needs to contain 3 or 4 arguments: x, y, inputstack, outputstack. It now contains " + arguments.length + ".");
        int x;
        try {
            x = Integer.parseInt(arguments[0]);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("The first parameter (the x coordinate) contains an invalid number. Check for invalid characters!");
        }
        int y;
        try {
            y = Integer.parseInt(arguments[1]);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("The second parameter (the y coordinate) contains an invalid number. Check for invalid characters!");
        }
        locatedTextures.add(new LocatedTexture(TextureSupplier.getTexture(Paths.MOD_ID_WITH_COLON + "textures/GuiFurnace.png"), x, y, (int)(82 / GuiWiki.TEXT_SCALE), (int)(54 / GuiWiki.TEXT_SCALE)));
        x = (int)(x * GuiWiki.TEXT_SCALE);
        y = (int)(y * GuiWiki.TEXT_SCALE);
        ItemStack inputStack = ItemStack.EMPTY;
        ItemStack resultStack = ItemStack.EMPTY;
        if(arguments[2].startsWith("key=")) {
            String resultStackCode = arguments[2].substring(4);
            inputStack = autoMappedFurnaceRecipes.get(resultStackCode);
            if(!inputStack.isEmpty()) {
                resultStack = WikiUtils.getStackFromName(resultStackCode);
            } else {
                FurnaceRetrievalEvent recipeEvent = new FurnaceRetrievalEvent(resultStackCode);
                MinecraftForge.EVENT_BUS.post(recipeEvent);
                inputStack = recipeEvent.inputStack;
                resultStack = recipeEvent.resultStack;
            }
        } else {
            inputStack = WikiUtils.getStackFromName(arguments[2]);
            resultStack = WikiUtils.getStackFromName(arguments[3]);
        }
        if(!inputStack.isEmpty()) {
            locatedStacks.add(new LocatedStack(inputStack, x + IntegratorCraftingRecipe.STACKS_X_OFFSET, y + IntegratorCraftingRecipe.STACKS_Y_OFFSET));
        }
        if(!resultStack.isEmpty()) {
            locatedStacks.add(new LocatedStack(resultStack, x + IntegratorCraftingRecipe.STACKS_X_OFFSET + 60, y + IntegratorCraftingRecipe.STACKS_Y_OFFSET + 18));
        }
    }

} public class IntegratorImage implements IRecipeIntegrator{

    @Override
    public String getCommandKey(){
        return "image";
    }

    @Override
    public void onCommandInvoke(String[] arguments, List<IReservedSpace> reservedSpaces, List<LocatedString> locatedStrings, List<LocatedStack> locatedStacks, List<IWidget> locatedTextures) throws IllegalArgumentException{
        if(arguments.length != 3 && arguments.length != 4) throw new IllegalArgumentException("The code needs to contain 3 or 4 parameters: x, y, [scale,] , texture location. It now contains " + arguments.length + ".");
        int[] coords = new int[2];
        double scale = 1;
        try {
            for(int i = 0; i < 2; i++)
                coords[i] = Integer.parseInt(arguments[i]);
            if(arguments.length == 4) {
                scale = Double.parseDouble(arguments[2]);
            }
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("The code contains an invalid number! Check for spaces or invalid characters.");
        }

        LocatedTexture texture = new LocatedTexture(TextureSupplier.getTexture(arguments[arguments.length - 1]), coords[0], coords[1]);
        texture.width = (int)(texture.width * scale);
        texture.height = (int)(texture.height * scale);
        locatedTextures.add(texture);
    }

} public class IntegratorStack implements IRecipeIntegrator{

    @Override
    public String getCommandKey(){
        return "stack";
    }

    @Override
    public void onCommandInvoke(String[] arguments, List<IReservedSpace> reservedSpaces, List<LocatedString> locatedStrings, List<LocatedStack> locatedStacks, List<IWidget> locatedTextures) throws IllegalArgumentException{
        if(arguments.length != 3) throw new IllegalArgumentException("stack command takes 3 arguments! Currently it has " + arguments.length);
        int x;
        try {
            x = Integer.parseInt(arguments[0]);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("The first parameter (the x coordinate) contains an invalid number. Check for invalid characters!");
        }
        int y;
        try {
            y = Integer.parseInt(arguments[1]);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("The second parameter (the y coordinate) contains an invalid number. Check for invalid characters!");
        }

        ItemStack stack = WikiUtils.getStackFromName(arguments[2]);
        if(stack.isEmpty()) throw new IllegalArgumentException("Item not found for the name " + arguments[2]);
        locatedStacks.add(new LocatedStack(stack, (int)(x * GuiWiki.TEXT_SCALE), (int)(y * GuiWiki.TEXT_SCALE)));
    }

} public class TooltipOverlayHandler{

    @SubscribeEvent
    public void tickEnd(TickEvent.RenderTickEvent event){
        if(event.phase == TickEvent.Phase.END && TickHandler.showTooltip() && ConfigHandler.shouldShowTooltip && FMLClientHandler.instance().getClient().inGameHasFocus && IGWMod.proxy.getPlayer().world != null) {
            Minecraft mc = FMLClientHandler.instance().getClient();
            ScaledResolution sr = new ScaledResolution(mc);
            FontRenderer fontRenderer = FMLClientHandler.instance().getClient().fontRenderer;
            String objectName = TickHandler.getCurrentObjectName();
            String moreInfo = "'" + Keyboard.getKeyName(ClientProxy.openInterfaceKey.getKeyCode()) + "' for more info";
            fontRenderer.drawString(objectName, sr.getScaledWidth() / 2 - fontRenderer.getStringWidth(objectName) / 2, sr.getScaledHeight() / 2 - 20, 0xFFFFFFFF);
            fontRenderer.drawString(moreInfo, sr.getScaledWidth() / 2 - fontRenderer.getStringWidth(moreInfo) / 2, sr.getScaledHeight() / 2 - 10, 0xFFFFFFFF);
        }
    }
}
import igwmod.api.WikiRegistry;
import igwmod.gui.GuiWiki;
import igwmod.gui.tabs.BlockAndItemWikiTab;
import igwmod.gui.tabs.EntityWikiTab;
import igwmod.gui.tabs.IGWWikiTab;
import igwmod.lib.Constants;
import igwmod.lib.IGWLog;
import igwmod.lib.Paths;
import igwmod.recipeintegration.IntegratorComment;
import igwmod.recipeintegration.IntegratorCraftingRecipe;
import igwmod.recipeintegration.IntegratorFurnace;
import igwmod.recipeintegration.IntegratorImage;
import igwmod.recipeintegration.IntegratorStack;
import igwmod.render.TooltipOverlayHandler;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.FurnaceRecipes;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.client.FMLClientHandler;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.event.FMLInterModComms;
import net.minecraftforge.fml.common.event.FMLInterModComms.IMCEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.InputEvent.KeyInputEvent;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.registries.IForgeRegistry;
import org.lwjgl.input.Keyboard;
package igwmod;





public class ClientProxy implements IProxy{
    public static KeyBinding openInterfaceKey;

    @Override
    public void preInit(FMLPreInitializationEvent event){
        MinecraftForge.EVENT_BUS.register(new TickHandler());

        MinecraftForge.EVENT_BUS.register(new TooltipOverlayHandler());

        //Not being used, as it doesn't really add anything...
        // MinecraftForge.EVENT_BUS.register(new HighlightHandler());

        openInterfaceKey = new KeyBinding("igwmod.keys.wiki", Constants.DEFAULT_KEYBIND_OPEN_GUI, "igwmod.keys.category");//TODO blend keybinding category in normal
        ClientRegistry.registerKeyBinding(openInterfaceKey);
        MinecraftForge.EVENT_BUS.register(this);//subscribe to key events.

        ConfigHandler.init(event.getSuggestedConfigurationFile());

        WikiRegistry.registerWikiTab(new IGWWikiTab());
        WikiRegistry.registerWikiTab(new BlockAndItemWikiTab());
        WikiRegistry.registerWikiTab(new EntityWikiTab());

        WikiRegistry.registerRecipeIntegrator(new IntegratorImage());
        WikiRegistry.registerRecipeIntegrator(new IntegratorCraftingRecipe());
        WikiRegistry.registerRecipeIntegrator(new IntegratorFurnace());
        WikiRegistry.registerRecipeIntegrator(new IntegratorStack());