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

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

import com.hypixel.hytale.event.IEventDispatcher;
import java.util.stream.Stream;
import com.hypixel.hytale.common.util.FormatUtil;
import java.util.Collection;
import com.hypixel.hytale.assetstore.event.AssetStoreMonitorEvent;
import com.hypixel.hytale.common.util.PathUtil;
import java.io.IOException;
import com.hypixel.hytale.server.core.util.io.FileUtil;
import java.util.logging.Level;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import com.hypixel.hytale.server.core.asset.monitor.EventKind;
import com.hypixel.hytale.assetstore.codec.AssetCodec;
import java.util.function.Predicate;
import java.util.List;
import com.hypixel.hytale.assetstore.JsonAsset;
import java.util.Objects;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import joptsimple.OptionSpec;
import com.hypixel.hytale.server.core.Options;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.event.IEventBus;
import java.util.Iterator;
import com.hypixel.hytale.server.core.util.NotificationUtil;
import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.assetstore.AssetLoadResult;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.assetstore.AssetUpdateQuery;
import java.util.Map;
import java.util.Set;
import com.hypixel.hytale.server.core.asset.monitor.AssetMonitor;
import com.hypixel.hytale.server.core.asset.monitor.AssetMonitorHandler;
import java.nio.file.Path;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.event.EventBus;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.ref.SoftReference;
import com.hypixel.hytale.protocol.ItemWithAllMetadata;
import java.util.function.Function;
import com.hypixel.hytale.server.core.asset.packet.AssetPacketGenerator;
import com.hypixel.hytale.protocol.Packet;
import java.util.function.Consumer;
import it.unimi.dsi.fastutil.objects.ObjectList;
import com.hypixel.hytale.assetstore.AssetStore;
import com.hypixel.hytale.assetstore.AssetMap;
import com.hypixel.hytale.assetstore.map.JsonAssetWithMap;

public class HytaleAssetStore<K, T extends JsonAssetWithMap<K, M>, M extends AssetMap<K, T>> extends AssetStore<K, T, M>
{
    public static final ObjectList<Consumer<Packet>> SETUP_PACKET_CONSUMERS;
    protected final AssetPacketGenerator<K, T, M> packetGenerator;
    protected final Function<K, ItemWithAllMetadata> notificationItemFunction;
    @Nullable
    protected SoftReference<Packet[]> cachedInitPackets;
    
    public HytaleAssetStore(@Nonnull final Builder<K, T, M> builder) {
        super(builder);
        this.packetGenerator = builder.packetGenerator;
        this.notificationItemFunction = builder.notificationItemFunction;
    }
    
    public AssetPacketGenerator<K, T, M> getPacketGenerator() {
        return this.packetGenerator;
    }
    
    public Function<K, ItemWithAllMetadata> getNotificationItemFunction() {
        return this.notificationItemFunction;
    }
    
    @Nonnull
    @Override
    protected EventBus getEventBus() {
        return HytaleServer.get().getEventBus();
    }
    
    @Override
    public void addFileMonitor(@Nonnull final String packKey, @Nonnull final Path assetsPath) {
        final AssetMonitor assetMonitor = AssetModule.get().getAssetMonitor();
        if (assetMonitor == null) {
            return;
        }
        assetMonitor.monitorDirectoryFiles(assetsPath, new AssetStoreMonitorHandler(packKey));
    }
    
    @Override
    public void removeFileMonitor(@Nonnull final Path path) {
        final AssetMonitor assetMonitor = AssetModule.get().getAssetMonitor();
        if (assetMonitor != null) {
            assetMonitor.removeMonitorDirectoryFiles(path, this);
        }
    }
    
