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 javax.annotation.Nullable;
import net.minecraft.world.level.storage.LevelResource;
import com.google.common.collect.Maps;
import net.minecraft.world.level.storage.LevelStorageSource;
import java.nio.file.Path;
import net.minecraft.server.packs.resources.ResourceManager;
import com.mojang.datafixers.DataFixer;
import net.minecraft.resources.ResourceLocation;
import java.util.Map;
import org.apache.logging.log4j.Logger;

public class StructureManager {
    private static final Logger LOGGER;
    private final Map<ResourceLocation, StructureTemplate> structureRepository;
    private final DataFixer fixerUpper;
    private ResourceManager resourceManager;
    private final Path generatedDir;
    
    public StructureManager(final ResourceManager abc, final LevelStorageSource.LevelStorageAccess a, final DataFixer dataFixer) {
        this.structureRepository = Maps.newHashMap();
        this.resourceManager = abc;
        this.fixerUpper = dataFixer;
        this.generatedDir = a.getLevelPath(LevelResource.GENERATED_DIR).normalize();
    }
    
    public StructureTemplate getOrCreate(final ResourceLocation uh) {
        StructureTemplate cve3 = this.get(uh);
        if (cve3 == null) {
            cve3 = new StructureTemplate();
            this.structureRepository.put(uh, cve3);
        }
        return cve3;
    }
    
    @Nullable
    public StructureTemplate get(final ResourceLocation uh) {
        final StructureTemplate cve3;
        return this.structureRepository.computeIfAbsent(uh, uh -> {
            cve3 = this.loadFromGenerated(uh);
            return (cve3 != null) ? cve3 : this.loadFromResource(uh);
        });
    }
    
    public void onResourceManagerReload(final ResourceManager abc) {
        this.resourceManager = abc;
        this.structureRepository.clear();
    }
    
    @Nullable
    private StructureTemplate loadFromResource(final ResourceLocation uh) {
        final ResourceLocation uh2 = new ResourceLocation(uh.getNamespace(), "structures/" + uh.getPath() + ".nbt");
        try (final Resource abb4 = this.resourceManager.getResource(uh2)) {
            return this.readStructure(abb4.getInputStream());
        }
        catch (FileNotFoundException fileNotFoundException4) {
            return null;
        }
        catch (Throwable throwable4) {
            StructureManager.LOGGER.error("Couldn't load structure {}: {}", uh, throwable4.toString());
            return null;
        }
    }
    
    @Nullable
    private StructureTemplate loadFromGenerated(final ResourceLocation uh) {
        if (!this.generatedDir.toFile().isDirectory()) {
            return null;
        }
        final Path path3 = this.createAndValidatePathToStructure(uh, ".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 le3 = NbtIo.readCompressed(inputStream);
        return this.readStructure(le3);
    }
    
    public StructureTemplate readStructure(final CompoundTag le) {
        if (!le.contains("DataVersion", 99)) {
            le.putInt("DataVersion", 500);
        }
        final StructureTemplate cve3 = new StructureTemplate();
        cve3.load(NbtUtils.update(this.fixerUpper, DataFixTypes.STRUCTURE, le, le.getInt("DataVersion")));
        return cve3;
    }
    
    public boolean save(final ResourceLocation uh) {
        final StructureTemplate cve3 = this.structureRepository.get(uh);
        if (cve3 == null) {
            return false;
        }
        final Path path4 = this.createAndValidatePathToStructure(uh, ".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 le6 = cve3.save(new CompoundTag());
        try (final OutputStream outputStream7 = new FileOutputStream(path4.toFile())) {
            NbtIo.writeCompressed(le6, outputStream7);
        }
        catch (Throwable throwable7) {
            return false;
        }
        return true;
    }
    
    public Path createPathToStructure(final ResourceLocation uh, final String string) {
        try {
            final Path path4 = this.generatedDir.resolve(uh.getNamespace());
            final Path path5 = path4.resolve("structures");
            return FileUtil.createPathToResource(path5, uh.getPath(), string);
        }
        catch (InvalidPathException invalidPathException4) {
            throw new ResourceLocationException("Invalid resource path: " + uh, invalidPathException4);
        }
    }
    
    private Path createAndValidatePathToStructure(final ResourceLocation uh, final String string) {
        if (uh.getPath().contains("//")) {
            throw new ResourceLocationException("Invalid resource path: " + uh);
        }
        final Path path4 = this.createPathToStructure(uh, 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 uh) {
        this.structureRepository.remove(uh);
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
