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

package com.hypixel.hytale.server.core.modules.i18n;

import java.util.List;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.asset.monitor.EventKind;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import com.hypixel.hytale.server.core.HytaleServerConfig;
import com.hypixel.hytale.server.core.io.PacketHandler;
import javax.annotation.Nullable;
import java.util.Collections;
import java.io.File;
import java.io.BufferedReader;
import com.hypixel.hytale.logger.sentry.SkipSentryException;
import com.hypixel.hytale.server.core.modules.i18n.parser.LangFileParser;
import java.util.stream.Stream;
import com.hypixel.hytale.server.core.util.io.FileUtil;
import com.hypixel.hytale.event.IEventDispatcher;
import com.hypixel.hytale.server.core.modules.i18n.event.MessagesUpdated;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.protocol.Packet;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.protocol.UpdateType;
import com.hypixel.hytale.protocol.packets.assets.UpdateTranslations;
import java.nio.file.DirectoryStream;
import com.hypixel.hytale.server.core.asset.monitor.AssetMonitor;
import java.nio.file.OpenOption;
import java.util.Properties;
import java.io.IOException;
import com.hypixel.hytale.logger.HytaleLogger;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.asset.monitor.AssetMonitorHandler;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import com.hypixel.hytale.server.core.modules.i18n.commands.EnableTmpTagsCommand;
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
import com.hypixel.hytale.server.core.modules.i18n.commands.InternationalizationCommands;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.Bench;
import java.util.Iterator;
import com.hypixel.hytale.server.core.asset.type.item.config.FieldcraftCategory;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.bench.CraftingBench;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent;
import com.hypixel.hytale.server.core.asset.AssetPackUnregisterEvent;
import com.hypixel.hytale.server.core.asset.AssetPackRegisterEvent;
import com.hypixel.hytale.assetstore.AssetPack;
import com.hypixel.hytale.server.core.asset.AssetModule;
import com.hypixel.hytale.server.core.asset.LoadAssetEvent;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import java.util.Map;
import java.nio.file.Path;
import com.hypixel.hytale.common.plugin.PluginManifest;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;

public class I18nModule extends JavaPlugin
{
    public static final PluginManifest MANIFEST;
    public static final String DEFAULT_LANGUAGE = "en-US";
    public static final Path FALLBACK_LANG_PATH;
    public static final String FILE_EXTENSION = ".lang";
    public static final String SERVER_ASSETS = "Server";
    public static final String LANGUAGE_ASSETS = "Languages";
    public static final Path DEFAULT_GENERATED_PATH;
    private static I18nModule instance;
    private final Map<String, String> fallbacks;
    private final Map<String, Map<String, String>> languages;
    private final Map<String, Map<String, String>> cachedLanguages;
    
    public static I18nModule get() {
        return I18nModule.instance;
    }
    
    public I18nModule(@Nonnull final JavaPluginInit parent) {
        super(parent);
        this.fallbacks = new ConcurrentHashMap<String, String>();
        this.languages = new ConcurrentHashMap<String, Map<String, String>>();
        this.cachedLanguages = new ConcurrentHashMap<String, Map<String, String>>();
        I18nModule.instance = this;
    }
    
