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

package com.hypixel.hytale.server.core.asset;

import com.hypixel.hytale.server.core.universe.world.worldmap.IWorldMap;
import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen;
import com.hypixel.hytale.common.util.FormatUtil;
import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenLoadException;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.worldmap.provider.IWorldMapProvider;
import com.hypixel.hytale.server.core.universe.world.worldgen.ValidatableWorldGen;
import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider;
import com.hypixel.hytale.assetstore.AssetLoadResult;
import com.hypixel.hytale.assetstore.AssetMap;
import com.hypixel.hytale.assetstore.map.JsonAssetWithMap;
import com.hypixel.hytale.event.IEvent;
import com.hypixel.hytale.server.core.HytaleServerConfig;
import com.hypixel.hytale.common.plugin.PluginIdentifier;
import java.nio.file.DirectoryStream;
import java.io.BufferedReader;
import java.nio.file.FileSystem;
import java.io.FileReader;
import com.hypixel.hytale.codec.ExtraInfo;
import java.io.Reader;
import com.hypixel.hytale.codec.util.RawJsonReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.FileSystems;
import java.util.Iterator;
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
import com.hypixel.hytale.server.core.asset.type.item.DroplistCommand;
import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.WorldSpawnPoint;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.HomeOrSpawnPoint;
import com.hypixel.hytale.server.core.asset.type.gameplay.respawn.RespawnController;
import java.util.Arrays;
import java.util.Comparator;
import com.hypixel.hytale.server.core.event.events.BootEvent;
import com.hypixel.hytale.assetstore.event.RemoveAssetStoreEvent;
import com.hypixel.hytale.assetstore.event.RegisterAssetStoreEvent;
import java.util.function.Consumer;
import com.hypixel.hytale.event.IBaseEvent;
import com.hypixel.hytale.sneakythrow.consumer.ThrowableConsumer;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import com.hypixel.hytale.event.EventPriority;
import com.hypixel.hytale.assetstore.AssetRegistry;
import com.hypixel.hytale.server.core.ShutdownReason;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.plugin.PluginManager;
import java.nio.file.Path;
import java.io.IOException;
import com.hypixel.hytale.logger.HytaleLogger;
import java.util.logging.Level;
import joptsimple.OptionSpec;
import com.hypixel.hytale.server.core.Options;
import java.util.concurrent.CopyOnWriteArrayList;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.assetstore.AssetStore;
import javax.annotation.Nonnull;
import com.hypixel.hytale.assetstore.AssetPack;
import java.util.List;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.asset.monitor.AssetMonitor;
import com.hypixel.hytale.common.plugin.PluginManifest;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;

public class AssetModule extends JavaPlugin
{
    public static final PluginManifest MANIFEST;
    private static AssetModule instance;
    @Nullable
    private AssetMonitor assetMonitor;
    @Nonnull
    private final List<AssetPack> assetPacks;
    private boolean hasLoaded;
    private final List<AssetStore<?, ?, ?>> pendingAssetStores;
    
    public static AssetModule get() {
        return AssetModule.instance;
    }
    
    public AssetModule(@Nonnull final JavaPluginInit init) {
        super(init);
        this.assetPacks = new CopyOnWriteArrayList<AssetPack>();
        this.hasLoaded = false;
        this.pendingAssetStores = new CopyOnWriteArrayList<AssetStore<?, ?, ?>>();
        AssetModule.instance = this;
    }
    
