package org.cyclops.cyclopscore.infobook;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.minecraftforge.client.event.RecipesUpdatedEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TagsUpdatedEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
import org.cyclops.cyclopscore.helper.MinecraftHelpers;
import org.cyclops.cyclopscore.infobook.pageelement.AdvancementRewards;

import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Registry for info books for a mod.
 * @author rubensworks
 */
public class InfoBookRegistry implements IInfoBookRegistry {

    private final Map<IInfoBook, String> bookPaths = Maps.newIdentityHashMap();
    private final Map<IInfoBook, InfoSection> bookRoots = Maps.newIdentityHashMap();
    private final List<SectionInjection> sectionInjections = Lists.newArrayList();

    public InfoBookRegistry() {
        MinecraftForge.EVENT_BUS.addListener(EventPriority.LOWEST, this::onClientTagsLoaded);
        MinecraftForge.EVENT_BUS.addListener(EventPriority.LOWEST, this::onClientRecipesLoaded);
        MinecraftForge.EVENT_BUS.addListener(this::onServerStarted);
    }

    @Override
    public void registerInfoBook(IInfoBook infoBook, String path) {
        bookPaths.put(infoBook, path);
    }

    @Override
    public void registerSection(IInfoBook infoBook, String parentSection, String sectionPath) {
        sectionInjections.add(new SectionInjection(infoBook, parentSection, sectionPath));
    }

    @Override
    public InfoSection getRoot(IInfoBook infoBook) {
        return bookRoots.get(infoBook);
    }

    // Reload infobooks if BOTH tags and recipes are initialized (can occur out-of-order in SMP)
    private volatile boolean infobookStageTags = false;
    private volatile boolean infobookStageRecipes = false;
    public void onClientTagsLoaded(TagsUpdatedEvent event) {
        infobookStageTags = true;
        if (infobookStageTags && infobookStageRecipes) {
            afterRecipesAndTagsLoaded();
        }
    }
    public void onClientRecipesLoaded(RecipesUpdatedEvent event) {
        infobookStageRecipes = true;
        if (infobookStageTags && infobookStageRecipes) {
            afterRecipesAndTagsLoaded();
        }
    }
    public void onServerStarted(FMLServerStartedEvent event) {
        if (!MinecraftHelpers.isClientSide()) {
            // Only call this on dedicated servers, as the RecipesUpdatedEvent won't be emitted there
            afterRecipesAndTagsLoaded();
        }
    }

    public void afterRecipesAndTagsLoaded() {
        AdvancementRewards.reset();
        this.infobookStageTags = false;
        this.infobookStageRecipes = false;

        // Load after recipes are loaded client-side
        for (Map.Entry<IInfoBook, String> entry : bookPaths.entrySet()) {
            bookRoots.put(entry.getKey(), InfoBookParser.initializeInfoBook(entry.getKey(), entry.getValue(), null));
            // Reset the infobook history
            entry.getKey().setCurrentSection(null);
        }

        // Load section injections
        for (SectionInjection sectionInjection : sectionInjections) {
            InfoSection section = sectionInjection.getInfoBook().getSection(sectionInjection.getParentSection());
            if (section == null) {
                throw new IllegalArgumentException(String.format("Could not find section '%s' in infobook '%s'.", sectionInjection.getParentSection(), sectionInjection.getInfoBook()));
            }
            section.registerSection(InfoBookParser.initializeInfoBook(sectionInjection.getInfoBook(), sectionInjection.getSectionPath(), section));
        }
    }

    private static final class SectionInjection {
        private final IInfoBook infoBook;
        private final String parentSection;
        private final String sectionPath;

        private SectionInjection(IInfoBook infoBook, String parentSection, String sectionPath) {
            this.infoBook = Objects.requireNonNull(infoBook);
            this.parentSection = parentSection;
            this.sectionPath = sectionPath;
        }

        public IInfoBook getInfoBook() {
            return infoBook;
        }

        public String getParentSection() {
            return parentSection;
        }

        public String getSectionPath() {
            return sectionPath;
        }
    }

}

@@ -40,48 +40,8 @@
 k;%0A%0A
-import com.google.common.collect.Lists;%0A
 impo
@@ -75,16 +75,16 @@
 t.Maps;%0A
+
 import n
@@ -487,31 +487,8 @@
 s;%0A%0A
-import java.util.List;%0A
 impo
@@ -530,16 +530,89 @@
 Objects;
+%0Aimport java.util.Queue;%0Aimport java.util.concurrent.LinkedBlockingQueue;
 %0A%0A/**%0A *
@@ -919,20 +919,21 @@
 e final 
-List
+Queue
 %3CSection
@@ -967,29 +967,57 @@
 s = 
-Lists.newArrayList();
+new LinkedBlockingQueue%3C%3E(); // Thread-safe queue
 %0A%0A  
@@ -1543,32 +1543,79 @@
  sectionPath) %7B%0A
+        synchronized (sectionInjections) %7B%0A    
         sectionI
@@ -1685,24 +1685,34 @@
 tionPath));%0A
+        %7D%0A
     %7D%0A%0A    @

Make infobook injections loading thread-safe, Closes #157