    @Override
    protected void setup() {
        this.getEventRegistry().register(LoadAssetEvent.class, event -> {
            for (final AssetPack pack : AssetModule.get().getAssetPacks()) {
                this.loadMessagesFromPack(pack);
            }
            return;
        });
        this.getEventRegistry().register(AssetPackRegisterEvent.class, event -> this.loadMessagesFromPack(event.getAssetPack()));
        this.getEventRegistry().register(AssetPackUnregisterEvent.class, event -> {});
        this.getEventRegistry().register(LoadedAssetsEvent.class, BlockType.class, event -> {
            final Object2ObjectOpenHashMap<String, String> addedMessages = new Object2ObjectOpenHashMap<String, String>();
            event.getLoadedAssets().values().forEach(item -> {
                final Bench bench = item.getBench();
                if (bench != null) {
                    final String id = item.getId();
                    if (bench instanceof final CraftingBench craftingBench) {
                        final CraftingBench.BenchCategory[] arr$ = craftingBench.getCategories();
                        for (int len$ = arr$.length, i$2 = 0; i$2 < len$; ++i$2) {
                            final CraftingBench.BenchCategory category = arr$[i$2];
                            addedMessages.put("server.items." + id + ".bench.categories." + category.getId() + ".name", category.getName());
                            if (category.getItemCategories() != null) {
                                final CraftingBench.BenchItemCategory[] arr$2 = category.getItemCategories();
                                for (int len$2 = arr$2.length, i$3 = 0; i$3 < len$2; ++i$3) {
                                    final CraftingBench.BenchItemCategory itemCategory = arr$2[i$3];
                                    addedMessages.put("server.items." + id + ".bench.categories." + category.getId() + ".itemCategories." + itemCategory.getId() + ".name", itemCategory.getName());
                                }
                            }
                        }
                    }
                    if (bench.getDescriptiveLabel() != null) {
                        addedMessages.put("server.items." + id + ".bench.descriptiveLabel", bench.getDescriptiveLabel());
                    }
                }
                return;
            });
            this.addDefaultMessages(addedMessages, event.isInitial());
            return;
        });
        this.getEventRegistry().register(LoadedAssetsEvent.class, FieldcraftCategory.class, event -> {
            final Object2ObjectOpenHashMap<String, String> addedMessages2 = new Object2ObjectOpenHashMap<String, String>();
            event.getLoadedAssets().values().forEach(category -> {
                if (category.getName() != null) {
                    addedMessages.put("fieldcraftCategories." + category.getId() + ".name", category.getName());
                }
                return;
            });
            this.addDefaultMessages(addedMessages2, event.isInitial());
        });
    }
    
    @Override
    protected void start() {
        this.getCommandRegistry().registerCommand(new InternationalizationCommands());
        this.getCommandRegistry().registerCommand(new EnableTmpTagsCommand());
    }
    
