public class GuideAPI {

    private static final Map<ResourceLocation, Book> BOOKS = Maps.newHashMap();
    private static final Map<Book, ItemStack> BOOK_TO_STACK = Maps.newHashMap();
    private static final Map<Book, Multimap<Class<? extends Block>, IInfoRenderer>> INFO_RENDERERS = Maps.newHashMap();
    private static List<Book> indexedBooks = Lists.newArrayList();

    /**
     * Obtains a new ItemStack associated with the provided book.
     *
     * @param book - The book to get an ItemStack for.
     * @return - The ItemStack associated with the provided book.
     */
    public static ItemStack getStackFromBook(Book book) {
        return BOOK_TO_STACK.get(book) == null ? ItemStack.EMPTY : BOOK_TO_STACK.get(book);
    }

    /**
     * Registers an IInfoRenderer
     *
     * @param infoRenderer - The renderer to register
     * @param blockClasses - The block classes that this should draw for
     */
    public static void registerInfoRenderer(Book book, IInfoRenderer infoRenderer, Class<? extends Block>... blockClasses) {
        if (!INFO_RENDERERS.containsKey(book))
            INFO_RENDERERS.put(book, ArrayListMultimap.<Class<? extends Block>, IInfoRenderer>create());

        for (Class<? extends Block> blockClass : blockClasses)
            INFO_RENDERERS.get(book).put(blockClass, infoRenderer);
    }

    /**
     * Helper method for setting a model for your book.
     * <p>
     * Use if you wish to use a custom model.
     * <p>
     * Only call <b>AFTER</b> you have registered your book.
     *
     * @param book        - Book to set model for
     * @param modelLoc    - Location of the model file
     * @param variantName - Variant to use
     */
    @SideOnly(Side.CLIENT)
    public static void setModel(Book book, ResourceLocation modelLoc, String variantName) {
        ModelResourceLocation mrl = new ModelResourceLocation(modelLoc, variantName);
        ModelLoader.setCustomModelResourceLocation(
                getStackFromBook(book).getItem(),
                0,
                mrl
        );
    }

    /**
     * Helper method for setting a model for your book.
     * <p>
     * Use if you wish to use the default model with color.
     * <p>
     * Only call <b>AFTER</b> you have registered your book.
     *
     * @param book - Book to set model for
     */
    @SideOnly(Side.CLIENT)
    public static void setModel(Book book) {
        setModel(book, new ResourceLocation("guideapi", "ItemGuideBook"), "inventory");
    }

    public static void initialize() {
        // No-op. Just here to initialize fields.
    }

    public static Map<ResourceLocation, Book> getBooks() {
        return ImmutableMap.copyOf(BOOKS);
    }

    public static Map<Book, ItemStack> getBookToStack() {
        return ImmutableMap.copyOf(BOOK_TO_STACK);
    }

    public static Map<Book, Multimap<Class<? extends Block>, IInfoRenderer>> getInfoRenderers() {
        return ImmutableMap.copyOf(INFO_RENDERERS);
    }