    @Override
    protected void setup() {
        if (Options.getOptionSet().has(Options.DISABLE_FILE_WATCHER)) {
            this.getLogger().at(Level.WARNING).log("Not running asset watcher because --disable-file-watcher was set");
        }
        else {
            try {
                this.assetMonitor = new AssetMonitor();
                this.getLogger().at(Level.INFO).log("Asset monitor enabled!");
            }
            catch (final IOException e) {
                this.getLogger().at(Level.SEVERE).withCause(e).log("Failed to create asset monitor!");
            }
        }
        final List<Path> paths = Options.getOptionSet().valuesOf(Options.ASSET_DIRECTORY);
        for (final Path path : paths) {
            this.loadAndRegisterPack(path);
        }
        this.loadPacksFromDirectory(PluginManager.MODS_PATH);
        for (final Path modsPath : Options.getOptionSet().valuesOf(Options.MODS_DIRECTORIES)) {
            this.loadPacksFromDirectory(modsPath);
        }
        if (this.assetPacks.isEmpty()) {
            HytaleServer.get().shutdownServer(ShutdownReason.MISSING_ASSETS.withMessage("Failed to load any asset packs"));
            return;
        }
        this.getEventRegistry().register((short)(-16), LoadAssetEvent.class, event -> {
            if (this.hasLoaded) {
                throw new IllegalStateException("LoadAssetEvent has already been dispatched");
            }
            else {
                AssetRegistry.ASSET_LOCK.writeLock().lock();
                try {
                    this.hasLoaded = true;
                    AssetRegistryLoader.preLoadAssets(event);
                    for (final AssetPack pack : this.assetPacks) {
                        AssetRegistryLoader.loadAssets(event, pack);
                    }
                }
                finally {
                    AssetRegistry.ASSET_LOCK.writeLock().unlock();
                }
                return;
            }
        });
        this.getEventRegistry().register((short)(-16), AssetPackRegisterEvent.class, event -> AssetRegistryLoader.loadAssets(null, event.getAssetPack()));
        this.getEventRegistry().register(AssetPackUnregisterEvent.class, event -> {
            for (final AssetStore<?, ?, ?> assetStore : AssetRegistry.getStoreMap().values()) {
                assetStore.removeAssetPack(event.getAssetPack().getName());
            }
            return;
        });
        this.getEventRegistry().register(LoadAssetEvent.class, AssetModule::validateWorldGen);
        this.getEventRegistry().register(EventPriority.FIRST, (Class<? super IBaseEvent>)LoadAssetEvent.class, (Consumer<IBaseEvent>)SneakyThrow.sneakyConsumer((ThrowableConsumer<EventType, Throwable>)AssetRegistryLoader::writeSchemas));
        this.getEventRegistry().register(RegisterAssetStoreEvent.class, this::onNewStore);
        this.getEventRegistry().register(RemoveAssetStoreEvent.class, this::onRemoveStore);
        this.getEventRegistry().registerGlobal(BootEvent.class, event -> {
            final StringBuilder sb = new StringBuilder("Total Loaded Assets: ");
            final AssetStore[] assetStores = AssetRegistry.getStoreMap().values().toArray(AssetStore[]::new);
            Arrays.sort(assetStores, Comparator.comparingInt(o -> o.getAssetMap().getAssetCount()));
            for (int i = assetStores.length - 1; i >= 0; --i) {
                final AssetStore assetStore2 = assetStores[i];
                final String simpleName = assetStore2.getAssetClass().getSimpleName();
                final int assetCount = assetStore2.getAssetMap().getAssetCount();
                sb.append(simpleName).append(": ").append(assetCount).append(", ");
            }
            sb.setLength();
            this.getLogger().at(Level.INFO).log(sb.toString());
            return;
        });
        RespawnController.CODEC.register("HomeOrSpawnPoint", HomeOrSpawnPoint.class, HomeOrSpawnPoint.CODEC);
        RespawnController.CODEC.register("WorldSpawnPoint", WorldSpawnPoint.class, WorldSpawnPoint.CODEC);
        this.getCommandRegistry().registerCommand(new DroplistCommand());
    }
    
    @Override
    protected void shutdown() {
        if (this.assetMonitor != null) {
            this.assetMonitor.shutdown();
            this.assetMonitor = null;
        }
        for (final AssetPack pack : this.assetPacks) {
            if (pack.getFileSystem() != null) {
                try {
                    pack.getFileSystem().close();
                }
                catch (final IOException e) {
                    this.getLogger().at(Level.WARNING).withCause(e).log("Failed to close asset pack filesystem: %s", pack.getName());
                }
            }
        }
        this.assetPacks.clear();
    }
    
    @Nonnull
    public AssetPack getBaseAssetPack() {
        return this.assetPacks.getFirst();
    }
    
    @Nonnull
    public List<AssetPack> getAssetPacks() {
        return this.assetPacks;
    }
    
    @Nullable
    public AssetMonitor getAssetMonitor() {
        return this.assetMonitor;
    }
    