    private void loadMessagesFromPack(final AssetPack pack) {
        final Path languagesPath = pack.getRoot().resolve("Server").resolve("Languages");
        if (Files.isDirectory(languagesPath, new LinkOption[0])) {
            final AssetMonitor assetMonitor = AssetModule.get().getAssetMonitor();
            if (assetMonitor != null && !pack.isImmutable()) {
                assetMonitor.monitorDirectoryFiles(languagesPath, new I18nAssetMonitorHandler(languagesPath));
            }
            try (final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(languagesPath, x$0 -> Files.isDirectory(x$0, new LinkOption[0]))) {
                for (final Path path : directoryStream) {
                    try {
                        final String languageKey = path.getFileName().toString();
                        final int entriesCount = this.loadMessages(languageKey, path);
                        this.getLogger().at(Level.INFO).log("Loaded %d entries for '%s' from %s", entriesCount, languageKey, languagesPath);
                    }
                    catch (final IOException e) {
                        this.getLogger().at(Level.SEVERE).withCause(e).log("Failed to load messages from: %s", path);
                    }
                }
            }
            catch (final IOException e2) {
                this.getLogger().at(Level.SEVERE).withCause(e2).log("Failed to load languages from: %s", languagesPath);
            }
        }
        final Path fallbackPath = languagesPath.resolve("fallback.lang");
        if (Files.exists(fallbackPath, new LinkOption[0])) {
            final Properties properties = new Properties();
            try {
                properties.load(Files.newInputStream(fallbackPath, new OpenOption[0]));
                for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
                    this.fallbacks.put(entry.getKey(), entry.getValue());
                }
                if (!properties.isEmpty()) {
                    this.getLogger().at(Level.INFO).log("Loaded %d entries from %s", properties.size(), fallbackPath);
                }
            }
            catch (final IOException e3) {
                this.getLogger().at(Level.SEVERE).withCause(e3).log("Failed to load fallback languages from: %s", fallbackPath);
            }
        }
    }
    
    @Nonnull
    public UpdateTranslations[] getUpdatePacketsForChanges(final String languageKey, @Nonnull final Map<String, Map<String, String>> changed, @Nonnull final Map<String, Map<String, String>> removed) {
        final Map<String, String> removedMessages = this.getMessages(removed, languageKey);
        final Map<String, String> changedMessages = this.getMessages(changed, languageKey);
        final int size = (!removedMessages.isEmpty() + !changedMessages.isEmpty()) ? 1 : 0;
        final UpdateTranslations[] packets = new UpdateTranslations[size];
        int index = 0;
        if (!removedMessages.isEmpty()) {
            packets[index++] = new UpdateTranslations(UpdateType.Remove, removedMessages);
        }
        if (!changedMessages.isEmpty()) {
            packets[index] = new UpdateTranslations(UpdateType.AddOrUpdate, changedMessages);
        }
        return packets;
    }
    
    private void addDefaultMessages(@Nonnull final Map<String, String> messages, final boolean isInitial) {
        final Map<String, String> languageMap = this.languages.computeIfAbsent("en-US", k -> new ConcurrentHashMap());
        for (final Map.Entry<String, String> entry : messages.entrySet()) {
            if (entry.getKey() == null || entry.getValue() == null) {
                this.getLogger().at(Level.WARNING).log("Attempted to add invalid default translation message: %s=%s", entry.getKey(), entry.getValue());
            }
            else {
                languageMap.put(entry.getKey(), entry.getValue());
            }
        }
        if (!isInitial) {
            final UpdateTranslations packet = new UpdateTranslations(UpdateType.AddOrUpdate, messages);
            Universe.get().broadcastPacketNoCache(packet);
            final IEventDispatcher<MessagesUpdated, MessagesUpdated> dispatch = HytaleServer.get().getEventBus().dispatchFor((Class<? super MessagesUpdated>)MessagesUpdated.class);
            if (dispatch.hasListener()) {
                final Object2ObjectOpenHashMap<String, Map<String, String>> languageMapping = new Object2ObjectOpenHashMap<String, Map<String, String>>();
                languageMapping.put("en-US", messages);
                dispatch.dispatch(new MessagesUpdated(languageMapping, new Object2ObjectOpenHashMap<String, Map<String, String>>()));
            }
        }
    }
    
    private int loadMessages(final String languageKey, @Nonnull final Path languagePath) throws IOException {
        final Map<String, String> messages = this.languages.computeIfAbsent(languageKey, k -> new ConcurrentHashMap());
        try (final Stream<Path> stream = Files.find(languagePath, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith(".lang") && attr.isRegularFile(), FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) {
            return stream.mapToInt(path -> {
                final String prefix = this.getPrefix(languagePath, path);
                return this.loadMessagesFrom(messages, prefix, path);
            }).sum();
        }
    }
    
    private int loadMessagesFrom(@Nonnull final Map<String, String> messages, final String prefix, @Nonnull final Path path) {
        Map<String, String> properties;
        try (final BufferedReader inputStream = Files.newBufferedReader(path)) {
            properties = LangFileParser.parse(inputStream);
        }
        catch (final Exception e) {
            this.getLogger().at(Level.SEVERE).withCause(new SkipSentryException(e)).log("Error parsing language file: %s", path.toString());
            return 0;
        }
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            final String key = prefix + "." + (String)entry.getKey();
            final String value = entry.getValue();
            final String prev = messages.get(key);
            if (prev != null) {
                if (prev.equals(value)) {
                    continue;
                }
                this.getLogger().at(Level.WARNING).log("'%s' has multiple definitions: `%s` and `%s`", key, prev, value);
            }
            else {
                messages.put(key, value);
            }
        }
        return properties.size();
    }
    
    @Nonnull
    private String getPrefix(@Nonnull final Path languagePath, @Nonnull final Path path) {
        String prefix = "";
        final Path directory = path.getParent();
        if (!languagePath.equals(directory)) {
            final Path relativePath = languagePath.relativize(directory);
            prefix += relativePath.toString().replace(File.separatorChar, '.');
        }
        final String name = path.getFileName().toString();
        prefix += name.substring(0, name.length() - ".lang".length());
        return prefix;
    }
    
    @Nonnull
    public Map<String, String> getMessages(final String language) {
        return this.cachedLanguages.computeIfAbsent(language, key -> Collections.unmodifiableMap((Map<?, ?>)this.getMessages(this.languages, key)));
    }
    
    public Map<String, String> getMessages(@Nonnull final Map<String, Map<String, String>> languageMap, @Nullable final String language) {
        if (language == null) {
            return this.getMessages(languageMap, "en-US");
        }
        final Map<String, String> messages = languageMap.get(language);
        if ("en-US".equals(language)) {
            return (messages != null) ? messages : Collections.emptyMap();
        }
        final String fallback = this.fallbacks.getOrDefault(language, "en-US");
        final Map<String, String> fallbackMessages = this.getMessages(languageMap, fallback);
        if (fallbackMessages == null) {
            return (messages != null) ? messages : Collections.emptyMap();
        }
        if (messages == null) {
            return fallbackMessages;
        }
        final Object2ObjectOpenHashMap<String, String> map = new Object2ObjectOpenHashMap<String, String>(fallbackMessages);
        map.putAll(messages);
        return map;
    }
    
    public void sendTranslations(@Nonnull final PacketHandler packetHandler, final String language) {
        if (this.isDisabled()) {
            return;
        }
        packetHandler.writeNoCache(new UpdateTranslations(UpdateType.Init, this.getMessages(language)));
    }
    
    @Nullable
    public String getMessage(final String language, @Nonnull final String key) {
        final HytaleServerConfig config = HytaleServer.get().getConfig();
        if (config != null && config.isDisplayTmpTagsInStrings()) {
            return this.getMessages(language).get(key);
        }
        final String translatedString = this.getMessages(language).get(key);
        if (translatedString != null) {
            return translatedString.replace("[TMP] ", "").replace("[TMP]", "");
        }
        return null;
    }
    
    static {
        MANIFEST = PluginManifest.corePlugin(I18nModule.class).depends(AssetModule.class).build();
        FALLBACK_LANG_PATH = Paths.get("fallback.lang", new String[0]);
        DEFAULT_GENERATED_PATH = Path.of("Server", "Languages", "en-US");
    }
    
    private class I18nAssetMonitorHandler implements AssetMonitorHandler
    {
        private final Path languagesPath;
        
        public I18nAssetMonitorHandler(final Path languagesPath) {
            this.languagesPath = languagesPath;
        }
        
        @Override
        public Object getKey() {
            return I18nModule.this;
        }
        
        @Override
        public boolean test(final Path path, final EventKind eventKind) {
            return !Files.isDirectory(path, new LinkOption[0]) && path.getFileName().toString().endsWith(".lang") && I18nModule.this.isEnabled();
        }
        
        @Override
        public void accept(final Map<Path, EventKind> map) {
            final Map<String, Map<String, String>> removed = new Object2ObjectOpenHashMap<String, Map<String, String>>();
            final Map<String, Map<String, String>> changed = new Object2ObjectOpenHashMap<String, Map<String, String>>();
            for (Map.Entry<Path, EventKind> entry : map.entrySet()) {
                final Path path = entry.getKey();
                final EventKind eventKind = entry.getValue();
                final Path normalized = path.toAbsolutePath().normalize();
                final Path relativized = this.languagesPath.relativize(normalized);
                if (I18nModule.FALLBACK_LANG_PATH.equals(relativized)) {
                    final Properties properties = new Properties();
                    try {
                        properties.load(Files.newInputStream(path, new OpenOption[0]));
                    }
                    catch (final IOException e) {
                        I18nModule.this.getLogger().at(Level.SEVERE).withCause(e).log("Failed to load fallback languages from: %s", path);
                        continue;
                    }
                    I18nModule.this.fallbacks.clear();
                    final String key;
                    properties.forEach((key, value) -> I18nModule.this.fallbacks.put((String)key, (String)value));
                }
                else {
                    final String languageKey = relativized.getName(0).toString();
                    final Path langPath = this.languagesPath.resolve(languageKey).toAbsolutePath().normalize();
                    final String prefix = I18nModule.this.getPrefix(langPath, normalized);
                    switch (eventKind) {
                        case ENTRY_MODIFY:
                        case ENTRY_DELETE: {
                            final String prefixWithDot = prefix;
                            final Map<String, String> removedMessages = removed.computeIfAbsent(languageKey, k -> new Object2ObjectOpenHashMap());
                            final Map<String, String> messages = I18nModule.this.languages.computeIfAbsent(languageKey, k -> new ConcurrentHashMap());
                            final Iterator<String> iterator = messages.keySet().iterator();
                            while (iterator.hasNext()) {
                                final String key = iterator.next();
                                if (key.startsWith(prefixWithDot)) {
                                    removedMessages.put(key, "");
                                    iterator.remove();
                                }
                            }
                            if (eventKind == EventKind.ENTRY_DELETE) {
                                continue;
                            }
                        }
                        case ENTRY_CREATE: {
                            final Map<String, String> changedMessages = changed.computeIfAbsent(languageKey, k -> new Object2ObjectOpenHashMap());
                            I18nModule.this.loadMessagesFrom(changedMessages, prefix, path);
                            continue;
                        }
                    }
                }
            }
            if (removed.isEmpty() && changed.isEmpty()) {
                return;
            }
            for (final Map.Entry<String, Map<String, String>> changedLang : changed.entrySet()) {
                final Map<String, String> messages2 = I18nModule.this.languages.computeIfAbsent((String)changedLang.getKey(), k -> new ConcurrentHashMap());
                messages2.putAll(changedLang.getValue());
                I18nModule.this.cachedLanguages.remove(changedLang.getKey());
            }
            for (final Map.Entry<String, Map<String, String>> removedLang : removed.entrySet()) {
                I18nModule.this.cachedLanguages.remove(removedLang.getKey());
                final Map<String, String> orig = I18nModule.this.getMessages(removedLang.getKey());
                final Map<String, String> changedMessages2 = changed.computeIfAbsent((String)removedLang.getKey(), k -> new Object2ObjectOpenHashMap());
                final Iterator<String> iterator2 = removedLang.getValue().keySet().iterator();
                while (iterator2.hasNext()) {
                    final String removedKey = iterator2.next();
                    if (changedMessages2.containsKey(removedKey)) {
                        iterator2.remove();
                    }
                    else {
                        final String fallback = orig.get(removedKey);
                        if (fallback == null) {
                            continue;
                        }
                        iterator2.remove();
                        changedMessages2.put(removedKey, fallback);
                    }
                }
            }
            final List<PlayerRef> players = Universe.get().getPlayers();
            final Map<String, UpdateTranslations[]> updatePackets = new Object2ObjectOpenHashMap<String, UpdateTranslations[]>();
            for (final PlayerRef playerRef : players) {
                final PacketHandler handler = playerRef.getPacketHandler();
                final String languageKey2 = playerRef.getLanguage();
                UpdateTranslations[] packets = updatePackets.get(languageKey2);
                if (packets == null) {
                    packets = I18nModule.this.getUpdatePacketsForChanges(languageKey2, changed, removed);
                    updatePackets.put(languageKey2, packets);
                }
                if (packets.length != 0) {
                    handler.write((Packet[])packets);
                }
            }
            final IEventDispatcher<MessagesUpdated, MessagesUpdated> dispatch = HytaleServer.get().getEventBus().dispatchFor((Class<? super MessagesUpdated>)MessagesUpdated.class);
            if (dispatch.hasListener()) {
                dispatch.dispatch(new MessagesUpdated(changed, removed));
            }
            I18nModule.this.getLogger().at(Level.INFO).log("Handled language changes for: %s", changed.keySet());
        }
    }
}