    @Override
    protected void handleRemoveOrUpdate(@Nullable final Set<K> toBeRemoved, @Nullable final Map<K, T> toBeUpdated, @Nonnull final AssetUpdateQuery query) {
        if (this.packetGenerator == null) {
            return;
        }
        this.cachedInitPackets = null;
        final Universe universe = Universe.get();
        if (universe.getPlayerCount() == 0 && HytaleAssetStore.SETUP_PACKET_CONSUMERS.isEmpty()) {
            return;
        }
        if (toBeRemoved != null && !toBeRemoved.isEmpty()) {
            final Packet packet = this.packetGenerator.generateRemovePacket(this.assetMap, toBeRemoved, query);
            universe.broadcastPacketNoCache(packet);
            for (int i = 0; i < HytaleAssetStore.SETUP_PACKET_CONSUMERS.size(); ++i) {
                final Consumer<Packet> c = HytaleAssetStore.SETUP_PACKET_CONSUMERS.get(i);
                c.accept(packet);
            }
        }
        if (toBeUpdated != null && !toBeUpdated.isEmpty()) {
            final Packet packet = this.packetGenerator.generateUpdatePacket(this.assetMap, toBeUpdated, query);
            universe.broadcastPacketNoCache(packet);
            for (int i = 0; i < HytaleAssetStore.SETUP_PACKET_CONSUMERS.size(); ++i) {
                final Consumer<Packet> c = HytaleAssetStore.SETUP_PACKET_CONSUMERS.get(i);
                c.accept(packet);
            }
        }
    }
    
    public void sendAssets(@Nonnull final Consumer<Packet[]> packetConsumer) {
        if (this.packetGenerator == null) {
            return;
        }
        Packet[] packets = (Packet[])((this.cachedInitPackets == null) ? null : ((Packet[])this.cachedInitPackets.get()));
        if (packets != null) {
            packetConsumer.accept(packets);
            return;
        }
        final Map<K, T> map = this.assetMap.getAssetMap();
        final Packet packet = this.packetGenerator.generateInitPacket(this.assetMap, map);
        this.cachedInitPackets = new SoftReference<Packet[]>(packets = new Packet[] { packet });
        packetConsumer.accept(packets);
    }
    
    protected void sendReloadedNotification(@Nonnull final AssetLoadResult<K, T> result) {
        this.sendNotificationKeys(Message.translation("server.general.assetstore.reloadAssets").param("class", this.tClass.getSimpleName()).color("#A7AfA7"), "Icons/AssetNotifications/AssetReloaded.png", result.getLoadedAssets().keySet());
        this.sendNotificationKeys(Message.translation("server.general.assetstore.loadFailed").param("class", this.tClass.getSimpleName()), null, result.getFailedToLoadKeys());
        this.sendNotificationPaths(Message.translation("server.general.assetstore.loadFailed").param("class", this.tClass.getSimpleName()), result.getFailedToLoadPaths());
    }
    
    protected void sendRemovedNotification(@Nonnull final Set<K> removedKeys) {
        this.sendNotificationKeys(Message.translation("server.general.assetstore.removedAssets").param("class", this.tClass.getSimpleName()).color("#FF3874"), "Icons/AssetNotifications/Trash.png", removedKeys);
    }
    
    protected void sendNotificationKeys(final Message primaryMessage, @Nullable final String icon, @Nonnull final Set<K> keys) {
        if (keys.isEmpty()) {
            return;
        }
        if (this.notificationItemFunction != null && keys.size() < 5) {
            for (final K key : keys) {
                final ItemWithAllMetadata item = this.notificationItemFunction.apply(key);
                if (item != null) {
                    NotificationUtil.sendNotificationToUniverse(primaryMessage, Message.raw(key.toString()), item, NotificationStyle.Default);
                }
                else {
                    NotificationUtil.sendNotificationToUniverse(primaryMessage, Message.raw(key.toString()), icon, null, NotificationStyle.Default);
                }
            }
            return;
        }
        final Message secondaryMessage = Message.translation("server.general.assetstore.removedAssetsSecondaryGeneric").param("count", keys.size());
        NotificationUtil.sendNotificationToUniverse(primaryMessage, secondaryMessage, icon, NotificationStyle.Default);
    }
    