    @Nullable
    public AssetPack findAssetPackForPath(Path path) {
        path = path.toAbsolutePath().normalize();
        for (final AssetPack pack : this.assetPacks) {
            if (path.getFileSystem() != pack.getRoot().getFileSystem()) {
                continue;
            }
            if (path.startsWith(pack.getRoot())) {
                return pack;
            }
        }
        return null;
    }
    
    public boolean isAssetPathImmutable(@Nonnull final Path path) {
        final AssetPack pack = this.findAssetPackForPath(path);
        return pack != null && pack.isImmutable();
    }
    
    @Nullable
    private PluginManifest loadPackManifest(final Path packPath) throws IOException {
        if (packPath.getFileName().toString().toLowerCase().endsWith(".zip")) {
            try (final FileSystem fs = FileSystems.newFileSystem(packPath, (ClassLoader)null)) {
                final Path manifestPath = fs.getPath("manifest.json", new String[0]);
                if (Files.exists(manifestPath, new LinkOption[0])) {
                    try (final BufferedReader reader = Files.newBufferedReader(manifestPath, StandardCharsets.UTF_8)) {
                        final char[] buffer = RawJsonReader.READ_BUFFER.get();
                        final RawJsonReader rawJsonReader = new RawJsonReader(reader, buffer);
                        final ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get();
                        final PluginManifest manifest = PluginManifest.CODEC.decodeJson(rawJsonReader, extraInfo);
                        extraInfo.getValidationResults().logOrThrowValidatorExceptions(this.getLogger());
                        return manifest;
                    }
                    if (fs != null) {
                        fs.close();
                    }
                }
            }
        }
        else if (Files.isDirectory(packPath, new LinkOption[0])) {
            final Path manifestPath2 = packPath.resolve("manifest.json");
            if (Files.exists(manifestPath2, new LinkOption[0])) {
                try (final FileReader reader2 = new FileReader(manifestPath2.toFile(), StandardCharsets.UTF_8)) {
                    final char[] buffer2 = RawJsonReader.READ_BUFFER.get();
                    final RawJsonReader rawJsonReader2 = new RawJsonReader(reader2, buffer2);
                    final ExtraInfo extraInfo2 = ExtraInfo.THREAD_LOCAL.get();
                    final PluginManifest manifest2 = PluginManifest.CODEC.decodeJson(rawJsonReader2, extraInfo2);
                    extraInfo2.getValidationResults().logOrThrowValidatorExceptions(this.getLogger());
                    return manifest2;
                }
            }
        }
        return null;
    }
    
    private void loadPacksFromDirectory(final Path modsPath) {
        if (!Files.isDirectory(modsPath, new LinkOption[0])) {
            return;
        }
        this.getLogger().at(Level.INFO).log("Loading packs from directory: %s", modsPath);
        try (final DirectoryStream<Path> stream = Files.newDirectoryStream(modsPath)) {
            for (final Path packPath : stream) {
                if (packPath.getFileName() != null) {
                    if (packPath.getFileName().toString().toLowerCase().endsWith(".jar")) {
                        continue;
                    }
                    this.loadAndRegisterPack(packPath);
                }
            }
        }
        catch (final IOException e) {
            this.getLogger().at(Level.SEVERE).withCause(e).log("Failed to load mods from: %s", modsPath);
        }
    }
    
    private void loadAndRegisterPack(final Path packPath) {
        PluginManifest manifest;
        try {
            manifest = this.loadPackManifest(packPath);
            if (manifest == null) {
                this.getLogger().at(Level.WARNING).log("Skipping pack at %s: missing or invalid manifest.json", packPath.getFileName());
                return;
            }
        }
        catch (final Exception e) {
            this.getLogger().at(Level.WARNING).withCause(e).log("Failed to load manifest for pack at %s", packPath);
            return;
        }
        final PluginIdentifier packIdentifier = new PluginIdentifier(manifest);
        final HytaleServerConfig.ModConfig modConfig = HytaleServer.get().getConfig().getModConfig().get(packIdentifier);
        final boolean enabled = modConfig == null || modConfig.getEnabled() == null || modConfig.getEnabled();
        final String packId = packIdentifier.toString();
        if (enabled) {
            this.registerPack(packId, packPath, manifest);
            this.getLogger().at(Level.INFO).log("Loaded pack: %s from %s", packId, packPath.getFileName());
        }
        else {
            this.getLogger().at(Level.INFO).log("Skipped disabled pack: %s", packId);
        }
    }
    
