package net.minecraft.util.worldupdate;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.logging.log4j.LogManager;
import java.util.regex.Matcher;
import net.minecraft.world.level.chunk.storage.RegionFile;
import com.google.common.collect.Lists;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.UnmodifiableIterator;
import net.minecraft.nbt.CompoundTag;
import java.util.List;
import java.util.Iterator;
import net.minecraft.ReportedException;
import java.io.IOException;
import net.minecraft.SharedConstants;
import net.minecraft.world.level.chunk.storage.ChunkStorage;
import net.minecraft.world.level.ChunkPos;
import java.util.ListIterator;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.MinecraftServer;
import net.minecraft.network.chat.TranslatableComponent;
import it.unimi.dsi.fastutil.objects.Object2FloatMaps;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.Object2FloatOpenCustomHashMap;
import net.minecraft.Util;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.DimensionDataStorage;
import java.util.regex.Pattern;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.dimension.DimensionType;
import it.unimi.dsi.fastutil.objects.Object2FloatMap;
import java.io.File;
import net.minecraft.world.level.storage.LevelStorage;
import java.util.concurrent.ThreadFactory;
import org.apache.logging.log4j.Logger;

public class WorldUpgrader {
    private static final Logger LOGGER;
    private static final ThreadFactory THREAD_FACTORY;
    private final String levelName;
    private final boolean eraseCache;
    private final LevelStorage levelStorage;
    private final Thread thread;
    private final File pathToWorld;
    private volatile boolean running;
    private volatile boolean finished;
    private volatile float progress;
    private volatile int totalChunks;
    private volatile int converted;
    private volatile int skipped;
    private final Object2FloatMap<DimensionType> progressMap;
    private volatile Component status;
    private static final Pattern REGEX;
    private final DimensionDataStorage overworldDataStorage;
    
    public WorldUpgrader(final String string, final LevelStorageSource crp, final LevelData crm, final boolean boolean4) {
        this.running = true;
        this.progressMap = (Object2FloatMap<DimensionType>)Object2FloatMaps.synchronize((Object2FloatMap)new Object2FloatOpenCustomHashMap((Hash.Strategy)Util.identityStrategy()));
        this.status = new TranslatableComponent("optimizeWorld.stage.counting", new Object[0]);
        this.levelName = crm.getLevelName();
        this.eraseCache = boolean4;
        (this.levelStorage = crp.selectLevel(string, null)).saveLevelData(crm);
        this.overworldDataStorage = new DimensionDataStorage(new File(DimensionType.OVERWORLD.getStorageFolder(this.levelStorage.getFolder()), "data"), this.levelStorage.getFixerUpper());
        this.pathToWorld = this.levelStorage.getFolder();
        (this.thread = WorldUpgrader.THREAD_FACTORY.newThread(this::work)).setUncaughtExceptionHandler((thread, throwable) -> {
            WorldUpgrader.LOGGER.error("Error upgrading world", throwable);
            this.status = new TranslatableComponent("optimizeWorld.stage.failed", new Object[0]);
            this.finished = true;
            return;
        });
        this.thread.start();
    }
    
    public void cancel() {
        this.running = false;
        try {
            this.thread.join();
        }
        catch (InterruptedException ex) {}
    }
    
