package net.minecraft.server;

import java.lang.reflect.Type;
import com.google.gson.GsonBuilder;
import org.apache.logging.log4j.LogManager;
import net.minecraft.network.protocol.game.ClientboundSelectAdvancementsTabPacket;
import net.minecraft.network.protocol.Packet;
import java.util.Collection;
import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket;
import net.minecraft.advancements.CriterionProgress;
import net.minecraft.advancements.CriterionTriggerInstance;
import net.minecraft.advancements.Criterion;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.level.GameRules;
import java.io.Writer;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import com.google.common.base.Charsets;
import java.io.FileOutputStream;
import java.util.stream.Stream;
import java.io.IOException;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.function.Function;
import java.util.Comparator;
import com.google.gson.JsonParseException;
import com.google.gson.JsonElement;
import net.minecraft.SharedConstants;
import net.minecraft.util.datafix.DataFixTypes;
import com.mojang.datafixers.types.DynamicOps;
import com.mojang.datafixers.Dynamic;
import com.google.gson.internal.Streams;
import com.mojang.datafixers.types.JsonOps;
import java.io.Reader;
import com.google.gson.stream.JsonReader;
import java.io.StringReader;
import com.google.common.io.Files;
import java.nio.charset.StandardCharsets;
import java.util.List;
import com.google.common.collect.Lists;
import java.util.Iterator;
import net.minecraft.advancements.CriterionTrigger;
import net.minecraft.advancements.CriteriaTriggers;
import com.google.common.collect.Sets;
import com.google.common.collect.Maps;
import javax.annotation.Nullable;
import net.minecraft.server.level.ServerPlayer;
import java.util.Set;
import net.minecraft.advancements.Advancement;
import java.io.File;
import net.minecraft.advancements.AdvancementProgress;
import net.minecraft.resources.ResourceLocation;
import java.util.Map;
import com.google.gson.reflect.TypeToken;
import com.google.gson.Gson;
import org.apache.logging.log4j.Logger;

public class PlayerAdvancements {
    private static final Logger LOGGER;
    private static final Gson GSON;
    private static final TypeToken<Map<ResourceLocation, AdvancementProgress>> TYPE_TOKEN;
    private final MinecraftServer server;
    private final File file;
    private final Map<Advancement, AdvancementProgress> advancements;
    private final Set<Advancement> visible;
    private final Set<Advancement> visibilityChanged;
    private final Set<Advancement> progressChanged;
    private ServerPlayer player;
    @Nullable
    private Advancement lastSelectedTab;
    private boolean isFirstPacket;
    
    public PlayerAdvancements(final MinecraftServer minecraftServer, final File file, final ServerPlayer xe) {
        this.advancements = Maps.newLinkedHashMap();
        this.visible = Sets.newLinkedHashSet();
        this.visibilityChanged = Sets.newLinkedHashSet();
        this.progressChanged = Sets.newLinkedHashSet();
        this.isFirstPacket = true;
        this.server = minecraftServer;
        this.file = file;
        this.player = xe;
        this.load();
    }
    
    public void setPlayer(final ServerPlayer xe) {
        this.player = xe;
    }
    
    public void stopListening() {
        for (final CriterionTrigger<?> ab3 : CriteriaTriggers.all()) {
            ab3.removePlayerListeners(this);
        }
    }
    
    public void reload() {
        this.stopListening();
        this.advancements.clear();
        this.visible.clear();
        this.visibilityChanged.clear();
        this.progressChanged.clear();
        this.isFirstPacket = true;
        this.lastSelectedTab = null;
        this.load();
    }
    
    private void registerListeners() {
        for (final Advancement u3 : this.server.getAdvancements().getAllAdvancements()) {
            this.registerListeners(u3);
        }
    }
    