    public void registerPack(@Nonnull final String name, @Nonnull final Path path, @Nonnull final PluginManifest manifest) {
        final Path packLocation;
        Path absolutePath = packLocation = path.toAbsolutePath().normalize();
        FileSystem fileSystem = null;
        boolean isImmutable = false;
        final String lowerFileName = absolutePath.getFileName().toString().toLowerCase();
        Label_0130: {
            Label_0111: {
                if (!lowerFileName.endsWith(".zip")) {
                    if (!lowerFileName.endsWith(".jar")) {
                        break Label_0111;
                    }
                }
                try {
                    fileSystem = FileSystems.newFileSystem(absolutePath, (ClassLoader)null);
                    absolutePath = fileSystem.getPath("", new String[0]).toAbsolutePath().normalize();
                    isImmutable = true;
                    break Label_0130;
                }
                catch (final IOException e) {
                    throw SneakyThrow.sneakyThrow(e);
                }
            }
            isImmutable = Files.isRegularFile(absolutePath.resolve("CommonAssetsIndex.hashes"), new LinkOption[0]);
        }
        final AssetPack pack = new AssetPack(packLocation, name, absolutePath, fileSystem, isImmutable, manifest);
        this.assetPacks.add(pack);
        AssetRegistry.ASSET_LOCK.writeLock().lock();
        try {
            if (!this.hasLoaded) {
                return;
            }
            HytaleServer.get().getEventBus().dispatchFor((Class<? super IEvent<Void>>)AssetPackRegisterEvent.class).dispatch(new AssetPackRegisterEvent(pack));
        }
        finally {
            AssetRegistry.ASSET_LOCK.writeLock().unlock();
        }
    }
    
    public void unregisterPack(@Nonnull final String name) {
        final AssetPack pack = this.getAssetPack(name);
        if (pack == null) {
            this.getLogger().at(Level.WARNING).log("Tried to unregister non-existent asset pack: %s", name);
            return;
        }
        this.assetPacks.remove(pack);
        if (pack.getFileSystem() != null) {
            try {
                pack.getFileSystem().close();
            }
            catch (final IOException e) {
                throw SneakyThrow.sneakyThrow(e);
            }
        }
        AssetRegistry.ASSET_LOCK.writeLock().lock();
        try {
            HytaleServer.get().getEventBus().dispatchFor((Class<? super IEvent<Void>>)AssetPackUnregisterEvent.class).dispatch(new AssetPackUnregisterEvent(pack));
        }
        finally {
            AssetRegistry.ASSET_LOCK.writeLock().unlock();
        }
    }
    
    public AssetPack getAssetPack(@Nonnull final String name) {
        for (final AssetPack pack : this.assetPacks) {
            if (name.equals(pack.getName())) {
                return pack;
            }
        }
        return null;
    }
    
    private void onRemoveStore(@Nonnull final RemoveAssetStoreEvent event) {
        final AssetStore<?, ? extends JsonAssetWithMap<?, ? extends AssetMap<?, ?>>, ? extends AssetMap<?, ? extends JsonAssetWithMap<?, ?>>> assetStore = (AssetStore<?, ? extends JsonAssetWithMap<?, ? extends AssetMap<?, ?>>, ? extends AssetMap<?, ? extends JsonAssetWithMap<?, ?>>>)event.getAssetStore();
        final String path = assetStore.getPath();
        if (path == null) {
            return;
        }
        for (final AssetPack pack : this.assetPacks) {
            if (pack.isImmutable()) {
                continue;
            }
            final Path assetsPath = pack.getRoot().resolve("Server").resolve(path);
            if (!Files.isDirectory(assetsPath, new LinkOption[0])) {
                continue;
            }
            assetStore.removeFileMonitor(assetsPath);
        }
    }
    
    private void onNewStore(@Nonnull final RegisterAssetStoreEvent event) {
        if (!AssetRegistry.HAS_INIT) {
            return;
        }
        this.pendingAssetStores.add(event.getAssetStore());
    }
    
