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

package com.hypixel.hytale.builtin.teleport;

import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig;
import com.hypixel.hytale.protocol.packets.worldmap.ContextMenuItem;
import com.hypixel.hytale.protocol.packets.worldmap.MapMarker;
import com.hypixel.hytale.server.core.util.PositionUtil;
import com.hypixel.hytale.server.core.universe.world.worldmap.markers.MapMarkerTracker;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.server.core.modules.entity.component.HiddenFromAdventurePlayers;
import com.hypixel.hytale.server.core.entity.nameplate.Nameplate;
import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent;
import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox;
import com.hypixel.hytale.server.core.modules.entity.component.Intangible;
import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Transform;
import java.util.Iterator;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.assetstore.map.DefaultAssetMap;
import com.hypixel.hytale.logger.HytaleLogger;
import org.bson.BsonArray;
import java.nio.file.Path;
import java.util.logging.Level;
import org.bson.BsonValue;
import com.hypixel.hytale.server.core.util.BsonUtil;
import org.bson.BsonDocument;
import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.event.EventRegistry;
import com.hypixel.hytale.server.core.command.system.CommandRegistry;
import com.hypixel.hytale.server.core.universe.world.events.AllWorldsLoadedEvent;
import com.hypixel.hytale.server.core.universe.world.worldmap.WorldMapManager;
import com.hypixel.hytale.server.core.universe.world.events.AddWorldEvent;
import com.hypixel.hytale.server.core.universe.world.events.ChunkPreLoadProcessEvent;
import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset;
import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent;
import com.hypixel.hytale.builtin.teleport.commands.teleport.SpawnCommand;
import com.hypixel.hytale.builtin.teleport.commands.warp.WarpCommand;
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
import com.hypixel.hytale.builtin.teleport.commands.teleport.TeleportCommand;
import java.util.concurrent.ConcurrentHashMap;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.asset.type.model.config.Model;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nonnull;
import java.util.concurrent.atomic.AtomicBoolean;
import com.hypixel.hytale.builtin.teleport.components.TeleportHistory;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;

public class TeleportPlugin extends JavaPlugin
{
    private static TeleportPlugin instance;
    public static final String WARP_MODEL_ID = "Warp";
    private ComponentType<EntityStore, TeleportHistory> teleportHistoryComponentType;
    private ComponentType<EntityStore, WarpComponent> warpComponentType;
    @Nonnull
    private final AtomicBoolean loaded;
    @Nonnull
    private final ReentrantLock saveLock;
    @Nonnull
    private final AtomicBoolean postSaveRedo;
    @Nonnull
    private final Map<String, Warp> warps;
    private Model warpModel;
    
    @Nonnull
    public static TeleportPlugin get() {
        return TeleportPlugin.instance;
    }
    
    public TeleportPlugin(@Nonnull final JavaPluginInit init) {
        super(init);
        this.loaded = new AtomicBoolean();
        this.saveLock = new ReentrantLock();
        this.postSaveRedo = new AtomicBoolean(false);
        this.warps = new ConcurrentHashMap<String, Warp>();
    }
    
    @Nonnull
    public ComponentType<EntityStore, TeleportHistory> getTeleportHistoryComponentType() {
        return this.teleportHistoryComponentType;
    }
    
    public boolean isWarpsLoaded() {
        return this.loaded.get();
    }
    
    @Override
    protected void setup() {
        TeleportPlugin.instance = this;
        final CommandRegistry commandRegistry = this.getCommandRegistry();
        final EventRegistry eventRegistry = this.getEventRegistry();
        commandRegistry.registerCommand(new TeleportCommand());
        commandRegistry.registerCommand(new WarpCommand());
        commandRegistry.registerCommand(new SpawnCommand());
        eventRegistry.register(LoadedAssetsEvent.class, ModelAsset.class, this::onModelAssetChange);
        eventRegistry.registerGlobal(ChunkPreLoadProcessEvent.class, this::onChunkPreLoadProcess);
        eventRegistry.registerGlobal(AddWorldEvent.class, event -> event.getWorld().getWorldMapManager().addMarkerProvider("warps", WarpMarkerProvider.INSTANCE));
        eventRegistry.registerGlobal(AllWorldsLoadedEvent.class, event -> this.loadWarps());
        this.teleportHistoryComponentType = EntityStore.REGISTRY.registerComponent(TeleportHistory.class, TeleportHistory::new);
        this.warpComponentType = EntityStore.REGISTRY.registerComponent(WarpComponent.class, () -> {
            throw new UnsupportedOperationException("WarpComponent must be created manually");
        });
    }
    
    @Override
    protected void start() {
        final ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset("Warp");
        if (modelAsset == null) {
            throw new IllegalStateException(String.format("Default warp model '%s' not found", "Warp"));
        }
        this.warpModel = Model.createUnitScaleModel(modelAsset);
    }
    
    @Override
    protected void shutdown() {
    }
    
    public void loadWarps() {
        BsonDocument document = null;
        final Path universePath = Universe.get().getPath();
        final Path oldPath = universePath.resolve("warps.bson");
        final Path path = universePath.resolve("warps.json");
        if (Files.exists(oldPath, new LinkOption[0]) && !Files.exists(path, new LinkOption[0])) {
            try {
                Files.move(oldPath, path, new CopyOption[0]);
            }
            catch (final IOException ex) {}
        }
        if (Files.exists(path, new LinkOption[0])) {
            document = BsonUtil.readDocument(path).join();
        }
        if (document != null) {
            final BsonArray bsonWarps = document.containsKey("Warps") ? document.getArray("Warps") : document.getArray("warps");
            this.warps.clear();
            for (final Warp warp : Warp.ARRAY_CODEC.decode(bsonWarps)) {
                this.warps.put(warp.getId().toLowerCase(), warp);
            }
            this.getLogger().at(Level.INFO).log("Loaded %d warps", bsonWarps.size());
        }
        else {
            this.getLogger().at(Level.INFO).log("Loaded 0 warps (No warps.json found)");
        }
        this.loaded.set(true);
    }
    