    public static List<Book> getIndexedBooks() {
        return ImmutableList.copyOf(indexedBooks);
    }
} public interface IGuideBook {

    /**
     * Build your guide book here. The returned book will be registered for you. The book created here can be modified
     * later, so make sure to keep a reference for yourself.
     *
     * @return a built book to be registered.
     */
    @Nullable
    Book buildBook();

    /**
     * Use this to handle setting the model of your book. Only exists on the client.
     *
     * @param bookStack - The ItemStack assigned to your book.
     */
    @SideOnly(Side.CLIENT)
    default void handleModel(@Nonnull ItemStack bookStack) {
        GuideAPI.setModel(((IGuideItem) bookStack.getItem()).getBook(bookStack));
    }

    /**
     * An IRecipe to use for your book. Called from {@link net.minecraftforge.event.RegistryEvent.Register<IRecipe>}
     *
     * @return an IRecipe to register for your book or null to not include one.
     */
    @Nullable
    default IRecipe getRecipe(@Nonnull ItemStack bookStack) {
        return null;
    }

    /**
     * Called during Post Initialization.
     */
    default void handlePost(@Nonnull ItemStack bookStack) {
        // No-op
    }
} public interface IPage {

    @SideOnly(Side.CLIENT)
    void draw(Book book, CategoryAbstract category, EntryAbstract entry, int guiLeft, int guiTop, int mouseX, int mouseY, GuiBase guiBase, FontRenderer fontRendererObj);

    @SideOnly(Side.CLIENT)
    void drawExtras(Book book, CategoryAbstract category, EntryAbstract entry, int guiLeft, int guiTop, int mouseX, int mouseY, GuiBase guiBase, FontRenderer fontRendererObj);

    boolean canSee(Book book, CategoryAbstract category, EntryAbstract entry, EntityPlayer player, ItemStack bookStack, GuiEntry guiEntry);

    @SideOnly(Side.CLIENT)
    void onLeftClicked(Book book, CategoryAbstract category, EntryAbstract entry, int mouseX, int mouseY, EntityPlayer player, GuiEntry guiEntry);

    @SideOnly(Side.CLIENT)
    void onRightClicked(Book book, CategoryAbstract category, EntryAbstract entry, int mouseX, int mouseY, EntityPlayer player, GuiEntry guiEntry);

    @SideOnly(Side.CLIENT)
    void onInit(Book book, CategoryAbstract category, EntryAbstract entry, EntityPlayer player, ItemStack bookStack, GuiEntry guiEntry);
} public class Book {

    private static final String GUITEXLOC = "guideapi:textures/gui/";

    private List<CategoryAbstract> categories = Lists.newArrayList();
    private String title = "item.GuideBook.name";
    private String header = title;
    private String itemName = title;
    private String author;
    private ResourceLocation pageTexture = new ResourceLocation(GUITEXLOC + "book_colored.png");
    private ResourceLocation outlineTexture = new ResourceLocation(GUITEXLOC + "book_greyscale.png");
    private boolean customModel;
    private Color color = new Color(171, 70, 30);
    private boolean spawnWithBook;
    private ResourceLocation registryName;
    private CreativeTabs creativeTab = CreativeTabs.MISC;

    /**
     * @deprecated see {@link BookBinder}. To be made package private in 1.13.
     */
    @Deprecated
    public Book(List<CategoryAbstract> categoryList, String title, String header, String displayName, String author, ResourceLocation pageTexture, ResourceLocation outlineTexture, boolean customModel, Color color, boolean spawnWithBook, ResourceLocation registryName, CreativeTabs creativeTab) {
        this.categories = categoryList;
        this.title = title;
        this.header = header;
        this.itemName = displayName;
        this.author = author;
        this.pageTexture = pageTexture;
        this.outlineTexture = outlineTexture;
        this.customModel = customModel;
        this.color = color;
        this.spawnWithBook = spawnWithBook;
        this.registryName = registryName;
        this.creativeTab = creativeTab;
    }

    /**
     * @deprecated see {@link BookBinder}. To be removed in 1.13.
     */
    @Deprecated
    public Book() {
    }

    public List<CategoryAbstract> getCategoryList() {
        return this.categories;
    }

    public String getTitle() {
        return this.title;
    }

    public String getItemName() {
        return this.itemName;
    }

    public String getHeader() {
        return this.header;
    }

    public String getAuthor() {
        return this.author;
    }

    public ResourceLocation getPageTexture() {
        return this.pageTexture;
    }

    public ResourceLocation getOutlineTexture() {
        return this.outlineTexture;
    }

    public boolean hasCustomModel() {
        return this.customModel;
    }

    public Color getColor() {
        return this.color;
    }

    public boolean shouldSpawnWithBook() {
        return this.spawnWithBook;
    }

    public ResourceLocation getRegistryName() {
        return this.registryName;
    }

    public CreativeTabs getCreativeTab() {
        return this.creativeTab;
    }

    /**
     * @deprecated see {@link #getItemName()}. To be removed in 1.13.
     */
    @Deprecated
    public String getDisplayName() {
        return this.itemName;
    }

    /**
     * @deprecated see {@link #getHeader()}. To be removed in 1.13.
     */
    @Deprecated
    public String getWelcomeMessage() {
        return this.header;
    }

    /**
     * @deprecated see {@link #hasCustomModel()}. To be removed in 1.13.
     */
    @Deprecated
    public boolean isCustomModel() {
        return this.customModel;
    }

    /**
     * @deprecated see {@link #shouldSpawnWithBook()}. To be removed in 1.13.
     */
    @Deprecated
    public boolean isSpawnWithBook() {
        return this.spawnWithBook;
    }

    /**
     * @deprecated see {@link BookBinder#addCategory(CategoryAbstract)}. To be removed in 1.13.
     */
    @Deprecated
    public void setCategoryList(List<CategoryAbstract> categoryList) {
        this.categories = categoryList;
    }

    /**
     * @deprecated see {@link BookBinder#setGuideTitle(String)}. To be removed in 1.13.
     */
    @Deprecated
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * @deprecated see {@link BookBinder#setHeader(String)}. To be removed in 1.13.
     */
    @Deprecated
    public void setWelcomeMessage(String header) {
        this.header = header;
    }

    /**
     * @deprecated see {@link BookBinder#setItemName(String)}. To be removed in 1.13.
     */
    @Deprecated
    public void setDisplayName(String displayName) {
        this.itemName = displayName;
    }

    /**
     * @deprecated see {@link BookBinder#setAuthor(String)}. To be removed in 1.13.
     */
    @Deprecated
    public void setAuthor(String author) {
        this.author = author;
    }

    /**
     * @deprecated see {@link BookBinder#setSpawnWithBook()}. To be removed in 1.13.
     */
    @Deprecated
    public void setSpawnWithBook(boolean spawnWithBook) {
        this.spawnWithBook = spawnWithBook;
    }

    /**
     * @deprecated see {@link BookBinder#setPageTexture(ResourceLocation)}. To be removed in 1.13.
     */
    @Deprecated
    public void setPageTexture(ResourceLocation pageTexture) {
        this.pageTexture = pageTexture;
    }

    /**
     * @deprecated see {@link BookBinder#setOutlineTexture(ResourceLocation)}. To be removed in 1.13.
     */
    @Deprecated
    public void setOutlineTexture(ResourceLocation outlineTexture) {
        this.outlineTexture = outlineTexture;
    }

    /**
     * @deprecated see {@link BookBinder#setHasCustomModel()}. To be removed in 1.13.
     */
    @Deprecated
    public void setCustomModel(boolean customModel) {
        this.customModel = customModel;
    }

    /**
     * @deprecated see {@link BookBinder#setColor(int)}. To be removed in 1.13.
     */
    @Deprecated
    public void setColor(Color color) {
        this.color = color;
    }

    /**
     * @deprecated see {@link BookBinder#BookBinder(ResourceLocation)}. To be removed in 1.13.
     */
    @Deprecated
    public void setRegistryName(ResourceLocation registryName) {
        this.registryName = registryName;
    }

    /**
     * @deprecated see {@link BookBinder#setCreativeTab(CreativeTabs)}. To be removed in 1.13.
     */
    @Deprecated
    public void setCreativeTab(CreativeTabs creativeTab) {
        this.creativeTab = creativeTab;
    }

    /**
     * @deprecated see {@link BookBinder#addCategory(CategoryAbstract)}. To be removed in 1.13.
     *
     * @param category - Add this category
     */
    @Deprecated
    public void addCategory(CategoryAbstract category) {
        this.categories.add(category);
    }

    /**
     * @deprecated see {@link BookBinder}. To be removed in 1.13.
     *
     * @param category - Remove this category
     */
    @Deprecated
    public void removeCategory(CategoryAbstract category) {
        this.categories.remove(category);
    }

    /**
     * @deprecated see {@link BookBinder#addCategory(CategoryAbstract)}. To be removed in 1.13.
     *
     * @param categories - Add these categories
     */
    @Deprecated
    public void addCategoryList(List<CategoryAbstract> categories) {
        this.categories.addAll(categories);
    }

    /**
     * @deprecated see {@link BookBinder}. To be removed in 1.13.
     *
     * @param categories - Remove these categories
     */
    @Deprecated
    public void removeCategoryList(List<CategoryAbstract> categories) {
        this.categories.removeAll(categories);
    }

    /**
     * @deprecated localize yourself with {@link #getTitle()}. To be removed in 1.13.
     *
     * @return - Localized book title
     */
    @Deprecated
    public String getLocalizedBookTitle() {
        return TextHelper.localizeEffect(getTitle());
    }

    /**
     * @deprecated localize yourself with {@link #getHeader()}. To be removed in 1.13.
     *
     * @return - Localized welcome message
     */
    @Deprecated
    public String getLocalizedWelcomeMessage() {
        return TextHelper.localizeEffect(getHeader());
    }

    /**
     * @deprecated localize yourself with {@link #getDisplayName()}. To be removed in 1.13.
     *
     * @return - Localized item display name
     */
    @Deprecated
    public String getLocalizedDisplayName() {
        return TextHelper.localize(getDisplayName());
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("categoryList", Joiner.on(", ").join(categories))
                .append("title", title)
                .append("header", header)
                .append("itemName", itemName)
                .append("author", author)
                .append("pageTexture", pageTexture)
                .append("outlineTexture", outlineTexture)
                .append("customModel", customModel)
                .append("color", color)
                .append("spawnWithBook", spawnWithBook)
                .append("registryName", registryName)
                .append("creativeTab", creativeTab)
                .toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Book book = (Book) o;

        return getRegistryName().equals(book.getRegistryName());

    }

    @Override
    public int hashCode() {
        return getRegistryName().hashCode();
    }
} public abstract class CategoryAbstract {

    public final Map<ResourceLocation, EntryAbstract> entries;
    public final String name;
    private String keyBase;

    public CategoryAbstract(Map<ResourceLocation, EntryAbstract> entries, String name) {
        this.entries = entries;
        this.name = name;
    }

    public CategoryAbstract(String name) {
        this(Maps.<ResourceLocation, EntryAbstract>newLinkedHashMap(), name);
    }

    /**
     * Adds an entry to this category.
     *
     * @param key   - The key of the entry to add.
     * @param entry - The entry to add.
     */
    public void addEntry(ResourceLocation key, EntryAbstract entry) {
        entries.put(key, entry);
    }

    /**
     * Adds an entry to this category.
     * <p>
     * Shorthand of {@link #addEntry(ResourceLocation, EntryAbstract)}. Requires {@link #withKeyBase(String)} to have been called.
     *
     * @param key   - The key of the entry to add.
     * @param entry - The entry to add.
     */
    public void addEntry(String key, EntryAbstract entry) {
        if (Strings.isNullOrEmpty(keyBase))
            throw new RuntimeException("keyBase in category with name '" + name + "' must be set.");

        addEntry(new ResourceLocation(keyBase, key), entry);
    }

    public void removeEntry(ResourceLocation key) {
        entries.remove(key);
    }

    public void addEntries(Map<ResourceLocation, EntryAbstract> entries) {
        this.entries.putAll(entries);
    }

    public void removeEntries(List<ResourceLocation> keys) {
        for (ResourceLocation key : keys)
            if (entries.containsKey(key))
                entries.remove(key);
    }

    /**
     * Obtains an entry from this category.
     * <p>
     * This <i>can</i> be null, however it is not marked as nullable to avoid annoying IDE warnings. I am making the
     * assumption that this will only be called while creating the book and thus the caller knows it exists.
     * <p>
     * If you are calling at any other time, make sure to nullcheck this.
     *
     * @param key - The key of the entry to obtain.
     * @return the found entry.
     */
    public EntryAbstract getEntry(ResourceLocation key) {
        return entries.get(key);
    }

    /**
     * Obtains an entry from this category.
     * <p>
     * Shorthand of {@link #getEntry(ResourceLocation)}. Requires {@link #withKeyBase(String)} to have been called.
     * <p>
     * This <i>can</i> be null, however it is not marked as nullable to avoid annoying IDE warnings. I am making the
     * assumption that this will only be called while creating the book and thus the caller knows it exists.
     * <p>
     * If you are calling at any other time, make sure to nullcheck this.
     *
     * @param key - The key of the entry to obtain.
     * @return the found entry.
     */
    public EntryAbstract getEntry(String key) {
        if (Strings.isNullOrEmpty(keyBase))
            throw new RuntimeException("keyBase in category with name '" + name + "' must be set.");

        return getEntry(new ResourceLocation(keyBase, key));
    }

    /**
     * Sets the domain to use for all ResourceLocation keys passed through {@link #getEntry(String)} and
     * {@link #addEntry(String, EntryAbstract)}
     * <p>
     * Required in order to use those.
     *
     * @param keyBase - The base domain for this entry.
     * @return self for chaining.
     */
    public CategoryAbstract withKeyBase(String keyBase) {
        this.keyBase = keyBase;
        return this;
    }

    /**
     * Obtains a localized copy of this category's name.
     *
     * @return a localized copy of this category's name.
     */
    public String getLocalizedName() {
        return TextHelper.localizeEffect(name);
    }

    public List<String> getTooltip() {
        return Lists.newArrayList(getLocalizedName());
    }

    @SideOnly(Side.CLIENT)
    public abstract void draw(Book book, int categoryX, int categoryY, int categoryWidth, int categoryHeight, int mouseX, int mouseY, GuiBase guiBase, boolean drawOnLeft, RenderItem renderItem);

    @SideOnly(Side.CLIENT)
    public abstract void drawExtras(Book book, int categoryX, int categoryY, int categoryWidth, int categoryHeight, int mouseX, int mouseY, GuiBase guiBase, boolean drawOnLeft, RenderItem renderItem);

    public abstract boolean canSee(EntityPlayer player, ItemStack bookStack);

    @SideOnly(Side.CLIENT)
    public abstract void onLeftClicked(Book book, int mouseX, int mouseY, EntityPlayer player, ItemStack bookStack);

    @SideOnly(Side.CLIENT)
    public abstract void onRightClicked(Book book, int mouseX, int mouseY, EntityPlayer player, ItemStack bookStack);

    @SideOnly(Side.CLIENT)
    public abstract void onInit(Book book, GuiHome guiHome, EntityPlayer player, ItemStack bookStack);

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        CategoryAbstract that = (CategoryAbstract) o;
        if (entries != null ? !entries.equals(that.entries) : that.entries != null) return false;
        if (name != null ? !name.equals(that.name) : that.name != null)
            return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = entries != null ? entries.hashCode() : 0;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
} public abstract class EntryAbstract {

    public final List<IPage> pageList;
    public final String name;
    public boolean unicode;

    public EntryAbstract(List<IPage> pageList, String name, boolean unicode) {
        this.pageList = pageList;
        this.name = name;
        this.unicode = unicode;
    }

    public EntryAbstract(List<IPage> pageList, String name) {
        this(pageList, name, false);
    }

    public EntryAbstract(String name, boolean unicode) {
        this(Lists.<IPage>newArrayList(), name, unicode);
    }

    public EntryAbstract(String name) {
        this(name, false);
    }

    public void addPage(IPage page) {
        this.pageList.add(page);
    }

    public void removePage(IPage page) {
        this.pageList.remove(page);
    }

    public void addPageList(List<IPage> pages) {
        this.pageList.addAll(pages);
    }

    public void removePageList(List<IPage> pages) {
        this.pageList.removeAll(pages);
    }

    public String getLocalizedName() {
        return TextHelper.localizeEffect(name);
    }

    @SideOnly(Side.CLIENT)
    public abstract void draw(Book book, CategoryAbstract category, int entryX, int entryY, int entryWidth, int entryHeight, int mouseX, int mouseY, GuiBase guiBase, FontRenderer renderer);

    @SideOnly(Side.CLIENT)
    public abstract void drawExtras(Book book, CategoryAbstract category, int entryX, int entryY, int entryWidth, int entryHeight, int mouseX, int mouseY, GuiBase guiBase, FontRenderer renderer);

    public abstract boolean canSee(EntityPlayer player, ItemStack bookStack);

    @SideOnly(Side.CLIENT)
    public abstract void onLeftClicked(Book book, CategoryAbstract category, int mouseX, int mouseY, EntityPlayer player, GuiCategory guiCategory);

    @SideOnly(Side.CLIENT)
    public abstract void onRightClicked(Book book, CategoryAbstract category, int mouseX, int mouseY, EntityPlayer player, GuiCategory guiCategory);

    @SideOnly(Side.CLIENT)
    public abstract void onInit(Book book, CategoryAbstract category, GuiCategory guiCategory, EntityPlayer player, ItemStack bookStack);

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EntryAbstract that = (EntryAbstract) o;
        if (pageList != null ? !pageList.equals(that.pageList) : that.pageList != null) return false;
        if (name != null ? !name.equals(that.name) : that.name != null)
            return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = pageList != null ? pageList.hashCode() : 0;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
} public class ItemGuideBook extends Item implements IGuideItem {

    @Nonnull
    private final Book book;

    public ItemGuideBook(@Nonnull Book book) {
        this.book = book;

        setMaxStackSize(1);
        setCreativeTab(book.getCreativeTab());
        setUnlocalizedName(GuideMod.ID + ".book." + book.getRegistryName().getResourceDomain() + "." + book.getRegistryName().getResourcePath());
    }

    @Override
    public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand) {
        ItemStack heldStack = player.getHeldItem(hand);

        BookEvent.Open event = new BookEvent.Open(book, heldStack, player);
        if (MinecraftForge.EVENT_BUS.post(event)) {
            player.sendStatusMessage(event.getCanceledText(), true);
            return ActionResult.newResult(EnumActionResult.FAIL, heldStack);
        }

        player.openGui(GuideMod.INSTANCE, GuideAPI.getIndexedBooks().indexOf(book), world, hand.ordinal(), 0, 0);
        return ActionResult.newResult(EnumActionResult.SUCCESS, heldStack);
    }

    @Override
    public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) {
        if (!world.isRemote || !player.isSneaking())
            return EnumActionResult.PASS;

        ItemStack stack = player.getHeldItem(hand);
        IBlockState state = world.getBlockState(pos);

        if (state.getBlock() instanceof IGuideLinked) {
            IGuideLinked guideLinked = (IGuideLinked) state.getBlock();
            ResourceLocation entryKey = guideLinked.getLinkedEntry(world, pos, player, stack);
            if (entryKey == null)
                return EnumActionResult.FAIL;

            for (CategoryAbstract category : book.getCategoryList()) {
                if (category.entries.containsKey(entryKey)) {
                    GuideMod.PROXY.openEntry(book, category, category.entries.get(entryKey), player, stack);
                    return EnumActionResult.SUCCESS;
                }
            }
        }

        return EnumActionResult.PASS;
    }

    @Override
    public String getItemStackDisplayName(ItemStack stack) {
        return !Strings.isNullOrEmpty(book.getItemName()) ? I18n.translateToLocal(getBook(stack).getItemName()) : super.getItemStackDisplayName(stack);
    }

    @Override
    public void addInformation(ItemStack stack, World playerIn, List<String> tooltip, ITooltipFlag advanced) {
        if (!Strings.isNullOrEmpty(book.getAuthor()))
            tooltip.add(TextHelper.localizeEffect(book.getAuthor()));
        if (!Strings.isNullOrEmpty(book.getAuthor()) && (advanced == TooltipFlags.ADVANCED))
            tooltip.add(book.getRegistryName().toString());
    }

    @Nullable
