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

package com.hypixel.hytale.server.core.io.handlers;

import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.common.util.CompletableFutureUtil;
import com.hypixel.hytale.protocol.packets.setup.WorldLoadFinished;
import com.hypixel.hytale.protocol.packets.setup.WorldLoadProgress;
import com.hypixel.hytale.server.core.modules.i18n.I18nModule;
import com.hypixel.hytale.server.core.asset.AssetRegistryLoader;
import com.hypixel.hytale.server.core.asset.common.events.SendCommonAssetsEvent;
import com.hypixel.hytale.server.core.util.DumpUtil;
import com.hypixel.hytale.protocol.packets.connection.DisconnectType;
import com.hypixel.hytale.protocol.io.netty.ProtocolUtil;
import com.hypixel.hytale.event.IEventDispatcher;
import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule;
import com.hypixel.hytale.server.core.Constants;
import com.hypixel.hytale.server.core.event.events.player.PlayerSetupDisconnectEvent;
import io.netty.channel.ChannelHandlerContext;
import com.hypixel.hytale.protocol.packets.setup.PlayerOptions;
import com.hypixel.hytale.protocol.packets.setup.ViewRadius;
import com.hypixel.hytale.protocol.packets.setup.RequestAssets;
import com.hypixel.hytale.protocol.packets.connection.Disconnect;
import com.hypixel.hytale.protocol.Asset;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.protocol.packets.auth.ClientReferral;
import com.hypixel.hytale.server.core.HytaleServerConfig;
import com.hypixel.hytale.protocol.packets.interface_.ServerInfo;
import com.hypixel.hytale.server.core.asset.common.CommonAssetModule;
import com.hypixel.hytale.protocol.packets.setup.WorldSettings;
import java.util.concurrent.CompletableFuture;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.protocol.Packet;
import com.hypixel.hytale.server.core.event.events.player.PlayerSetupConnectEvent;
import java.util.logging.Level;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.io.PacketHandler;
import com.hypixel.hytale.server.core.io.netty.NettyUtil;
import com.hypixel.hytale.server.core.auth.PlayerAuthentication;
import com.hypixel.hytale.server.core.io.ProtocolVersion;
import javax.annotation.Nonnull;
import io.netty.channel.Channel;
import com.hypixel.hytale.server.core.asset.common.PlayerCommonAssets;
import com.hypixel.hytale.protocol.HostAddress;
import java.util.UUID;

public class SetupPacketHandler extends GenericConnectionPacketHandler
{
    private final UUID uuid;
    private final String username;
    private final byte[] referralData;
    private final HostAddress referralSource;
    private PlayerCommonAssets assets;
    private boolean receivedRequest;
    private int clientViewRadiusChunks;
    
    public SetupPacketHandler(@Nonnull final Channel channel, @Nonnull final ProtocolVersion protocolVersion, final String language, final UUID uuid, final String username) {
        this(channel, protocolVersion, language, uuid, username, null, null);
    }
    
    public SetupPacketHandler(@Nonnull final Channel channel, @Nonnull final ProtocolVersion protocolVersion, final String language, final UUID uuid, final String username, final byte[] referralData, final HostAddress referralSource) {
        super(channel, protocolVersion, language);
        this.clientViewRadiusChunks = 6;
        this.uuid = uuid;
        this.username = username;
        this.referralData = referralData;
        this.referralSource = referralSource;
        this.auth = null;
    }
    
    public SetupPacketHandler(@Nonnull final Channel channel, @Nonnull final ProtocolVersion protocolVersion, final String language, @Nonnull final PlayerAuthentication auth) {
        super(channel, protocolVersion, language);
        this.clientViewRadiusChunks = 6;
        this.uuid = auth.getUuid();
        this.username = auth.getUsername();
        this.auth = auth;
        this.referralData = auth.getReferralData();
        this.referralSource = auth.getReferralSource();
    }
    
    @Nonnull
    @Override
    public String getIdentifier() {
        return "{Setup(" + NettyUtil.formatRemoteAddress(this.channel) + "), " + this.username + ", " + String.valueOf(this.uuid) + ", " + ((this.auth != null) ? "SECURE" : "INSECURE");
    }
    
