// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.prefab.selection.buffer;

import java.util.concurrent.locks.StampedLock;
import java.util.concurrent.ConcurrentHashMap;
import java.nio.file.attribute.FileTime;
import com.hypixel.hytale.server.core.util.BsonUtil;
import org.bson.BsonDocument;
import java.util.concurrent.CompletionException;
import com.hypixel.hytale.logger.sentry.SkipSentryException;
import java.util.logging.Level;
import joptsimple.OptionSpec;
import com.hypixel.hytale.server.core.Options;
import java.nio.file.attribute.BasicFileAttributes;
import javax.annotation.Nullable;
import io.netty.buffer.ByteBuf;
import java.util.function.Supplier;
import io.netty.buffer.Unpooled;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.OpenOption;
import java.util.Set;
import java.nio.file.attribute.FileAttribute;
import java.util.concurrent.CompletableFuture;
import com.hypixel.hytale.assetstore.AssetPack;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import java.io.IOException;
import com.hypixel.hytale.server.core.util.io.FileUtil;
import com.hypixel.hytale.server.core.asset.AssetModule;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer;
import javax.annotation.Nonnull;
import java.lang.ref.WeakReference;
import java.util.Map;
import com.hypixel.hytale.logger.HytaleLogger;
import java.util.regex.Pattern;
import java.nio.file.Path;

public class PrefabBufferUtil
{
    public static final Path CACHE_PATH;
    public static final String LPF_FILE_SUFFIX = ".lpf";
    public static final String JSON_FILE_SUFFIX = ".prefab.json";
    public static final String JSON_LPF_FILE_SUFFIX = ".prefab.json.lpf";
    public static final String FILE_SUFFIX_REGEX = "((!\\.prefab\\.json)\\.lpf|\\.prefab\\.json)$";
    public static final Pattern FILE_SUFFIX_PATTERN;
    public static final HytaleLogger LOGGER;
    private static final Map<Path, WeakReference<CachedEntry>> CACHE;
    
    @Nonnull
    public static IPrefabBuffer getCached(@Nonnull final Path path) {
        final WeakReference<CachedEntry> reference = PrefabBufferUtil.CACHE.get(path);
        CachedEntry cachedPrefab = (reference != null) ? reference.get() : null;
        if (cachedPrefab != null) {
            final long stamp = cachedPrefab.lock.readLock();
            try {
                if (cachedPrefab.buffer != null) {
                    return cachedPrefab.buffer.newAccess();
                }
            }
            finally {
                cachedPrefab.lock.unlockRead(stamp);
            }
        }
        cachedPrefab = getOrCreateCacheEntry(path);
        final long stamp = cachedPrefab.lock.writeLock();
        try {
            if (cachedPrefab.buffer != null) {
                return cachedPrefab.buffer.newAccess();
            }
            cachedPrefab.buffer = loadBuffer(path);
            return cachedPrefab.buffer.newAccess();
        }
        finally {
            cachedPrefab.lock.unlockWrite(stamp);
        }
    }
    