    public void initPendingStores() {
        for (int i = 0; i < this.pendingAssetStores.size(); ++i) {
            this.initStore(this.pendingAssetStores.get(i));
        }
        this.pendingAssetStores.clear();
    }
    
    private void initStore(@Nonnull final AssetStore<?, ?, ?> assetStore) {
        AssetRegistry.ASSET_LOCK.writeLock().lock();
        try {
            final List<?> preAddedAssets = assetStore.getPreAddedAssets();
            if (preAddedAssets != null && !preAddedAssets.isEmpty()) {
                final AssetLoadResult loadResult = assetStore.loadAssets("Hytale:Hytale", preAddedAssets);
                if (loadResult.hasFailed()) {
                    throw new RuntimeException("Failed to load asset store: " + String.valueOf(assetStore.getAssetClass()));
                }
            }
            for (final AssetPack pack : this.assetPacks) {
                final Path serverAssetDirectory = pack.getRoot().resolve("Server");
                final String path = assetStore.getPath();
                if (path != null) {
                    final Path assetsPath = serverAssetDirectory.resolve(path);
                    if (Files.isDirectory(assetsPath, new LinkOption[0])) {
                        final AssetLoadResult<?, ? extends JsonAssetWithMap<?, ? extends AssetMap<?, ?>>> loadResult2 = (AssetLoadResult<?, ? extends JsonAssetWithMap<?, ? extends AssetMap<?, ?>>>)assetStore.loadAssetsFromDirectory(pack.getName(), assetsPath);
                        if (loadResult2.hasFailed()) {
                            throw new RuntimeException("Failed to load asset store: " + String.valueOf(assetStore.getAssetClass()));
                        }
                    }
                    else {
                        this.getLogger().at(Level.SEVERE).log("Path for %s isn't a directory or doesn't exist: %s", assetStore.getAssetClass().getSimpleName(), assetsPath);
                    }
                }
                assetStore.validateCodecDefaults();
                if (path != null) {
                    final Path assetsPath = serverAssetDirectory.resolve(path);
                    if (!Files.isDirectory(assetsPath, new LinkOption[0])) {
                        continue;
                    }
                    assetStore.addFileMonitor(pack.getName(), assetsPath);
                }
            }
        }
        catch (final IOException e) {
            throw SneakyThrow.sneakyThrow(e);
        }
        finally {
            AssetRegistry.ASSET_LOCK.writeLock().unlock();
        }
    }
    
    private static void validateWorldGen(@Nonnull final LoadAssetEvent event) {
        if (!Options.getOptionSet().has(Options.VALIDATE_WORLD_GEN)) {
            return;
        }
        final long start = System.nanoTime();
        try {
            final IWorldGenProvider provider = IWorldGenProvider.CODEC.getDefault();
            final IWorldGen generator = provider.getGenerator();
            generator.getDefaultSpawnProvider(0);
            if (generator instanceof final ValidatableWorldGen validatableWorldGen) {
                final boolean valid = validatableWorldGen.validate();
                if (!valid) {
                    event.failed(true, "failed to validate world gen");
                }
            }
            if (generator instanceof final IWorldMapProvider worldMapProvider) {
                final IWorldMap worldMap = worldMapProvider.getGenerator(null);
                worldMap.getWorldMapSettings();
            }
        }
        catch (final WorldGenLoadException e) {
            HytaleLogger.getLogger().at(Level.SEVERE).withCause(e).log("Failed to load default world gen!");
            HytaleLogger.getLogger().at(Level.SEVERE).log("\n" + e.getTraceMessage("\n"));
            event.failed(true, "failed to validate world gen: " + e.getTraceMessage(" -> "));
        }
        catch (final Throwable e2) {
            HytaleLogger.getLogger().at(Level.SEVERE).withCause(e2).log("Failed to load default world gen!");
            event.failed(true, "failed to validate world gen");
        }
        HytaleLogger.getLogger().at(Level.INFO).log("Validate world gen phase completed! Boot time %s, Took %s", FormatUtil.nanosToString(System.nanoTime() - event.getBootStart()), FormatUtil.nanosToString(System.nanoTime() - start));
    }
    
    static {
        MANIFEST = PluginManifest.corePlugin(AssetModule.class).build();
    }
}