    public void registered0(@Nonnull final PacketHandler oldHandler) {
        final HytaleServerConfig.TimeoutProfile timeouts = HytaleServer.get().getConfig().getConnectionTimeouts();
        this.enterStage("setup:world-settings", timeouts.getSetupWorldSettings(), () -> this.assets != null);
        if (this.referralSource != null) {
            HytaleLogger.getLogger().at(Level.INFO).log("Player %s referred from %s:%d with %d bytes of data", this.username, this.referralSource.host, this.referralSource.port, (this.referralData != null) ? this.referralData.length : 0);
        }
        final PlayerSetupConnectEvent event = HytaleServer.get().getEventBus().dispatchFor((Class<? super PlayerSetupConnectEvent>)PlayerSetupConnectEvent.class).dispatch(new PlayerSetupConnectEvent(this, this.username, this.uuid, this.auth, this.referralData, this.referralSource));
        if (event.isCancelled()) {
            this.disconnect(event.getReason());
            return;
        }
        final ClientReferral clientReferral = event.getClientReferral();
        if (clientReferral != null) {
            this.writeNoCache(clientReferral);
            return;
        }
        final PlayerRef otherPlayer = Universe.get().getPlayer(this.uuid);
        if (otherPlayer != null) {
            HytaleLogger.getLogger().at(Level.INFO).log("Found match of player %s on %s", this.uuid, otherPlayer.getUsername());
            final Channel otherPlayerChannel = otherPlayer.getPacketHandler().getChannel();
            if (!NettyUtil.isFromSameOrigin(otherPlayerChannel, this.channel)) {
                this.disconnect("You are already logged in on that account!");
                otherPlayer.sendMessage(Message.translation("server.io.setuppackethandler.otherLoginAttempt"));
                return;
            }
            final Ref<EntityStore> reference = otherPlayer.getReference();
            if (reference != null) {
                final World world = reference.getStore().getExternalData().getWorld();
                if (world != null) {
                    final CompletableFuture<Void> removalFuture = new CompletableFuture<Void>();
                    world.execute(() -> {
                        otherPlayer.getPacketHandler().disconnect("You logged in again with the account!");
                        world.execute(() -> removalFuture.complete(null));
                        return;
                    });
                    removalFuture.join();
                }
                else {
                    otherPlayer.getPacketHandler().disconnect("You logged in again with the account!");
                }
            }
        }
        PacketHandler.logConnectionTimings(this.channel, "Load Player Config", Level.FINE);
        final WorldSettings worldSettings = new WorldSettings();
        worldSettings.worldHeight = 320;
        final Asset[] requiredAssets = CommonAssetModule.get().getRequiredAssets();
        this.assets = new PlayerCommonAssets(requiredAssets);
        worldSettings.requiredAssets = requiredAssets;
        this.write(worldSettings);
        final HytaleServerConfig serverConfig = HytaleServer.get().getConfig();
        this.write(new ServerInfo(HytaleServer.get().getServerName(), serverConfig.getMotd(), serverConfig.getMaxPlayers()));
        this.continueStage("setup:assets-request", timeouts.getSetupAssetsRequest(), () -> this.receivedRequest);
    }
    
    @Override
    public void accept(@Nonnull final Packet packet) {
        switch (packet.getId()) {
            case 1: {
                this.handle((Disconnect)packet);
                break;
            }
            case 23: {
                this.handle((RequestAssets)packet);
                break;
            }
            case 32: {
                this.handle((ViewRadius)packet);
                break;
            }
            case 33: {
                this.handle((PlayerOptions)packet);
                break;
            }
            default: {
                this.disconnect("Protocol error: unexpected packet " + packet.getId());
                break;
            }
        }
    }
    
    @Override
    public void closed(final ChannelHandlerContext ctx) {
        super.closed(ctx);
        final IEventDispatcher<PlayerSetupDisconnectEvent, PlayerSetupDisconnectEvent> dispatcher = HytaleServer.get().getEventBus().dispatchFor((Class<? super PlayerSetupDisconnectEvent>)PlayerSetupDisconnectEvent.class);
        if (dispatcher.hasListener()) {
            dispatcher.dispatch(new PlayerSetupDisconnectEvent(this.username, this.uuid, this.auth, this.disconnectReason));
        }
        if (Constants.SINGLEPLAYER) {
            if (Universe.get().getPlayerCount() == 0) {
                HytaleLogger.getLogger().at(Level.INFO).log("No players left on singleplayer server shutting down!");
                HytaleServer.get().shutdownServer();
            }
            else if (SingleplayerModule.isOwner(this.auth, this.uuid)) {
                HytaleLogger.getLogger().at(Level.INFO).log("Owner left the singleplayer server shutting down!");
                Universe.get().getPlayers().forEach(p -> p.getPacketHandler().disconnect(this.username + " left! Shutting down singleplayer world!"));
                HytaleServer.get().shutdownServer();
            }
        }
    }
    