    private void ensureAllVisible() {
        final List<Advancement> list2 = Lists.newArrayList();
        for (final Map.Entry<Advancement, AdvancementProgress> entry4 : this.advancements.entrySet()) {
            if (entry4.getValue().isDone()) {
                list2.add(entry4.getKey());
                this.progressChanged.add(entry4.getKey());
            }
        }
        for (final Advancement u4 : list2) {
            this.ensureVisibility(u4);
        }
    }
    
    private void checkForAutomaticTriggers() {
        for (final Advancement u3 : this.server.getAdvancements().getAllAdvancements()) {
            if (u3.getCriteria().isEmpty()) {
                this.award(u3, "");
                u3.getRewards().grant(this.player);
            }
        }
    }
    
    private void load() {
        if (this.file.isFile()) {
            try (final JsonReader jsonReader2 = new JsonReader(new StringReader(Files.toString(this.file, StandardCharsets.UTF_8)))) {
                jsonReader2.setLenient(false);
                Dynamic<JsonElement> dynamic4 = (Dynamic<JsonElement>)new Dynamic((DynamicOps)JsonOps.INSTANCE, Streams.parse(jsonReader2));
                if (!dynamic4.get("DataVersion").asNumber().isPresent()) {
                    dynamic4 = (Dynamic<JsonElement>)dynamic4.set("DataVersion", dynamic4.createInt(1343));
                }
                dynamic4 = (Dynamic<JsonElement>)this.server.getFixerUpper().update(DataFixTypes.ADVANCEMENTS.getType(), (Dynamic)dynamic4, dynamic4.get("DataVersion").asInt(0), SharedConstants.getCurrentVersion().getWorldVersion());
                dynamic4 = (Dynamic<JsonElement>)dynamic4.remove("DataVersion");
                final Map<ResourceLocation, AdvancementProgress> map5 = PlayerAdvancements.GSON.<Map<ResourceLocation, AdvancementProgress>>getAdapter(PlayerAdvancements.TYPE_TOKEN).fromJsonTree((JsonElement)dynamic4.getValue());
                if (map5 == null) {
                    throw new JsonParseException("Found null for advancements");
                }
                final Stream<Map.Entry<ResourceLocation, AdvancementProgress>> stream6 = map5.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getValue));
                for (final Map.Entry<ResourceLocation, AdvancementProgress> entry8 : stream6.collect(Collectors.<Map.Entry<ResourceLocation, AdvancementProgress>>toList())) {
                    final Advancement u9 = this.server.getAdvancements().getAdvancement(entry8.getKey());
                    if (u9 == null) {
                        PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", entry8.getKey(), this.file);
                    }
                    else {
                        this.startProgress(u9, entry8.getValue());
                    }
                }
            }
            catch (JsonParseException jsonParseException2) {
                PlayerAdvancements.LOGGER.error("Couldn't parse player advancements in {}", this.file, jsonParseException2);
            }
            catch (IOException iOException2) {
                PlayerAdvancements.LOGGER.error("Couldn't access player advancements in {}", this.file, iOException2);
            }
        }
        this.checkForAutomaticTriggers();
        this.ensureAllVisible();
        this.registerListeners();
    }
    
    public void save() {
        final Map<ResourceLocation, AdvancementProgress> map2 = Maps.newHashMap();
        for (final Map.Entry<Advancement, AdvancementProgress> entry4 : this.advancements.entrySet()) {
            final AdvancementProgress w5 = entry4.getValue();
            if (w5.hasProgress()) {
                map2.put(entry4.getKey().getId(), w5);
            }
        }
        if (this.file.getParentFile() != null) {
            this.file.getParentFile().mkdirs();
        }
        final JsonElement jsonElement3 = PlayerAdvancements.GSON.toJsonTree(map2);
        jsonElement3.getAsJsonObject().addProperty("DataVersion", SharedConstants.getCurrentVersion().getWorldVersion());
        try (final OutputStream outputStream4 = new FileOutputStream(this.file);
             final Writer writer6 = new OutputStreamWriter(outputStream4, Charsets.UTF_8.newEncoder())) {
            PlayerAdvancements.GSON.toJson(jsonElement3, writer6);
        }
        catch (IOException iOException4) {
            PlayerAdvancements.LOGGER.error("Couldn't save player advancements to {}", this.file, iOException4);
        }
    }
    
    public boolean award(final Advancement u, final String string) {
        boolean boolean4 = false;
        final AdvancementProgress w5 = this.getOrStartProgress(u);
        final boolean boolean5 = w5.isDone();
        if (w5.grantProgress(string)) {
            this.unregisterListeners(u);
            this.progressChanged.add(u);
            boolean4 = true;
            if (!boolean5 && w5.isDone()) {
                u.getRewards().grant(this.player);
                if (u.getDisplay() != null && u.getDisplay().shouldAnnounceChat() && this.player.level.getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
                    this.server.getPlayerList().broadcastMessage(new TranslatableComponent("chat.type.advancement." + u.getDisplay().getFrame().getName(), new Object[] { this.player.getDisplayName(), u.getChatComponent() }));
                }
            }
        }
        if (w5.isDone()) {
            this.ensureVisibility(u);
        }
        return boolean4;
    }
    
    public boolean revoke(final Advancement u, final String string) {
        boolean boolean4 = false;
        final AdvancementProgress w5 = this.getOrStartProgress(u);
        if (w5.revokeProgress(string)) {
            this.registerListeners(u);
            this.progressChanged.add(u);
            boolean4 = true;
        }
        if (!w5.hasProgress()) {
            this.ensureVisibility(u);
        }
        return boolean4;
    }
    
    private void registerListeners(final Advancement u) {
        final AdvancementProgress w3 = this.getOrStartProgress(u);
        if (w3.isDone()) {
            return;
        }
        for (final Map.Entry<String, Criterion> entry5 : u.getCriteria().entrySet()) {
            final CriterionProgress aa6 = w3.getCriterion(entry5.getKey());
            if (aa6 != null) {
                if (aa6.isDone()) {
                    continue;
                }
                final CriterionTriggerInstance ac7 = entry5.getValue().getTrigger();
                if (ac7 == null) {
                    continue;
                }
                final CriterionTrigger<CriterionTriggerInstance> ab8 = CriteriaTriggers.<CriterionTriggerInstance>getCriterion(ac7.getCriterion());
                if (ab8 == null) {
                    continue;
                }
                ab8.addPlayerListener(this, new CriterionTrigger.Listener<CriterionTriggerInstance>(ac7, u, entry5.getKey()));
            }
        }
    }
    
    private void unregisterListeners(final Advancement u) {
        final AdvancementProgress w3 = this.getOrStartProgress(u);
        for (final Map.Entry<String, Criterion> entry5 : u.getCriteria().entrySet()) {
            final CriterionProgress aa6 = w3.getCriterion(entry5.getKey());
            if (aa6 != null) {
                if (!aa6.isDone() && !w3.isDone()) {
                    continue;
                }
                final CriterionTriggerInstance ac7 = entry5.getValue().getTrigger();
                if (ac7 == null) {
                    continue;
                }
                final CriterionTrigger<CriterionTriggerInstance> ab8 = CriteriaTriggers.<CriterionTriggerInstance>getCriterion(ac7.getCriterion());
                if (ab8 == null) {
                    continue;
                }
                ab8.removePlayerListener(this, new CriterionTrigger.Listener<CriterionTriggerInstance>(ac7, u, entry5.getKey()));
            }
        }
    }
    
    public void flushDirty(final ServerPlayer xe) {
        if (this.isFirstPacket || !this.visibilityChanged.isEmpty() || !this.progressChanged.isEmpty()) {
            final Map<ResourceLocation, AdvancementProgress> map3 = Maps.newHashMap();
            final Set<Advancement> set4 = Sets.newLinkedHashSet();
            final Set<ResourceLocation> set5 = Sets.newLinkedHashSet();
            for (final Advancement u7 : this.progressChanged) {
                if (this.visible.contains(u7)) {
                    map3.put(u7.getId(), this.advancements.get(u7));
                }
            }
            for (final Advancement u7 : this.visibilityChanged) {
                if (this.visible.contains(u7)) {
                    set4.add(u7);
                }
                else {
                    set5.add(u7.getId());
                }
            }
            if (this.isFirstPacket || !map3.isEmpty() || !set4.isEmpty() || !set5.isEmpty()) {
                xe.connection.send(new ClientboundUpdateAdvancementsPacket(this.isFirstPacket, set4, set5, map3));
                this.visibilityChanged.clear();
                this.progressChanged.clear();
            }
        }
        this.isFirstPacket = false;
    }
    
    public void setSelectedTab(@Nullable final Advancement u) {
        final Advancement u2 = this.lastSelectedTab;
        if (u != null && u.getParent() == null && u.getDisplay() != null) {
            this.lastSelectedTab = u;
        }
        else {
            this.lastSelectedTab = null;
        }
        if (u2 != this.lastSelectedTab) {
            this.player.connection.send(new ClientboundSelectAdvancementsTabPacket((this.lastSelectedTab == null) ? null : this.lastSelectedTab.getId()));
        }
    }
    
    public AdvancementProgress getOrStartProgress(final Advancement u) {
        AdvancementProgress w3 = this.advancements.get(u);
        if (w3 == null) {
            w3 = new AdvancementProgress();
            this.startProgress(u, w3);
        }
        return w3;
    }
    
    private void startProgress(final Advancement u, final AdvancementProgress w) {
        w.update(u.getCriteria(), u.getRequirements());
        this.advancements.put(u, w);
    }
    
    private void ensureVisibility(final Advancement u) {
        final boolean boolean3 = this.shouldBeVisible(u);
        final boolean boolean4 = this.visible.contains(u);
        if (boolean3 && !boolean4) {
            this.visible.add(u);
            this.visibilityChanged.add(u);
            if (this.advancements.containsKey(u)) {
                this.progressChanged.add(u);
            }
        }
        else if (!boolean3 && boolean4) {
            this.visible.remove(u);
            this.visibilityChanged.add(u);
        }
        if (boolean3 != boolean4 && u.getParent() != null) {
            this.ensureVisibility(u.getParent());
        }
        for (final Advancement u2 : u.getChildren()) {
            this.ensureVisibility(u2);
        }
    }
    
    private boolean shouldBeVisible(Advancement u) {
        for (int integer3 = 0; u != null && integer3 <= 2; u = u.getParent(), ++integer3) {
            if (integer3 == 0 && this.hasCompletedChildrenOrSelf(u)) {
                return true;
            }
            if (u.getDisplay() == null) {
                return false;
            }
            final AdvancementProgress w4 = this.getOrStartProgress(u);
            if (w4.isDone()) {
                return true;
            }
            if (u.getDisplay().isHidden()) {
                return false;
            }
        }
        return false;
    }
    
    private boolean hasCompletedChildrenOrSelf(final Advancement u) {
        final AdvancementProgress w3 = this.getOrStartProgress(u);
        if (w3.isDone()) {
            return true;
        }
        for (final Advancement u2 : u.getChildren()) {
            if (this.hasCompletedChildrenOrSelf(u2)) {
                return true;
            }
        }
        return false;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        GSON = new GsonBuilder().registerTypeAdapter(AdvancementProgress.class, new AdvancementProgress.Serializer()).registerTypeAdapter(ResourceLocation.class, new ResourceLocation.Serializer()).setPrettyPrinting().create();
        TYPE_TOKEN = new TypeToken<Map<ResourceLocation, AdvancementProgress>>() {};
    }
}