    @Nonnull
    public static PrefabBuffer loadBuffer(@Nonnull final Path path) {
        final String fileNameStr = path.getFileName().toString();
        final String fileName = fileNameStr.replace(".prefab.json.lpf", "").replace(".prefab.json", "");
        final Path lpfPath = path.resolveSibling(fileName + ".lpf");
        if (Files.exists(lpfPath, new LinkOption[0])) {
            return loadFromLPF(path, lpfPath);
        }
        AssetPack pack;
        Path cachedLpfPath;
        if (AssetModule.get().isAssetPathImmutable(path)) {
            final Path lpfConvertedPath = path.resolveSibling(fileName + ".prefab.json.lpf");
            if (Files.exists(lpfConvertedPath, new LinkOption[0])) {
                return loadFromLPF(path, lpfConvertedPath);
            }
            pack = AssetModule.get().findAssetPackForPath(path);
            if (pack != null) {
                final String safePackName = FileUtil.INVALID_FILENAME_CHARACTERS.matcher(pack.getName()).replaceAll("_");
                cachedLpfPath = PrefabBufferUtil.CACHE_PATH.resolve(safePackName).resolve(pack.getRoot().relativize(lpfConvertedPath).toString());
            }
            else if (lpfConvertedPath.getRoot() != null) {
                cachedLpfPath = PrefabBufferUtil.CACHE_PATH.resolve(lpfConvertedPath.subpath(1, lpfConvertedPath.getNameCount()).toString());
            }
            else {
                cachedLpfPath = PrefabBufferUtil.CACHE_PATH.resolve(lpfConvertedPath.toString());
            }
        }
        else {
            cachedLpfPath = path.resolveSibling(fileName + ".prefab.json.lpf");
            pack = null;
        }
        final Path jsonPath = path.resolveSibling(fileName + ".prefab.json");
        if (!Files.exists(jsonPath, new LinkOption[0])) {
            try {
                Files.deleteIfExists(cachedLpfPath);
            }
            catch (final IOException ex) {}
            throw new Error("Error loading Prefab from " + String.valueOf(jsonPath.toAbsolutePath()) + " (.lpf and .prefab.json) File NOT found!");
        }
        try {
            return loadFromJson(pack, path, cachedLpfPath, jsonPath);
        }
        catch (final IOException e) {
            throw SneakyThrow.sneakyThrow(e);
        }
    }
    