    public void handle(@Nonnull final Disconnect packet) {
        this.disconnectReason.setClientDisconnectType(packet.type);
        HytaleLogger.getLogger().at(Level.INFO).log("%s - %s at %s left with reason: %s - %s", this.uuid, this.username, NettyUtil.formatRemoteAddress(this.channel), packet.type.name(), packet.reason);
        ProtocolUtil.closeApplicationConnection(this.channel);
        if (packet.type == DisconnectType.Crash && Constants.SINGLEPLAYER && (Universe.get().getPlayerCount() == 0 || SingleplayerModule.isOwner(this.auth, this.uuid))) {
            DumpUtil.dump(true, false);
        }
    }
    
    public void handle(@Nonnull final RequestAssets packet) {
        if (this.receivedRequest) {
            throw new IllegalArgumentException("Received duplicate RequestAssets!");
        }
        this.receivedRequest = true;
        PacketHandler.logConnectionTimings(this.channel, "Request Assets", Level.FINE);
        final CompletableFuture<Void> future = CompletableFutureUtil._catch((CompletableFuture<Void>)HytaleServer.get().getEventBus().dispatchForAsync((Class<? super SendCommonAssetsEvent>)SendCommonAssetsEvent.class).dispatch(new SendCommonAssetsEvent(this, packet.assets)).thenAccept(event -> {
            if (!this.channel.isActive()) {
                return;
            }
            else {
                PacketHandler.logConnectionTimings(this.channel, "Send Common Assets", Level.FINE);
                this.assets.sent(event.getRequestedAssets());
                AssetRegistryLoader.sendAssets(this);
                I18nModule.get().sendTranslations(this, this.language);
                PacketHandler.logConnectionTimings(this.channel, "Send Config Assets", Level.FINE);
                this.write(new WorldLoadProgress("Loading world...", 0, 0));
                this.write(new WorldLoadFinished());
                return;
            }
        }).exceptionally(throwable -> {
            if (!this.channel.isActive()) {
                return null;
            }
            else {
                this.disconnect("An exception occurred while trying to login!");
                throw new RuntimeException("Exception when player was joining", throwable);
            }
        }));
        final HytaleServerConfig.TimeoutProfile timeouts = HytaleServer.get().getConfig().getConnectionTimeouts();
        this.continueStage("setup:send-assets", timeouts.getSetupSendAssets(), () -> future.isDone() || !future.cancel(true));
    }
    
    public void handle(@Nonnull final ViewRadius packet) {
        this.clientViewRadiusChunks = MathUtil.ceil(packet.value / 32.0f);
    }
    
    public void handle(@Nonnull final PlayerOptions packet) {
        if (!this.receivedRequest) {
            throw new IllegalArgumentException("Hasn't received RequestAssets yet!");
        }
        PacketHandler.logConnectionTimings(this.channel, "Player Options", Level.FINE);
        if (!this.channel.isActive()) {
            return;
        }
        if (packet.skin != null) {
            try {
                CosmeticsModule.get().validateSkin(packet.skin);
            }
            catch (final CosmeticsModule.InvalidSkinException e) {
                final String msg = """
                                   Your skin contains parts that aren't available on this server.
                                   This usually happens when assets are out of sync.
                                   
                                   """ + e.getMessage();
                this.disconnect(msg);
                return;
            }
        }
        final CompletableFuture<Void> future = CompletableFutureUtil._catch(Universe.get().addPlayer(this.channel, this.language, this.protocolVersion, this.uuid, this.username, this.auth, this.clientViewRadiusChunks, packet.skin).thenAccept(player -> {
            if (!this.channel.isActive()) {
                return;
            }
            else {
                PacketHandler.logConnectionTimings(this.channel, "Add To Universe", Level.FINE);
                this.clearTimeout();
                return;
            }
        }).exceptionally(throwable -> {
            if (!this.channel.isActive()) {
                return null;
            }
            else {
                this.disconnect("An exception occurred when adding to the universe!");
                throw new RuntimeException("Exception when player adding to universe", throwable);
            }
        }));
        final HytaleServerConfig.TimeoutProfile timeouts = HytaleServer.get().getConfig().getConnectionTimeouts();
        this.continueStage("setup:add-to-universe", timeouts.getSetupAddToUniverse(), () -> future.isDone() || !future.cancel(true));
    }
}