//    @Override TODO - Soft override because this hasn't been merged into Forge yet. https://github.com/MinecraftForge/MinecraftForge/pull/4330
    public String getCreatorModId(ItemStack stack) {
        return book.getRegistryName().getResourceDomain();
    }

    // IGuideItem

    @Override
    public Book getBook(ItemStack stack) {
        return book;
    }
} public class PageJsonRecipe extends PageIRecipe {

    private final ResourceLocation recipeId;

    public PageJsonRecipe(ResourceLocation recipeId) {
        super(null, null);

        this.recipeId = recipeId;
    }

    public void init() {
        this.recipe = ForgeRegistries.RECIPES.getValue(recipeId);
        this.iRecipeRenderer = getRenderer(recipe);
        this.isValid = recipe != null && iRecipeRenderer != null;
    }
} @SuppressWarnings("unchecked")
public class APISetter {

    public static void registerBook(Book book) {
        try {
            sanityCheck();
        } catch (IllegalAccessException e) {
            Throwables.propagate(e);
            return;
        }

        try {
            Field books = GuideAPI.class.getDeclaredField("BOOKS");
            books.setAccessible(true);
            Map<ResourceLocation, Book> BOOKS = (Map<ResourceLocation, Book>) books.get(null);
            BOOKS.put(book.getRegistryName(), book);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void setBookForStack(Book book, ItemStack stack) {
        try {
            sanityCheck();
        } catch (IllegalAccessException e) {
            Throwables.propagate(e);
            return;
        }

        try {
            Field stacks = GuideAPI.class.getDeclaredField("BOOK_TO_STACK");
            stacks.setAccessible(true);
            Map<Book, ItemStack> BOOK_TO_STACK = (Map<Book, ItemStack>) stacks.get(null);
            BOOK_TO_STACK.put(book, stack);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void setIndexedBooks(List<Book> books) {
        try {
            sanityCheck();
        } catch (IllegalAccessException e) {
            Throwables.propagate(e);
            return;
        }

        try {
            Field indexedBooks = GuideAPI.class.getDeclaredField("indexedBooks");
            indexedBooks.setAccessible(true);
            indexedBooks.set(null, books);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void sanityCheck() throws IllegalAccessException {
        ModContainer activeMod = Loader.instance().activeModContainer();
        if (!activeMod.getModId().equals(GuideMod.ID))
            throw new IllegalAccessException("Mod " + activeMod.getModId() + " tried to access an internal-only method in GuideAPI. Please report this.");
    }
} public class AnnotationHandler {

    public static final List<Pair<Book, IGuideBook>> BOOK_CLASSES = Lists.newArrayList();

    public static void gatherBooks(ASMDataTable dataTable) {
        for (EventPriority priority : EventPriority.values())
            for (ASMDataTable.ASMData data : dataTable.getAll(GuideBook.class.getCanonicalName())) {
                try {
                    Class<?> genericClass = Class.forName(data.getClassName());
                    if (!IGuideBook.class.isAssignableFrom(genericClass))
                        continue;

                    IGuideBook guideBook = (IGuideBook) genericClass.newInstance();
                    ModAnnotation.EnumHolder holder = (ModAnnotation.EnumHolder) data.getAnnotationInfo().get("priority");
                    EventPriority bookPriority = holder == null ? EventPriority.NORMAL : EventPriority.valueOf(holder.getValue());
                    if (priority != bookPriority)
                        continue;
                    Book book = guideBook.buildBook();
                    if (book == null)
                        continue;
                    APISetter.registerBook(book);
                    BOOK_CLASSES.add(Pair.of(book, guideBook));
                } catch (Exception e) {
                    LogHelper.error("Error registering book for class " + data.getClassName());
                    e.printStackTrace();
                }
            }

        APISetter.setIndexedBooks(Lists.newArrayList(GuideAPI.getBooks().values()));
    }
}
import amerifrance.guideapi.api.GuideAPI;
import amerifrance.guideapi.api.IGuideBook;
import amerifrance.guideapi.api.IPage;
import amerifrance.guideapi.api.impl.Book;
import amerifrance.guideapi.api.impl.abstraction.CategoryAbstract;
import amerifrance.guideapi.api.impl.abstraction.EntryAbstract;
import amerifrance.guideapi.item.ItemGuideBook;
import amerifrance.guideapi.page.PageJsonRecipe;
import amerifrance.guideapi.util.APISetter;
import amerifrance.guideapi.util.AnnotationHandler;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import org.apache.commons.lang3.tuple.Pair;
package amerifrance.guideapi;


@Mod.EventBusSubscriber
public class RegistrarGuideAPI {

    /*
     * Why Register<Biome>? Well, that's an easy one. We need to register our items during the registry event phase so they
     * can be properly used during init and beyond. However, we cannot handle it during Register<Item> because any mods
     * using @ObjectHolder will not have their Item fields populated. Thus, we must use an event that fires *after*
     * Item object holders are populated, but *before* the recipe registry. Since the events are fired in alphabetical order,
     * we just pick one that starts with a letter before "r".
     */
    @SubscribeEvent
    public static void registerItemsInADifferentRegistryEventBecauseLoadOrderingAndObjectHoldersAreImportant(RegistryEvent.Register<Biome> event) {
        AnnotationHandler.gatherBooks(GuideMod.dataTable);

        for (Book book : GuideAPI.getBooks().values()) {