    private void work() {
        final File file2 = this.levelStorage.getFolder();
        this.totalChunks = 0;
        final ImmutableMap.Builder<DimensionType, ListIterator<ChunkPos>> builder3 = ImmutableMap.<DimensionType, ListIterator<ChunkPos>>builder();
        for (final DimensionType cbi5 : DimensionType.getAllTypes()) {
            final List<ChunkPos> list6 = this.getAllChunkPos(cbi5);
            builder3.put(cbi5, list6.listIterator());
            this.totalChunks += list6.size();
        }
        if (this.totalChunks == 0) {
            this.finished = true;
            return;
        }
        final float float4 = (float)this.totalChunks;
        final ImmutableMap<DimensionType, ListIterator<ChunkPos>> immutableMap5 = builder3.build();
        final ImmutableMap.Builder<DimensionType, ChunkStorage> builder4 = ImmutableMap.<DimensionType, ChunkStorage>builder();
        for (final DimensionType cbi6 : DimensionType.getAllTypes()) {
            final File file3 = cbi6.getStorageFolder(file2);
            builder4.put(cbi6, new ChunkStorage(new File(file3, "region"), this.levelStorage.getFixerUpper()));
        }
        final ImmutableMap<DimensionType, ChunkStorage> immutableMap6 = builder4.build();
        long long8 = Util.getMillis();
        this.status = new TranslatableComponent("optimizeWorld.stage.upgrading", new Object[0]);
        while (this.running) {
            boolean boolean10 = false;
            float float5 = 0.0f;
            for (final DimensionType cbi7 : DimensionType.getAllTypes()) {
                final ListIterator<ChunkPos> listIterator14 = immutableMap5.get(cbi7);
                final ChunkStorage cay15 = immutableMap6.get(cbi7);
                if (listIterator14.hasNext()) {
                    final ChunkPos bjh16 = listIterator14.next();
                    boolean boolean11 = false;
                    try {
                        final CompoundTag jt18 = cay15.read(bjh16);
                        if (jt18 != null) {
                            final int integer19 = ChunkStorage.getVersion(jt18);
                            final CompoundTag jt19 = cay15.upgradeChunkTag(cbi7, () -> this.overworldDataStorage, jt18);
                            final CompoundTag jt20 = jt19.getCompound("Level");
                            final ChunkPos bjh17 = new ChunkPos(jt20.getInt("xPos"), jt20.getInt("zPos"));
                            if (!bjh17.equals(bjh16)) {
                                WorldUpgrader.LOGGER.warn("Chunk {} has invalid position {}", bjh16, bjh17);
                            }
                            boolean boolean12 = integer19 < SharedConstants.getCurrentVersion().getWorldVersion();
                            if (this.eraseCache) {
                                boolean12 = (boolean12 || jt20.contains("Heightmaps"));
                                jt20.remove("Heightmaps");
                                boolean12 = (boolean12 || jt20.contains("isLightOn"));
                                jt20.remove("isLightOn");
                            }
                            if (boolean12) {
                                cay15.write(bjh16, jt19);
                                boolean11 = true;
                            }
                        }
                    }
                    catch (ReportedException q18) {
                        final Throwable throwable19 = q18.getCause();
                        if (!(throwable19 instanceof IOException)) {
                            throw q18;
                        }
                        WorldUpgrader.LOGGER.error("Error upgrading chunk {}", bjh16, throwable19);
                    }
                    catch (IOException iOException18) {
                        WorldUpgrader.LOGGER.error("Error upgrading chunk {}", bjh16, iOException18);
                    }
                    if (boolean11) {
                        ++this.converted;
                    }
                    else {
                        ++this.skipped;
                    }
                    boolean10 = true;
                }
                final float float6 = listIterator14.nextIndex() / float4;
                this.progressMap.put(cbi7, float6);
                float5 += float6;
            }
            this.progress = float5;
            if (!boolean10) {
                this.running = false;
            }
        }
        this.status = new TranslatableComponent("optimizeWorld.stage.finished", new Object[0]);
        for (final ChunkStorage cay16 : immutableMap6.values()) {
            try {
                cay16.close();
            }
            catch (IOException iOException19) {
                WorldUpgrader.LOGGER.error("Error upgrading chunk", (Throwable)iOException19);
            }
        }
        this.overworldDataStorage.save();
        long8 = Util.getMillis() - long8;
        WorldUpgrader.LOGGER.info("World optimizaton finished after {} ms", long8);
        this.finished = true;
    }
    
    private List<ChunkPos> getAllChunkPos(final DimensionType cbi) {
        final File file3 = cbi.getStorageFolder(this.pathToWorld);
        final File file4 = new File(file3, "region");
        final File[] arr5 = file4.listFiles((file, string) -> string.endsWith(".mca"));
        if (arr5 == null) {
            return ImmutableList.of();
        }
        final List<ChunkPos> list6 = Lists.newArrayList();
        for (final File file5 : arr5) {
            final Matcher matcher11 = WorldUpgrader.REGEX.matcher(file5.getName());
            if (matcher11.matches()) {
                final int integer12 = Integer.parseInt(matcher11.group(1)) << 5;
                final int integer13 = Integer.parseInt(matcher11.group(2)) << 5;
                try (final RegionFile cbc14 = new RegionFile(file5, file4)) {
                    for (int integer14 = 0; integer14 < 32; ++integer14) {
                        for (int integer15 = 0; integer15 < 32; ++integer15) {
                            final ChunkPos bjh18 = new ChunkPos(integer14 + integer12, integer15 + integer13);
                            if (cbc14.doesChunkExist(bjh18)) {
                                list6.add(bjh18);
                            }
                        }
                    }
                }
                catch (Throwable t3) {}
            }
        }
        return list6;
    }
    
    public boolean isFinished() {
        return this.finished;
    }
    
    public float dimensionProgress(final DimensionType cbi) {
        return this.progressMap.getFloat(cbi);
    }
    
    public float getProgress() {
        return this.progress;
    }
    
    public int getTotalChunks() {
        return this.totalChunks;
    }
    
    public int getConverted() {
        return this.converted;
    }
    
    public int getSkipped() {
        return this.skipped;
    }
    
    public Component getStatus() {
        return this.status;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        THREAD_FACTORY = new ThreadFactoryBuilder().setDaemon(true).build();
        REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$");
    }
}