    private void saveWarps0() {
        final Warp[] array = this.warps.values().toArray(Warp[]::new);
        final BsonDocument document = new BsonDocument("Warps", Warp.ARRAY_CODEC.encode(array));
        final Path path = Universe.get().getPath().resolve("warps.json");
        BsonUtil.writeDocument(path, document).join();
        this.getLogger().at(Level.INFO).log("Saved %d warps to warps.json", array.length);
    }
    
    public void saveWarps() {
        if (this.saveLock.tryLock()) {
            try {
                this.saveWarps0();
            }
            catch (final Throwable e) {
                this.getLogger().at(Level.SEVERE).withCause(e).log("Failed to save warps:");
            }
            finally {
                this.saveLock.unlock();
            }
            if (this.postSaveRedo.getAndSet(false)) {
                this.saveWarps();
            }
        }
        else {
            this.postSaveRedo.set(true);
        }
    }
    
    public Map<String, Warp> getWarps() {
        return this.warps;
    }
    
    private void onModelAssetChange(@Nonnull final LoadedAssetsEvent<String, ModelAsset, DefaultAssetMap<String, ModelAsset>> event) {
        final Map<String, ModelAsset> modelMap = event.getLoadedAssets();
        final ModelAsset modelAsset = modelMap.get("Warp");
        if (modelAsset == null) {
            return;
        }
        this.warpModel = Model.createUnitScaleModel(modelAsset);
    }
    
    private void onChunkPreLoadProcess(@Nonnull final ChunkPreLoadProcessEvent event) {
        final WorldChunk chunk = event.getChunk();
        final BlockChunk blockChunk = chunk.getBlockChunk();
        if (blockChunk == null) {
            return;
        }
        final int chunkX = blockChunk.getX();
        final int chunkZ = blockChunk.getZ();
        final World world = chunk.getWorld();
        final String worldName = world.getName();
        for (final Map.Entry<String, Warp> warpEntry : this.warps.entrySet()) {
            final Warp warp = warpEntry.getValue();
            final Transform transform = warp.getTransform();
            if (transform == null) {
                continue;
            }
            final Vector3d position = transform.getPosition();
            if (!ChunkUtil.isInsideChunk(chunkX, chunkZ, MathUtil.floor(position.x), MathUtil.floor(position.z))) {
                continue;
            }
            if (!warp.getWorld().equals(worldName)) {
                continue;
            }
            world.execute(() -> {
                final Store<EntityStore> store = world.getEntityStore().getStore();
                store.addEntity(this.createWarp(warp, store), AddReason.LOAD);
            });
        }
    }
    
    @Nonnull
    public Holder<EntityStore> createWarp(@Nonnull final Warp warp, @Nonnull final Store<EntityStore> store) {
        final Transform transform = warp.getTransform();
        final Holder<EntityStore> holder = EntityStore.REGISTRY.newHolder();
        holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(transform.getPosition(), transform.getRotation()));
        holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId()));
        holder.ensureComponent(Intangible.getComponentType());
        holder.addComponent(BoundingBox.getComponentType(), new BoundingBox(this.warpModel.getBoundingBox()));
        holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(this.warpModel));
        holder.addComponent(Nameplate.getComponentType(), new Nameplate(warp.getId()));
        holder.ensureComponent(HiddenFromAdventurePlayers.getComponentType());
        holder.ensureComponent(EntityStore.REGISTRY.getNonSerializedComponentType());
        holder.addComponent(this.warpComponentType, new WarpComponent(warp));
        return holder;
    }
    
    record WarpComponent(Warp warp) implements Component<EntityStore> {
        public static ComponentType<EntityStore, WarpComponent> getComponentType() {
            return TeleportPlugin.get().warpComponentType;
        }
        
        @Nonnull
        @Override
        public Component<EntityStore> clone() {
            return new WarpComponent(this.warp);
        }
    }
    
    public static class WarpMarkerProvider implements WorldMapManager.MarkerProvider
    {
        public static final WarpMarkerProvider INSTANCE;
        
        @Override
        public void update(@Nonnull final World world, @Nonnull final MapMarkerTracker tracker, final int chunkViewRadius, final int playerChunkX, final int playerChunkZ) {
            final Map<String, Warp> warps = TeleportPlugin.get().getWarps();
            if (warps.isEmpty()) {
                return;
            }
            final GameplayConfig gameplayConfig = world.getGameplayConfig();
            if (!gameplayConfig.getWorldMapConfig().isDisplayWarps()) {
                return;
            }
            for (Warp warp : warps.values()) {
                if (!warp.getWorld().equals(world.getName())) {
                    continue;
                }
                tracker.trySendMarker(chunkViewRadius, playerChunkX, playerChunkZ, warp.getTransform().getPosition(), warp.getTransform().getRotation().getYaw(), "Warp-" + warp.getId(), "Warp: " + warp.getId(), warp, (id, name, w) -> new MapMarker(id, name, "Warp.png", PositionUtil.toTransformPacket(w.getTransform()), null));
            }
        }
        
        static {
            INSTANCE = new WarpMarkerProvider();
        }
    }
}