    @Nonnull
    public static CompletableFuture<Void> writeToFileAsync(@Nonnull final PrefabBuffer prefab, @Nonnull final Path path) {
        return CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> {
            try (final SeekableByteChannel channel = Files.newByteChannel(path, FileUtil.DEFAULT_WRITE_OPTIONS, (FileAttribute<?>[])new FileAttribute[0])) {
                channel.write(BinaryPrefabBufferCodec.INSTANCE.serialize(prefab).nioBuffer());
            }
        }));
    }
    
    public static PrefabBuffer readFromFile(@Nonnull final Path path) {
        return readFromFileAsync(path).join();
    }
    
    @Nonnull
    public static CompletableFuture<PrefabBuffer> readFromFileAsync(@Nonnull final Path path) {
        return CompletableFuture.supplyAsync((Supplier<PrefabBuffer>)SneakyThrow.sneakySupplier(() -> {
            final SeekableByteChannel channel = Files.newByteChannel(path, new OpenOption[0]);
            try {
                final int size = (int)channel.size();
                final ByteBuf buf = Unpooled.buffer(size);
                buf.writerIndex(size);
                if (channel.read(buf.internalNioBuffer(0, size)) != size) {
                    throw new IOException("Didn't read full file!");
                }
                else {
                    BinaryPrefabBufferCodec.INSTANCE.deserialize(path, buf);
                    if (channel != null) {
                        channel.close();
                    }
                    return;
                }
            }
            catch (final Throwable t$) {
                if (channel != null) {
                    try {
                        channel.close();
                    }
                    catch (final Throwable x2) {
                        t$.addSuppressed(x2);
                    }
                }
                throw t$;
            }
        }));
    }
    
    @Nonnull
    public static PrefabBuffer loadFromLPF(@Nonnull final Path path, @Nonnull final Path realPath) {
        try {
            return readFromFile(realPath);
        }
        catch (final Exception e) {
            throw new Error("Error while loading prefab " + String.valueOf(path.toAbsolutePath()) + " from " + String.valueOf(realPath.toAbsolutePath()), (Throwable)e);
        }
    }
    
    @Nonnull
    public static PrefabBuffer loadFromJson(@Nullable final AssetPack pack, final Path path, @Nonnull final Path cachedLpfPath, @Nonnull final Path jsonPath) throws IOException {
        BasicFileAttributes cachedAttr = null;
        try {
            cachedAttr = Files.readAttributes(cachedLpfPath, BasicFileAttributes.class, new LinkOption[0]);
        }
        catch (final IOException ex) {}
        FileTime targetModifiedTime;
        if (pack == null || !pack.isImmutable()) {
            targetModifiedTime = Files.readAttributes(jsonPath, BasicFileAttributes.class, new LinkOption[0]).lastModifiedTime();
        }
        else {
            targetModifiedTime = Files.readAttributes(pack.getPackLocation(), BasicFileAttributes.class, new LinkOption[0]).lastModifiedTime();
        }
        if (cachedAttr != null && targetModifiedTime.compareTo(cachedAttr.lastModifiedTime()) <= 0) {
            try {
                return readFromFile(cachedLpfPath);
            }
            catch (final CompletionException e) {
                if (!Options.getOptionSet().has(Options.VALIDATE_PREFABS)) {
                    if (e.getCause() instanceof UpdateBinaryPrefabException) {
                        PrefabBufferUtil.LOGGER.at(Level.FINE).log("Ignoring LPF %s due to: %s", path, e.getMessage());
                    }
                    else {
                        PrefabBufferUtil.LOGGER.at(Level.WARNING).withCause(new SkipSentryException(e)).log("Failed to load %s", cachedLpfPath);
                    }
                }
            }
        }
        try {
            final PrefabBuffer buffer = BsonPrefabBufferDeserializer.INSTANCE.deserialize(jsonPath, (BsonDocument)BsonUtil.readDocument(jsonPath, false).join());
            if (!Options.getOptionSet().has(Options.DISABLE_CPB_BUILD)) {
                try {
                    Files.createDirectories(cachedLpfPath.getParent(), (FileAttribute<?>[])new FileAttribute[0]);
                    writeToFileAsync(buffer, cachedLpfPath).thenRun(() -> {
                        try {
                            Files.setLastModifiedTime(cachedLpfPath, targetModifiedTime);
                        }
                        catch (final IOException ex2) {}
                        return;
                    }).exceptionally(throwable -> {
                        HytaleLogger.getLogger().at(Level.FINE).withCause(new SkipSentryException(throwable)).log("Failed to save prefab cache %s", cachedLpfPath);
                        return null;
                    });
                }
                catch (final IOException e2) {
                    PrefabBufferUtil.LOGGER.at(Level.FINE).log("Cannot create cache directory for %s: %s", cachedLpfPath, e2.getMessage());
                }
            }
            return buffer;
        }
        catch (final Exception e3) {
            throw new Error("Error while loading Prefab from " + String.valueOf(jsonPath.toAbsolutePath()), (Throwable)e3);
        }
    }
    
    @Nonnull
    private static CachedEntry getOrCreateCacheEntry(final Path path) {
        final CachedEntry[] temp = { null };
        PrefabBufferUtil.CACHE.compute(path, (p, ref) -> {
            if (ref != null) {
                final CachedEntry cached = (CachedEntry)ref.get();
                if ((temp[0] = cached) != null) {
                    return ref;
                }
            }
            new(java.lang.ref.WeakReference.class)();
            final CachedEntry cachedEntry = new CachedEntry();
            final int n;
            new WeakReference(temp[n] = cachedEntry);
            return;
        });
        return temp[0];
    }
    
    static {
        CACHE_PATH = Options.getOrDefault(Options.PREFAB_CACHE_DIRECTORY, Options.getOptionSet(), Path.of(".cache/prefabs", new String[0]));
        FILE_SUFFIX_PATTERN = Pattern.compile("((!\\.prefab\\.json)\\.lpf|\\.prefab\\.json)$");
        LOGGER = HytaleLogger.forEnclosingClass();
        CACHE = new ConcurrentHashMap<Path, WeakReference<CachedEntry>>();
    }
    
    private static class CachedEntry
    {
        private final StampedLock lock;
        private PrefabBuffer buffer;
        
        private CachedEntry() {
            this.lock = new StampedLock();
        }
    }
}
