package org.dimdev.vanillafix.profiler.mixins.client;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiNewChat;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.profiler.ISnooperInfo;
import net.minecraft.profiler.Profiler;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.IThreadListener;
import net.minecraft.util.text.TextComponentTranslation;
import org.dimdev.vanillafix.profiler.IPatchedMinecraftServer;
import org.lwjgl.input.Keyboard;
import org.spongepowered.asm.lib.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;

@Mixin(Minecraft.class)
public abstract class MixinMinecraft implements IThreadListener, ISnooperInfo {
    @Shadow @Nullable private IntegratedServer integratedServer;
    @Shadow @Final public Profiler profiler;
    @Shadow public GameSettings gameSettings;
    @Shadow protected abstract void displayDebugInfo(long elapsedTicksTime);

    @Shadow protected abstract void debugFeedbackTranslated(String untranslatedTemplate, Object... objs);
    @Shadow private String debugProfilerName;
    private boolean useIntegratedServerProfiler = false;

    /** @reason Implement using Ctrl + 0-9 to select profiler sections 10-19. */
    @ModifyVariable(method = "updateDebugProfilerName", at = @At("HEAD"), ordinal = 0)
    private int getKeyCountForProfilerNameUpdate(int keyCount) {
        return GuiScreen.isCtrlKeyDown() ? keyCount + 10 : keyCount;
    }

    /** @reason Implement F3 + S to toggle between client and integrated server profilers. */
    @Inject(method = "processKeyF3", at = @At("HEAD"), cancellable = true)
    private void checkF3S(int auxKey, CallbackInfoReturnable<Boolean> cir) {
        if (auxKey == Keyboard.KEY_S) {
            if (integratedServer != null) {
                useIntegratedServerProfiler = !useIntegratedServerProfiler;
                if (useIntegratedServerProfiler) {
                    debugFeedbackTranslated("vanillafix.debug.switch_profiler.server");
                } else {
                    debugFeedbackTranslated("vanillafix.debug.switch_profiler.client");
                }
            }
            cir.setReturnValue(true);
            cir.cancel();
        }
    }

    /** @reason Add the F3 + S help message to the F3 + Q debug help menu. */
    @Inject(method = "processKeyF3", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiNewChat;printChatMessage(Lnet/minecraft/util/text/ITextComponent;)V", ordinal = 9), locals = LocalCapture.CAPTURE_FAILHARD)
    private void addF3SHelpMessage(int auxKey, CallbackInfoReturnable<Boolean> cir, GuiNewChat chatGui) {
        chatGui.printChatMessage(new TextComponentTranslation("vanillafix.debug.switch_profiler.help"));
    }

    /** @reason Use the integrated server profiler rather than client profiler after F3 + S was pressed. */
    @SuppressWarnings("InvalidMemberReference") // https://github.com/minecraft-dev/MinecraftDev/issues/387
    @Redirect(method = {"displayDebugInfo", "updateDebugProfilerName"}, at = @At(value = "FIELD", target = "Lnet/minecraft/client/Minecraft;profiler:Lnet/minecraft/profiler/Profiler;"))
    private Profiler getCurrentProfiler(Minecraft minecraft) {
        return useIntegratedServerProfiler && integratedServer != null ? integratedServer.profiler : profiler;
    }

    /**
     * @reason Profiler isn't safe to use async, so get the results from server's last tick if server
     * profilder is being displayed.
     * <p>
     * Note: profilerName is always "root" client-side
     */
    @SuppressWarnings("InvalidMemberReference") // https://github.com/minecraft-dev/MinecraftDev/issues/387
    @Redirect(method = {"displayDebugInfo", "updateDebugProfilerName"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/profiler/Profiler;getProfilingData(Ljava/lang/String;)Ljava/util/List;"))
    private List<Profiler.Result> getProfilerData(Profiler profiler, String profilerName) {
        if (useIntegratedServerProfiler && integratedServer != null) {
            return new ArrayList<>(((IPatchedMinecraftServer) integratedServer).getLastProfilerData());
        } else {
            return profiler.getProfilingData(profilerName);
        }
    }

    /** @reason Get the correct debug profiler name */
    @Redirect(method = "updateDebugProfilerName", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Minecraft;debugProfilerName:Ljava/lang/String;", opcode = Opcodes.GETFIELD))
    private String getDebugProfilerName(Minecraft mc) {
        if (useIntegratedServerProfiler && integratedServer != null) {
            return ((IPatchedMinecraftServer) integratedServer).getProfilerName();
        } else {
            return debugProfilerName;
        }
    }

    /** @reason Set the correct debug profiler name */
    @Redirect(method = "updateDebugProfilerName", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Minecraft;debugProfilerName:Ljava/lang/String;", opcode = Opcodes.PUTFIELD))
    private void setDebugProfilerName(Minecraft mc, String debugProfilerName) {
        if (useIntegratedServerProfiler && integratedServer != null) {
            ((IPatchedMinecraftServer) integratedServer).setProfilerName(debugProfilerName);
        } else {
            this.debugProfilerName = debugProfilerName;
        }
    }

    /**
     * @reason Enable profiling for the integrated server too. Reset useIntegratedServerProfiler to
     * false if the integrated server becomes null.
     */
    @Inject(method = "runGameLoop", at = @At(value = "FIELD", target = "Lnet/minecraft/client/settings/GameSettings;showDebugInfo:Z"))
    private void enableIntegratedServerProfiling(CallbackInfo ci) {
        if (useIntegratedServerProfiler && integratedServer == null) {
            useIntegratedServerProfiler = false;
        }

        if (gameSettings.showDebugInfo && gameSettings.showDebugProfilerChart && useIntegratedServerProfiler && !gameSettings.hideGUI) {
            if (!integratedServer.profiler.profilingEnabled) {
                integratedServer.enableProfiling();
            } else if (((IPatchedMinecraftServer) integratedServer).getLastProfilerData() != null) {
                displayDebugInfo(0);
            }
        } else if (integratedServer != null) {
            integratedServer.profiler.profilingEnabled = false;
        }
    }

    /** @reason Disable client profiling when profiling the integrated server. */
    @Redirect(method = "runGameLoop", at = @At(value = "FIELD", target = "Lnet/minecraft/client/settings/GameSettings;showDebugProfilerChart:Z"))
    private boolean disableClientProfilingIfUnnecessary(GameSettings gameSettings) {
        return gameSettings.showDebugProfilerChart && !useIntegratedServerProfiler;
    }
}