    protected void sendNotificationPaths(final Message primaryMessage, @Nonnull final Set<Path> paths) {
        if (paths.isEmpty()) {
            return;
        }
        NotificationUtil.sendNotificationToUniverse(primaryMessage, Message.raw(paths.toString()), NotificationStyle.Warning);
    }
    
    @Nonnull
    public static <T extends JsonAssetWithMap<String, M>, M extends AssetMap<String, T>> Builder<String, T, M> builder(final Class<T> tClass, final M assetMap) {
        return new Builder<String, T, M>(String.class, tClass, assetMap);
    }
    
    @Nonnull
    public static <K, T extends JsonAssetWithMap<K, M>, M extends AssetMap<K, T>> Builder<K, T, M> builder(final Class<K> kClass, final Class<T> tClass, final M assetMap) {
        return new Builder<K, T, M>(kClass, tClass, assetMap);
    }
    
    static {
        AssetStore.DISABLE_ASSET_COMPARE = Options.getOptionSet().has(Options.DISABLE_ASSET_COMPARE);
        SETUP_PACKET_CONSUMERS = new ObjectArrayList<Consumer<Packet>>();
    }
    
    public static class Builder<K, T extends JsonAssetWithMap<K, M>, M extends AssetMap<K, T>> extends AssetStore.Builder<K, T, M, Builder<K, T, M>>
    {
        protected AssetPacketGenerator<K, T, M> packetGenerator;
        protected Function<K, ItemWithAllMetadata> notificationItemFunction;
        
        public Builder(final Class<K> kClass, final Class<T> tClass, final M assetMap) {
            super(kClass, tClass, assetMap);
        }
        
        @Nonnull
        public Builder<K, T, M> setPacketGenerator(final AssetPacketGenerator<K, T, M> packetGenerator) {
            this.packetGenerator = Objects.requireNonNull(packetGenerator, "packetGenerator can't be null!");
            return this;
        }
        
        @Nonnull
        public Builder<K, T, M> setNotificationItemFunction(final Function<K, ItemWithAllMetadata> notificationItemFunction) {
            this.notificationItemFunction = Objects.requireNonNull(notificationItemFunction, "notificationItemFunction can't be null!");
            return this;
        }
        
        @Nonnull
        @Override
        public HytaleAssetStore<K, T, M> build() {
            return new HytaleAssetStore<K, T, M>(this);
        }
    }
    
    private class AssetStoreMonitorHandler implements AssetMonitorHandler
    {
        private final String packKey;
        
        public AssetStoreMonitorHandler(final String packKey) {
            this.packKey = packKey;
        }
        
        @Override
        public Object getKey() {
            return HytaleAssetStore.this;
        }
        
        @Override
        public boolean test(final Path path, final EventKind eventKind) {
            return !Files.isRegularFile(path, new LinkOption[0]) || path.getFileName().toString().endsWith(HytaleAssetStore.this.extension);
        }
        
