package net.minecraft.world.level.levelgen.structure.templatesystem;

import org.apache.logging.log4j.LogManager;
import java.nio.file.InvalidPathException;
import net.minecraft.ResourceLocationException;
import net.minecraft.FileUtil;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import java.io.InputStream;
import java.io.IOException;
import java.io.FileInputStream;
import net.minecraft.server.packs.resources.Resource;
import java.io.FileNotFoundException;
import net.minecraft.server.packs.resources.ResourceManager;
import javax.annotation.Nullable;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import com.google.common.collect.Maps;
import java.io.File;
import java.nio.file.Path;
import net.minecraft.server.MinecraftServer;
import com.mojang.datafixers.DataFixer;
import net.minecraft.resources.ResourceLocation;
import java.util.Map;
import org.apache.logging.log4j.Logger;
import net.minecraft.server.packs.resources.ResourceManagerReloadListener;

public class StructureManager implements ResourceManagerReloadListener {
    private static final Logger LOGGER;
    private final Map<ResourceLocation, StructureTemplate> structureRepository;
    private final DataFixer fixerUpper;
    private final MinecraftServer server;
    private final Path generatedDir;
    
    public StructureManager(final MinecraftServer minecraftServer, final File file, final DataFixer dataFixer) {
        this.structureRepository = Maps.newHashMap();
        this.server = minecraftServer;
        this.fixerUpper = dataFixer;
        this.generatedDir = file.toPath().resolve("generated").normalize();
        minecraftServer.getResources().registerReloadListener(this);
    }
    
    public StructureTemplate getOrCreate(final ResourceLocation sm) {
        StructureTemplate cms3 = this.get(sm);
        if (cms3 == null) {
            cms3 = new StructureTemplate();
            this.structureRepository.put(sm, cms3);
        }
        return cms3;
    }
    
    @Nullable
    public StructureTemplate get(final ResourceLocation sm) {
        final StructureTemplate cms3;
        return this.structureRepository.computeIfAbsent(sm, sm -> {
            cms3 = this.loadFromGenerated(sm);
            return (cms3 != null) ? cms3 : this.loadFromResource(sm);
        });
    }
    
    @Override
    public void onResourceManagerReload(final ResourceManager zb) {
        this.structureRepository.clear();
    }
    
    @Nullable
    private StructureTemplate loadFromResource(final ResourceLocation sm) {
        final ResourceLocation sm2 = new ResourceLocation(sm.getNamespace(), "structures/" + sm.getPath() + ".nbt");
        try (final Resource za4 = this.server.getResources().getResource(sm2)) {
            return this.readStructure(za4.getInputStream());
        }
        catch (FileNotFoundException fileNotFoundException4) {
            return null;
        }
        catch (Throwable throwable4) {
            StructureManager.LOGGER.error("Couldn't load structure {}: {}", sm, throwable4.toString());
            return null;
        }
    }
    
    @Nullable
    private StructureTemplate loadFromGenerated(final ResourceLocation sm) {
        if (!this.generatedDir.toFile().isDirectory()) {
            return null;
        }
        final Path path3 = this.createAndValidatePathToStructure(sm, ".nbt");
        try (final InputStream inputStream4 = new FileInputStream(path3.toFile())) {
            return this.readStructure(inputStream4);
        }
        catch (FileNotFoundException fileNotFoundException4) {
            return null;
        }
        catch (IOException iOException4) {
            StructureManager.LOGGER.error("Couldn't load structure from {}", path3, iOException4);
            return null;
        }
    }
    
    private StructureTemplate readStructure(final InputStream inputStream) throws IOException {
        final CompoundTag jt3 = NbtIo.readCompressed(inputStream);
        return this.readStructure(jt3);
    }
    
    public StructureTemplate readStructure(final CompoundTag jt) {
        if (!jt.contains("DataVersion", 99)) {
            jt.putInt("DataVersion", 500);
        }
        final StructureTemplate cms3 = new StructureTemplate();
        cms3.load(NbtUtils.update(this.fixerUpper, DataFixTypes.STRUCTURE, jt, jt.getInt("DataVersion")));
        return cms3;
    }
    
    public boolean save(final ResourceLocation sm) {
        final StructureTemplate cms3 = this.structureRepository.get(sm);
        if (cms3 == null) {
            return false;
        }
        final Path path4 = this.createAndValidatePathToStructure(sm, ".nbt");
        final Path path5 = path4.getParent();
        if (path5 == null) {
            return false;
        }
        try {
            Files.createDirectories(Files.exists(path5) ? path5.toRealPath() : path5, new FileAttribute[0]);
        }
        catch (IOException iOException6) {
            StructureManager.LOGGER.error("Failed to create parent directory: {}", path5);
            return false;
        }
        final CompoundTag jt6 = cms3.save(new CompoundTag());
        try (final OutputStream outputStream7 = new FileOutputStream(path4.toFile())) {
            NbtIo.writeCompressed(jt6, outputStream7);
        }
        catch (Throwable throwable7) {
            return false;
        }
        return true;
    }
    
    public Path createPathToStructure(final ResourceLocation sm, final String string) {
        try {
            final Path path4 = this.generatedDir.resolve(sm.getNamespace());
            final Path path5 = path4.resolve("structures");
            return FileUtil.createPathToResource(path5, sm.getPath(), string);
        }
        catch (InvalidPathException invalidPathException4) {
            throw new ResourceLocationException("Invalid resource path: " + sm, invalidPathException4);
        }
    }
    
    private Path createAndValidatePathToStructure(final ResourceLocation sm, final String string) {
        if (sm.getPath().contains("//")) {
            throw new ResourceLocationException("Invalid resource path: " + sm);
        }
        final Path path4 = this.createPathToStructure(sm, string);
        if (!path4.startsWith(this.generatedDir) || !FileUtil.isPathNormalized(path4) || !FileUtil.isPathPortable(path4)) {
            throw new ResourceLocationException("Invalid resource path: " + path4);
        }
        return path4;
    }
    
    public void remove(final ResourceLocation sm) {
        this.structureRepository.remove(sm);
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
