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.Util;
import net.minecraft.network.chat.ChatType;
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.serialization.DynamicOps;
import com.mojang.serialization.Dynamic;
import com.google.gson.internal.Streams;
import com.mojang.serialization.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.server.players.PlayerList;
import com.mojang.datafixers.DataFixer;
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 DataFixer dataFixer;
    private final PlayerList playerList;
    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 DataFixer dataFixer, final PlayerList abp, final ServerAdvancementManager us, final File file, final ServerPlayer ze) {
        this.advancements = Maps.newLinkedHashMap();
        this.visible = Sets.newLinkedHashSet();
        this.visibilityChanged = Sets.newLinkedHashSet();
        this.progressChanged = Sets.newLinkedHashSet();
        this.isFirstPacket = true;
        this.dataFixer = dataFixer;
        this.playerList = abp;
        this.file = file;
        this.player = ze;
        this.load(us);
    }
    
    public void setPlayer(final ServerPlayer ze) {
        this.player = ze;
    }
    
    public void stopListening() {
        for (final CriterionTrigger<?> ad3 : CriteriaTriggers.all()) {
            ad3.removePlayerListeners(this);
        }
    }
    
    public void reload(final ServerAdvancementManager us) {
        this.stopListening();
        this.advancements.clear();
        this.visible.clear();
        this.visibilityChanged.clear();
        this.progressChanged.clear();
        this.isFirstPacket = true;
        this.lastSelectedTab = null;
        this.load(us);
    }
    
    private void registerListeners(final ServerAdvancementManager us) {
        for (final Advancement w4 : us.getAllAdvancements()) {
            this.registerListeners(w4);
        }
    }
    
    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 w4 : list2) {
            this.ensureVisibility(w4);
        }
    }
    
    private void checkForAutomaticTriggers(final ServerAdvancementManager us) {
        for (final Advancement w4 : us.getAllAdvancements()) {
            if (w4.getCriteria().isEmpty()) {
                this.award(w4, "");
                w4.getRewards().grant(this.player);
            }
        }
    }
    
    private void load(final ServerAdvancementManager us) {
        if (this.file.isFile()) {
            try (final JsonReader jsonReader3 = new JsonReader(new StringReader(Files.toString(this.file, StandardCharsets.UTF_8)))) {
                jsonReader3.setLenient(false);
                Dynamic<JsonElement> dynamic5 = (Dynamic<JsonElement>)new Dynamic((DynamicOps)JsonOps.INSTANCE, Streams.parse(jsonReader3));
                if (!dynamic5.get("DataVersion").asNumber().result().isPresent()) {
                    dynamic5 = (Dynamic<JsonElement>)dynamic5.set("DataVersion", dynamic5.createInt(1343));
                }
                dynamic5 = (Dynamic<JsonElement>)this.dataFixer.update(DataFixTypes.ADVANCEMENTS.getType(), (Dynamic)dynamic5, dynamic5.get("DataVersion").asInt(0), SharedConstants.getCurrentVersion().getWorldVersion());
                dynamic5 = (Dynamic<JsonElement>)dynamic5.remove("DataVersion");
                final Map<ResourceLocation, AdvancementProgress> map6 = PlayerAdvancements.GSON.<Map<ResourceLocation, AdvancementProgress>>getAdapter(PlayerAdvancements.TYPE_TOKEN).fromJsonTree((JsonElement)dynamic5.getValue());
                if (map6 == null) {
                    throw new JsonParseException("Found null for advancements");
                }
                final Stream<Map.Entry<ResourceLocation, AdvancementProgress>> stream7 = map6.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getValue));
                for (final Map.Entry<ResourceLocation, AdvancementProgress> entry9 : stream7.collect(Collectors.<Map.Entry<ResourceLocation, AdvancementProgress>>toList())) {
                    final Advancement w10 = us.getAdvancement(entry9.getKey());
                    if (w10 == null) {
                        PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", entry9.getKey(), this.file);
                    }
                    else {
                        this.startProgress(w10, entry9.getValue());
                    }
                }
            }
            catch (JsonParseException jsonParseException3) {
                PlayerAdvancements.LOGGER.error("Couldn't parse player advancements in {}", this.file, jsonParseException3);
            }
            catch (IOException iOException3) {
                PlayerAdvancements.LOGGER.error("Couldn't access player advancements in {}", this.file, iOException3);
            }
        }
        this.checkForAutomaticTriggers(us);
        this.ensureAllVisible();
        this.registerListeners(us);
    }
    
    public void save() {
        final Map<ResourceLocation, AdvancementProgress> map2 = Maps.newHashMap();
        for (final Map.Entry<Advancement, AdvancementProgress> entry4 : this.advancements.entrySet()) {
            final AdvancementProgress y5 = entry4.getValue();
            if (y5.hasProgress()) {
                map2.put(entry4.getKey().getId(), y5);
            }
        }
        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 w, final String string) {
        boolean boolean4 = false;
        final AdvancementProgress y5 = this.getOrStartProgress(w);
        final boolean boolean5 = y5.isDone();
        if (y5.grantProgress(string)) {
            this.unregisterListeners(w);
            this.progressChanged.add(w);
            boolean4 = true;
            if (!boolean5 && y5.isDone()) {
                w.getRewards().grant(this.player);
                if (w.getDisplay() != null && w.getDisplay().shouldAnnounceChat() && this.player.level.getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
                    this.playerList.broadcastMessage(new TranslatableComponent("chat.type.advancement." + w.getDisplay().getFrame().getName(), new Object[] { this.player.getDisplayName(), w.getChatComponent() }), ChatType.SYSTEM, Util.NIL_UUID);
                }
            }
        }
        if (y5.isDone()) {
            this.ensureVisibility(w);
        }
        return boolean4;
    }
    
    public boolean revoke(final Advancement w, final String string) {
        boolean boolean4 = false;
        final AdvancementProgress y5 = this.getOrStartProgress(w);
        if (y5.revokeProgress(string)) {
            this.registerListeners(w);
            this.progressChanged.add(w);
            boolean4 = true;
        }
        if (!y5.hasProgress()) {
            this.ensureVisibility(w);
        }
        return boolean4;
    }
    
    private void registerListeners(final Advancement w) {
        final AdvancementProgress y3 = this.getOrStartProgress(w);
        if (y3.isDone()) {
            return;
        }
        for (final Map.Entry<String, Criterion> entry5 : w.getCriteria().entrySet()) {
            final CriterionProgress ac6 = y3.getCriterion(entry5.getKey());
            if (ac6 != null) {
                if (ac6.isDone()) {
                    continue;
                }
                final CriterionTriggerInstance ae7 = entry5.getValue().getTrigger();
                if (ae7 == null) {
                    continue;
                }
                final CriterionTrigger<CriterionTriggerInstance> ad8 = CriteriaTriggers.<CriterionTriggerInstance>getCriterion(ae7.getCriterion());
                if (ad8 == null) {
                    continue;
                }
                ad8.addPlayerListener(this, new CriterionTrigger.Listener<CriterionTriggerInstance>(ae7, w, entry5.getKey()));
            }
        }
    }
    
    private void unregisterListeners(final Advancement w) {
        final AdvancementProgress y3 = this.getOrStartProgress(w);
        for (final Map.Entry<String, Criterion> entry5 : w.getCriteria().entrySet()) {
            final CriterionProgress ac6 = y3.getCriterion(entry5.getKey());
            if (ac6 != null) {
                if (!ac6.isDone() && !y3.isDone()) {
                    continue;
                }
                final CriterionTriggerInstance ae7 = entry5.getValue().getTrigger();
                if (ae7 == null) {
                    continue;
                }
                final CriterionTrigger<CriterionTriggerInstance> ad8 = CriteriaTriggers.<CriterionTriggerInstance>getCriterion(ae7.getCriterion());
                if (ad8 == null) {
                    continue;
                }
                ad8.removePlayerListener(this, new CriterionTrigger.Listener<CriterionTriggerInstance>(ae7, w, entry5.getKey()));
            }
        }
    }
    
    public void flushDirty(final ServerPlayer ze) {
        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 w7 : this.progressChanged) {
                if (this.visible.contains(w7)) {
                    map3.put(w7.getId(), this.advancements.get(w7));
                }
            }
            for (final Advancement w7 : this.visibilityChanged) {
                if (this.visible.contains(w7)) {
                    set4.add(w7);
                }
                else {
                    set5.add(w7.getId());
                }
            }
            if (this.isFirstPacket || !map3.isEmpty() || !set4.isEmpty() || !set5.isEmpty()) {
                ze.connection.send(new ClientboundUpdateAdvancementsPacket(this.isFirstPacket, set4, set5, map3));
                this.visibilityChanged.clear();
                this.progressChanged.clear();
            }
        }
        this.isFirstPacket = false;
    }
    
    public void setSelectedTab(@Nullable final Advancement w) {
        final Advancement w2 = this.lastSelectedTab;
        if (w != null && w.getParent() == null && w.getDisplay() != null) {
            this.lastSelectedTab = w;
        }
        else {
            this.lastSelectedTab = null;
        }
        if (w2 != this.lastSelectedTab) {
            this.player.connection.send(new ClientboundSelectAdvancementsTabPacket((this.lastSelectedTab == null) ? null : this.lastSelectedTab.getId()));
        }
    }
    
    public AdvancementProgress getOrStartProgress(final Advancement w) {
        AdvancementProgress y3 = this.advancements.get(w);
        if (y3 == null) {
            y3 = new AdvancementProgress();
            this.startProgress(w, y3);
        }
        return y3;
    }
    
    private void startProgress(final Advancement w, final AdvancementProgress y) {
        y.update(w.getCriteria(), w.getRequirements());
        this.advancements.put(w, y);
    }
    
    private void ensureVisibility(final Advancement w) {
        final boolean boolean3 = this.shouldBeVisible(w);
        final boolean boolean4 = this.visible.contains(w);
        if (boolean3 && !boolean4) {
            this.visible.add(w);
            this.visibilityChanged.add(w);
            if (this.advancements.containsKey(w)) {
                this.progressChanged.add(w);
            }
        }
        else if (!boolean3 && boolean4) {
            this.visible.remove(w);
            this.visibilityChanged.add(w);
        }
        if (boolean3 != boolean4 && w.getParent() != null) {
            this.ensureVisibility(w.getParent());
        }
        for (final Advancement w2 : w.getChildren()) {
            this.ensureVisibility(w2);
        }
    }
    
    private boolean shouldBeVisible(Advancement w) {
        for (int integer3 = 0; w != null && integer3 <= 2; w = w.getParent(), ++integer3) {
            if (integer3 == 0 && this.hasCompletedChildrenOrSelf(w)) {
                return true;
            }
            if (w.getDisplay() == null) {
                return false;
            }
            final AdvancementProgress y4 = this.getOrStartProgress(w);
            if (y4.isDone()) {
                return true;
            }
            if (w.getDisplay().isHidden()) {
                return false;
            }
        }
        return false;
    }
    
    private boolean hasCompletedChildrenOrSelf(final Advancement w) {
        final AdvancementProgress y3 = this.getOrStartProgress(w);
        if (y3.isDone()) {
            return true;
        }
        for (final Advancement w2 : w.getChildren()) {
            if (this.hasCompletedChildrenOrSelf(w2)) {
                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>>() {};
    }
}