        @Override
        public void accept(final Map<Path, EventKind> map) {
            final ObjectArrayList<Path> createdOrModifiedFilesToLoad = new ObjectArrayList<Path>();
            final ObjectArrayList<Path> removedFilesToUnload = new ObjectArrayList<Path>();
            final ObjectArrayList<Path> createdOrModifiedDirectories = new ObjectArrayList<Path>();
            final ObjectArrayList<Path> removedFilesAndDirectories = new ObjectArrayList<Path>();
            for (final Map.Entry<Path, EventKind> entry : map.entrySet()) {
                final Path path = entry.getKey();
                switch (entry.getValue()) {
                    case ENTRY_CREATE: {
                        if (Files.isDirectory(path, new LinkOption[0])) {
                            HytaleAssetStore.this.logger.at(Level.FINEST).log("Directory Created: %s", path);
                            try (final Stream<Path> stream = Files.walk(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) {
                                stream.forEach(child -> {
                                    if (Files.isDirectory(child, new LinkOption[0])) {
                                        createdOrModifiedDirectories.add(path);
                                    }
                                    if (Files.isRegularFile(child, new LinkOption[0]) && child.getFileName().toString().endsWith(HytaleAssetStore.this.extension)) {
                                        createdOrModifiedFilesToLoad.add(child);
                                    }
                                    return;
                                });
                                if (stream == null) {
                                    continue;
                                }
                            }
                            catch (final IOException e) {
                                HytaleAssetStore.this.logger.at(Level.SEVERE).withCause(e).log("Failed to reload assets in directory: %s", path);
                            }
                            continue;
                        }
                        HytaleAssetStore.this.logger.at(Level.FINEST).log("File Created: %s", path);
                        createdOrModifiedFilesToLoad.add(path);
                        createdOrModifiedFilesToLoad.add(path);
                        continue;
                    }
                    case ENTRY_DELETE: {
                        removedFilesAndDirectories.add(path);
                        for (final Path value : HytaleAssetStore.this.getAssetMap().getPathMap(this.packKey).values()) {
                            if (PathUtil.isChildOf(path, value)) {
                                HytaleAssetStore.this.logger.at(Level.FINEST).log("Deleted: %s", value);
                                removedFilesToUnload.add(value);
                            }
                        }
                        continue;
                    }
                    case ENTRY_MODIFY: {
                        if (Files.isDirectory(path, new LinkOption[0])) {
                            HytaleAssetStore.this.logger.at(Level.FINEST).log("Directory Modified: %s", path);
                            createdOrModifiedDirectories.add(path);
                            continue;
                        }
                        HytaleAssetStore.this.logger.at(Level.FINEST).log("File Modified: %s", path);
                        createdOrModifiedFilesToLoad.add(path);
                        continue;
                    }
                    default: {
                        throw new IllegalArgumentException("Unknown eventKind " + String.valueOf(entry.getValue()));
                    }
                }
            }
            if (!removedFilesAndDirectories.isEmpty() || !createdOrModifiedFilesToLoad.isEmpty() || !createdOrModifiedDirectories.isEmpty()) {
                final IEventDispatcher<AssetStoreMonitorEvent, AssetStoreMonitorEvent> dispatchFor = HytaleAssetStore.this.getEventBus().dispatchFor((Class<? super AssetStoreMonitorEvent>)AssetStoreMonitorEvent.class);
                if (dispatchFor.hasListener()) {
                    dispatchFor.dispatch(new AssetStoreMonitorEvent(this.packKey, HytaleAssetStore.this, createdOrModifiedFilesToLoad, removedFilesToUnload, createdOrModifiedDirectories, removedFilesAndDirectories));
                }
            }
            if (!createdOrModifiedFilesToLoad.isEmpty()) {
                HytaleAssetStore.this.logger.at(Level.INFO).log("Reloading assets: %s", createdOrModifiedFilesToLoad);
                final long start = System.nanoTime();
                final AssetLoadResult<K, T> result = HytaleAssetStore.this.loadAssetsFromPaths(this.packKey, createdOrModifiedFilesToLoad, AssetUpdateQuery.DEFAULT, true);
                HytaleAssetStore.this.logger.at(Level.INFO).log("Took %s to reload assets: %s", FormatUtil.nanosToString(System.nanoTime() - start), createdOrModifiedFilesToLoad);
                HytaleAssetStore.this.sendReloadedNotification(result);
            }
            if (!removedFilesToUnload.isEmpty()) {
                HytaleAssetStore.this.logger.at(Level.INFO).log("Removing deleted assets: %s", removedFilesToUnload);
                final Set<K> removedKeys = HytaleAssetStore.this.removeAssetWithPaths(this.packKey, removedFilesToUnload);
                HytaleAssetStore.this.sendRemovedNotification(removedKeys);
            }
        }
    }
}
